Common Mistakes
Patterns that look reasonable but fight the module’s design.
1. Forgetting to dispose the pipeline
Section titled “1. Forgetting to dispose the pipeline”ImagePipeline owns a native buffer. Not disposing leaks native memory until the finaliser runs.
// ❌var pipe = ImagePipeline.Load(bytes);pipe.Resize(400, 400);var webp = pipe.Encode(ImageFormat.WebP);// pipe never disposed: leaks until GC
// ✅using var pipe = ImagePipeline.Load(bytes);pipe.Resize(400, 400);var webp = pipe.Encode(ImageFormat.WebP);Prefer using/await using even for single-operation flows.
2. Sharing a pipeline across threads
Section titled “2. Sharing a pipeline across threads”ImagePipeline is not thread-safe. The native buffer is mutable state; concurrent access corrupts it or panics the Rust code.
// ❌ one pipeline, two tasksusing var pipe = ImagePipeline.Load(bytes);await Task.WhenAll( Task.Run(() => pipe.Thumbnail(200, 200).Encode(ImageFormat.Png)), Task.Run(() => pipe.Thumbnail(400, 400).Encode(ImageFormat.Png)));
// ✅ one pipeline per taskawait Task.WhenAll( Task.Run(() => { using var p = ImagePipeline.Load(bytes); p.Thumbnail(200, 200); return p.Encode(ImageFormat.Png); }), Task.Run(() => { using var p = ImagePipeline.Load(bytes); p.Thumbnail(400, 400); return p.Encode(ImageFormat.Png); }));For bounded concurrency over many inputs, use ImageBatch (static).
3. Processing untrusted uploads without ImagingOptions
Section titled “3. Processing untrusted uploads without ImagingOptions”Decompression-bomb attacks (a 20 KB file that decodes to 50,000×50,000) and unexpected formats are common on public endpoints. The defaults are conservative but not tailored to adversarial input.
// ❌using var pipe = ImagePipeline.Load(userUpload);
// ✅using var pipe = ImagePipeline.Load(userUpload, new ImagingOptions{ MaxInputBytes = 20 * 1024 * 1024, MaxMegapixels = 25, MaxWidth = 4096, MaxHeight = 4096, AllowedFormats = [ImageFormat.Jpeg, ImageFormat.Png, ImageFormat.WebP],});Or start from ImagingOptions.Strict and add MaxWidth/MaxHeight/AllowedFormats.
4. Using Resize when Thumbnail is what you want
Section titled “4. Using Resize when Thumbnail is what you want”Resize(width, height) forces exact dimensions and stretches if the ratio changes. Thumbnail(maxW, maxH) fits into the box and preserves aspect.
// ❌ 1000×500 stretched into 400×400 → squashedpipe.Resize(400, 400);
// ✅ 1000×500 fits into 400×400 as 400×200pipe.Thumbnail(400, 400);Only use Resize(w, h) when you deliberately want to force dimensions.
5. Trying to rotate by non-90° angles
Section titled “5. Trying to rotate by non-90° angles”The native layer only supports lossless quarter-turn rotations (90, 180, 270).
// ❌pipe.Rotate(45); // throws ArgumentException
// ✅pipe.Rotate(90);Arbitrary-angle rotation needs resampling and antialiasing; if you need it, use a full imaging library.
6. Expecting EXIF metadata to survive re-encoding
Section titled “6. Expecting EXIF metadata to survive re-encoding”Every transformation decodes and re-encodes. EXIF is dropped as a side effect (this is also how ImageConverter.StripExif works).
- For thumbnails / resized versions this is usually what you want (metadata stripped for privacy)
- For original-quality re-saves you’d lose camera / GPS / orientation data
Pragmatic.Imaging does not preserve EXIF through re-encoding. If you need it, use a dedicated metadata tool.
7. Expecting the quality argument to affect PNG or WebP
Section titled “7. Expecting the quality argument to affect PNG or WebP”quality applies to JPEG only.
- PNG / BMP / TIFF / GIF are lossless or paletted:
qualityis ignored. - WebP encodes lossless and AVIF uses the library default:
qualityis ignored (animage0.25 limitation).
pipe.Encode(ImageFormat.Png, quality: 50); // quality ignored, normal PNGpipe.Encode(ImageFormat.Jpeg, quality: 75); // quality honouredFor size-constrained lossy output, use JPEG.
8. Expecting Encode to consume the pipeline
Section titled “8. Expecting Encode to consume the pipeline”Encode/EncodeTo do not dispose the pipeline. You can encode several formats from one pipeline state, or keep transforming.
using var pipe = ImagePipeline.Load(bytes);pipe.Thumbnail(400, 400);var webp = pipe.Encode(ImageFormat.WebP); // ✅ still valid afterwardsvar png = pipe.Encode(ImageFormat.Png);The native buffer is released only on Dispose (i.e. at the end of the using).
9. Loading a Stream then expecting to reuse it
Section titled “9. Loading a Stream then expecting to reuse it”ImagePipeline.Load(Stream) reads the whole stream. After load, the position is at the end.
using var fs = File.OpenRead("photo.jpg");using var pipe = ImagePipeline.Load(fs);// fs is now at EOF: re-reading needs fs.Position = 0If you need to inspect first, call ImageInfo.FromStream (it reads only the header), then rewind before Load.
10. Assuming Thumbnail upscales small sources
Section titled “10. Assuming Thumbnail upscales small sources”By default Thumbnail never enlarges: a source already smaller than the box comes back unchanged.
// Source is 300×200pipe.Thumbnail(400, 400); // stays 300×200pipe.Thumbnail(400, 400, allowUpscale: true); // 400×267Pass allowUpscale: true to enlarge, or enforce a minimum with Resize.
11. Expecting QR codes to embed logos or custom colours
Section titled “11. Expecting QR codes to embed logos or custom colours”QrCode.GeneratePng produces a black/white QR with configurable module size and margin: no colour, no logo, no ECC tuning. For richer output, use a dedicated library.
12. Running on Alpine Linux
Section titled “12. Running on Alpine Linux”The native binary is built against glibc; Alpine uses musl. The app fails at the first image operation with DllNotFoundException. Use a Debian/Ubuntu base image, or build the native library for musl yourself (non-trivial). See native-deployment.md.
13. Expecting cancellation to interrupt a native call
Section titled “13. Expecting cancellation to interrupt a native call”The native functions run to completion once invoked: there’s no cancellation cooperation on the Rust side. CancellationToken is checked between operations (before/after each native call), not during one. For long batches, use ImageBatch with maxConcurrency to cap work in flight.
Related
Section titled “Related”- troubleshooting.md: runtime errors
- concepts.md: why the API is shaped this way
