Repository navigation
Releases: sdcb/SimdCodecs
Release list
v1.2.0: QOI and Netpbm
New packages
- Sdcb.SimdCodecs.Qoi: QOI ("Quite OK Image") decoder and encoder, written from the specification. It decodes RGB and RGBA straight into a caller's buffer in any 8-bit layout, with no allocation. It encodes from RGBA, BGRA, RGB, BGR or grey, to an array or a stream.
- Sdcb.SimdCodecs.Pnm: the Netpbm family, decode and encode.
- P1–P6 PBM / PGM / PPM, plain (ASCII) and raw, with maxval up to 65535 (16-bit samples).
- P7 PAM with 1–4 channels.
- PFM, 32-bit float, both byte orders.
Facade
ImageFormat.Qoinow decodes; it was recognised only before.- New
ImageFormat.PnmandImageFormat.Pfm, both sniffed by the facade. - PFM returns float samples. Use
PixelConverter.StretchToRgba8to get a viewable image.
Performance
Measured on a Ryzen 7 5800X at 1920×1080, single thread. Figures are the geometric mean of our time ÷ ImageSharp 3.1.12's, so lower is faster.
| Decode | Encode to a stream | |
|---|---|---|
| QOI | 0.29× | 0.49× |
| Netpbm | 0.23× | 0.54× |
Full results are in benchmarks/Sdcb.SimdCodecs.QoiPnm.Benchmarks/results/.
Verification
- Exact against ImageMagick in both directions, for every variant it writes.
- Hand-written streams cover every QOI chunk, plus Netpbm comments, odd maxvals and PFM byte orders.
- Every SIMD tier is tested, and 300k fuzz iterations per format ran clean.
- NativeAOT and Mono AOT (wasm) output matches JIT.
Samples
- LINQPad
qoi: QOI against PNG on size and speed. - LINQPad
netpbm: every format, a hand-written PPM, 16-bit PGM, PFM.
Known gaps
- Netpbm reads only the first image in a file.
- PFM does not decode into 8-bit layouts.
- PAM DEPTH is limited to 1–4.
- QOI has not been benchmarked against a native implementation.
- ImageSharp 3.1.12 reads and writes 16-bit PPM little-endian, unlike the spec and ImageMagick, so that case is not timed against it.
Details are in docs/known-gaps.md.
Sdcb.SimdCodecs 1.1.0
Sdcb.SimdCodecs 1.1.0 adds two codecs, BMP and ICO / CUR, each with a decoder and an encoder. Both are pure managed, NativeAOT- and browser-wasm-compatible, and SIMD-accelerated, with a bit-identical scalar fallback.
dotnet add package Sdcb.SimdCodecs # the facade now decodes .bmp, .ico and .cur too
dotnet add package Sdcb.SimdCodecs.Bmp # or just the codec you need
dotnet add package Sdcb.SimdCodecs.IcoNew: Sdcb.SimdCodecs.Bmp
- Decode handles every DIB variant in practical use:
- Headers: OS/2 1.x core, OS/2 2.x (16–64 bytes), Windows
BITMAPINFOHEADER, and V2–V5. - Pixel formats: 1/2/4/8-bit palettes, and 16/24/32-bit pixels with arbitrary
BITFIELDS/ALPHABITFIELDSmasks. - Compression: RLE4 and RLE8 with deltas, OS/2 RLE24 and Huffman 1D, and embedded PNG.
- Also: top-down rows, V5 embedded ICC profiles, OS/2 bitmap arrays, and bare DIBs.
DecodeTo/TryDecodeIntowrite into your buffer in any pixel layout and allocate nothing.
- Headers: OS/2 1.x core, OS/2 2.x (16–64 bytes), Windows
- Encode writes 1/4/8-bit palettes, RLE4 and RLE8, 5-5-5 and 5-6-5, 24-bit and 32-bit BGRA.
- Palettes are exact when the image has few enough colours and quantised otherwise.
- Headers can be core, V3, V4 or V5, and V5 headers can carry an ICC profile.
- A
Streamoverload writes one row at a time.
- Where Windows Imaging Component (WIC), ImageMagick and browsers disagree, the package documents its choice in a Decisions table in its README. One example is whether the 4th byte of a 32-bit
BI_RGBpixel is alpha.
New: Sdcb.SimdCodecs.Ico
- Reads and writes Windows icons (
.ico) and cursors (.cur).- Each entry can be PNG or a DIB with an AND mask.
- Cursors carry hot spots.
- The facade treats each entry as a frame (
TryOpenFrames).TryDecodereturns the largest entry, breaking ties by colour depth. ImageFormat.Curis new..bmp,.icoand.curfiles now decode through the facade instead of being only recognised.
Performance
Measured single-threaded on an AMD Ryzen 7 5800X with .NET 10, using 1920×1080 images:
| Sdcb.SimdCodecs | WIC | ImageSharp 3.1.12 | |
|---|---|---|---|
| BMP decode, geometric mean of 22 layouts (time relative to WIC) | 0.20× | 1× | 1.62× |
| BMP encode, 5-6-5 | 2.5 ms | — | 14.4 ms |
| BMP encode, 8-bit palette from a photo (quantised) | 97 ms | — | 208 ms |
WIC encodes only 24- and 32-bit BMP, and our encoder is more than 20× faster at both. ImageSharp is still faster at one thing: writing 24- or 32-bit output to a stream from RGBA input. It takes 0.28 ms and 0.23 ms against our 0.33 ms and 0.41 ms. From B, G, R(, A) input, we are faster (0.20 ms) or on par (0.23 ms). Full tables are in benchmarks/Sdcb.SimdCodecs.Bmp.Benchmarks/results.
Correctness
- Output matches WIC pixel for pixel on a corpus covering every BMP variant, and on every entry of the test icons. It is also checked against Magick.NET and against independently hand-assembled streams.
- Each vector kernel produces identical output on its AVX2, Vector128 and scalar paths. NativeAOT and Mono AOT produce the same output as JIT.
- The decoder passed a 300,000-iteration fuzz run.
Other changes
- PNG:
TryReadInfo/TryDecodeused to throw on an IHDR whose bit depth is not allowed for its colour type. They now returnfalse, as theTry*contract requires. The BMP fuzzer found this through embedded PNG. - GIF: The palette quantizer is now shared with BMP. Its palette lookup is faster on large images, and the output is byte-for-byte unchanged.
Packages
Sdcb.SimdCodecs (facade), .Abstractions, .Jpeg, .Jpeg.GainMap, .Jpeg.IccInjector, .Png, .Tiff, .Webp, .Gif, .Bmp, .Ico, .Jpeg2000, .Jbig2, .Exif, .Color.Icc. All 15 packages are on NuGet under the MIT licence.
Full changelog: v1.0.1...v1.1.0
Sdcb.SimdCodecs 1.0.1
The first stable release of Sdcb.SimdCodecs: pure-managed image codecs for .NET 10. There are no native dependencies, the libraries are NativeAOT- and browser-wasm-compatible, and the kernels are SIMD-accelerated (AVX2, SSE, AdvSimd, WebAssembly SIMD) with a bit-identical scalar fallback.
dotnet add package Sdcb.SimdCodecsusing Sdcb.SimdCodecs;
using Sdcb.SimdCodecs.Abstractions;
byte[] file = File.ReadAllBytes("photo.webp"); // JPEG, PNG, TIFF, WebP, GIF, JPEG 2000 or JBIG2
if (!SimdCodecs.TryReadInfo(file, out ImageInfo info)) throw new InvalidDataException("Not a supported image");
byte[] rgba = new byte[info.Width * info.Height * 4];
SimdCodecs.TryDecodeIntoRgba8(file, rgba);Formats
| Format | Decode | Encode | Notes |
|---|---|---|---|
| JPEG | ✅ | ✅ | Baseline and progressive, any subsampling, CMYK / YCCK, 1/2–1/8 scaled decode, zero-allocation DecodeTo; the encoder writes baseline, optimised Huffman or progressive streams with EXIF, XMP and ICC |
| Ultra HDR JPEG | ✅ | ✅ | Gain-map read, assembly and HDR reconstruction |
| PNG | ✅ | ✅ | Every bit depth, indexed colour, text chunks, HDR signalling (cICP, mDCV, cLLI) |
| TIFF | ✅ | ✅ | Multi-page; 8/16/32-bit integer and float samples; strips or tiles; reads Deflate, LZW, PackBits and CCITT G3/G4 |
| WebP | ✅ | — | Lossy, lossless, alpha and animation with seeking; output is byte-exact with libwebp |
| GIF | ✅ | ✅ | Stills and animations, every disposal method; quantising encoder |
| JPEG 2000 | ✅ | — | Part 1 (T.800), for PDF /JPXDecode |
| JBIG2 | ✅ | — | T.88, for PDF /JBIG2Decode |
| EXIF | ✅ | ✅ | Read from JPEG, TIFF, PNG and WebP; ExifWriter builds the block the JPEG encoder embeds |
| ICC | ✅ | ✅ | Decoders return embedded profiles; IccProfiles.Describe gives a profile's name and TryGetPrimaries its gamut; bundled sRGB v4 profile |
The facade
A single Sdcb.SimdCodecs reference brings in the whole family, which ships in lockstep under one version, and exposes these entry points:
DetectFormat: identifies a file by its signature. It also recognises formats it does not decode (HEIF, AVIF, JPEG XL, BMP, …), andIsDecodabletells the two kinds apart.TryReadInfo: reads dimensions, channels, bit depth, format, EXIF orientation and frame count from the headers alone.TryDecode: returns the native layout (8/16-bit or float samples) together with the ICC profile and an H.273 colour description.TryDecodeInto: writes RGBA, BGRA, RGB, BGR or grey straight into your buffer, at any row stride.TryOpenFrames: decodes frame by frame, covering animated WebP and GIF frames and multi-page TIFF.PixelConverter.StretchToRgba8: makes float and HDR rasters viewable, with optional sRGB encoding for linear light.
Every Try* call on untrusted input returns false instead of throwing, hanging or allocating without bound.
Performance
Measured single-threaded on an AMD Ryzen 7 5800X with .NET 10. Each time is relative to the native library, so lower is faster:
| Time | |
|---|---|
| JPEG decode to RGBA (446 files), vs. libjpeg-turbo 3.2.0 | 0.82×, with no managed allocation per decode |
| WebP decode, 1920×1080, vs. libwebp 1.6.0 | 0.88× lossy, 0.53× lossy + alpha, 0.72× lossless |
The JPEG decoder is at least as accurate as libjpeg-turbo's default mode. Its output is bit-identical on AVX2, SSE, AdvSimd, scalar, NativeAOT and Mono AOT; CI checks every one of these paths on each push. The full methodology is in the README.
Packages
Sdcb.SimdCodecs (facade), .Abstractions, .Jpeg, .Jpeg.GainMap, .Jpeg.IccInjector, .Png, .Tiff, .Webp, .Gif, .Jpeg2000, .Jbig2, .Exif, .Color.Icc. All 13 are on NuGet and MIT-licensed.
Known limits, such as APNG frames, BigTIFF and 12-bit JPEG, are listed in docs/known-gaps.md (in Chinese).