Skip to content

Sdcb.SimdCodecs 1.1.0

Choose a tag to compare

@sdcb sdcb released this 30 Oct 06:39
· 5 commits to main since this release

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.Ico

New: 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 / ALPHABITFIELDS masks.
    • 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 / TryDecodeInto write into your buffer in any pixel layout and allocate nothing.
  • 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 Stream overload 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_RGB pixel 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). TryDecode returns the largest entry, breaking ties by colour depth.
  • ImageFormat.Cur is new. .bmp, .ico and .cur files 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 / TryDecode used to throw on an IHDR whose bit depth is not allowed for its colour type. They now return false, as the Try* 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