SimdCodecs
Image codecs in pure managed C#: JPEG, PNG, TIFF, WebP, GIF, JPEG 2000 and JBIG2, plus EXIF and ICC. Not a single native DLL — and a JPEG decoder faster than libjpeg-turbo.
dotnet add package Sdcb.SimdCodecs
Why
Reading an image in .NET shouldn’t need a native library
Turning a file into pixels in .NET today means one of three roads:
- System.DrawingGDI+ — Windows only
- SkiaSharp · OpenCvSharp · Magick.NETA native binary per platform: heavy, and AOT / WASM need extra work
- ImageSharpManaged, but 1.77× libjpeg-turbo’s time on JPEG decode
SimdCodecs is the fourth: every codec written in C# from the specification, with hand-written AVX2 / SSE / AdvSimd kernels and a scalar fallback. The bar is not “it works” — it is every encoder and decoder at or beyond its native reference library.
All three share one pixel buffer: decode straight into it, enhance in place, recognise. The first step used to borrow ImageSharp or SkiaSharp — now it is pure C# too.
Formats
One package, seven formats
The Sdcb.SimdCodecs facade sniffs the magic bytes and dispatches; each format is also its own package with a richer API. The ones marked “browsers can’t” are worth a look in the lab.
JPEG
decodeencodeBaseline / progressive, any subsampling, CMYK / YCCK, 1/2–1/8 scaled decode, zero-allocation DecodeTo; encodes optimised Huffman, progressive, EXIF / XMP
Ultra HDR
decodeencodeJPEG gain-map read, assemble and HDR reconstruction (Ultra HDR / Adobe)
PNG
decodeencodeEvery bit depth 1–16 and palettes, Adam7 interlacing, text chunks, HDR signalling (cICP / mDCV / cLLI)
TIFF
decodeencodeMulti-page; 8/16/32-bit integer and float; strips or tiles; Deflate, LZW, PackBits, CCITT G3/G4 fax
browsers can’t → see it in the labWebP
decodeencodeLossy, lossless, alpha, animation with seeking; byte-exact with libwebp
GIF
decodeencode87a / 89a, interlacing, transparency, every disposal; encodes stills and animations (exact palette, or Wu quantization + dithering)
EXIF
decodeencodeRead from JPEG, TIFF and PNG; ExifWriter builds a block for the JPEG encoder
ICC
decodeencodeDecoders return embedded profiles; bundled sRGB v4; splice a profile into an existing JPEG
HEIC
nextWhat iPhones shoot by default, and what Chrome and Firefox can’t open. On its way — pure C#, zero native deps, like the rest.
Star on GitHub to follow along →Quick start
Decode into your own memory — any layout, any stride
using Sdcb.SimdCodecs;
using Sdcb.SimdCodecs.Abstractions;
byte[] file = File.ReadAllBytes("photo.webp"); // JPEG, PNG, TIFF, WebP, GIF, JPEG 2000, JBIG2
// Sniffs the format from the magic bytes; headers only
if (!SimdCodecs.TryReadInfo(file, out ImageInfo info)) throw new InvalidDataException();
Console.WriteLine($"{info.Width}×{info.Height}, {info.BitDepth}-bit, {info.FrameCount} frame(s), {info.Orientation}");
// Decode into a buffer you own (R G B A)
byte[] rgba = new byte[info.Width * info.Height * 4];
SimdCodecs.TryDecodeIntoRgba8(file, rgba);// Straight into SkiaSharp / WPF / OpenCV pixel memory — no RGBA frame in between
using SKBitmap bmp = new(info.Width, info.Height, SKColorType.Bgra8888, SKAlphaType.Unpremul);
unsafe
{
Span<byte> dst = new((byte*)bmp.GetPixels(), bmp.RowBytes * bmp.Height);
SimdCodecs.TryDecodeInto(file, dst, bmp.RowBytes, ImagePixelFormat.Bgra32);
}
// OCR wants grey: a colour JPEG hands over its Y plane, chroma skipped
byte[] grey = new byte[info.Width * info.Height];
SimdCodecs.TryDecodeInto(file, grey, info.Width, ImagePixelFormat.Gray8);using Sdcb.SimdCodecs.Jpeg;
// SIMD encoder: baseline / optimised Huffman / progressive, 4:2:0 or 4:4:4, EXIF and XMP
byte[] jpeg = JpegEncoder.Encode(rgba, info.Width, info.Height, channels: 4, new JpegEncodeOptions
{
Quality = 90,
Subsampling = JpegSubsampling.Chroma420,
Progressive = true,
});
File.WriteAllBytes("photo.jpg", jpeg);// Composited WebP / GIF frames with durations, a multi-page TIFF’s pages — one API
if (SimdCodecs.TryOpenFrames(file, out IImageFrameReader? reader))
using (reader)
{
for (int i = 0; i < reader.FrameCount; i++)
{
reader.TryGetFrameInfo(i, out ImageFrameInfo frame);
byte[] buffer = new byte[frame.Info.Width * frame.Info.Height * 4];
reader.TryDecodeFrameInto(i, buffer, stride: 0, ImagePixelFormat.Rgba32);
Console.WriteLine($"#{i}: {frame.Info.Width}×{frame.Info.Height}, {frame.Duration?.TotalMilliseconds} ms");
}
}Every package ships runnable LINQPad samples (listed in LINQPad’s NuGet browser), covering SkiaSharp, OpenCvSharp, Ultra HDR, multi-page TIFF and more.
Performance
Measured against the native libraries themselves
Single-threaded, AMD Ryzen 7 5800X, .NET 10. Time relative to the native library — shorter is faster.
JPEG decode to RGBA · 446 files
0 B managed allocation per decode (ImageSharp: ~16 KiB plus its own image). At least as accurate as libjpeg-turbo’s default (~1 dB higher RGB PSNR where chroma is upsampled), and bit-identical on AVX2, SSE, AdvSimd, scalar and NativeAOT.Full report
WebP decode to RGBA · 1920×1080
Output is byte-identical to libwebp. On photo-like opaque lossy files the two are on par (0.87–1.13×).Full report
Lab
The same C#, in your browser
Compiled to WebAssembly; your images never leave your machine. It does only the three things browsers can’t do themselves:
What browsers can’t open
Multi-page and float TIFF, CCITT fax, JPEG 2000, JBIG2 — all over PDFs and scans, and browsers show none of them.
Try itWhat’s inside an image
Bit depth, chroma subsampling, progressive or not, EXIF, ICC, every frame’s duration — browsers hide all of it.
Try itJPEG encoder bench
Quality, 4:2:0 / 4:4:4, baseline / optimised Huffman / progressive — size, time, PSNR and an error map, live.
Try itNuGet
13 packages, one version
Sdcb.SimdCodecs (facade)
├─ Abstractions
├─ Png ──────────┬─ Color.Icc ─ Abstractions
│ └─ Abstractions
├─ Tiff ─────────┬─ Png
│ └─ Abstractions
├─ Jpeg ─────────── Abstractions
├─ Jpeg.GainMap ─┬─ Jpeg
│ └─ Abstractions
├─ Jbig2 ────────── Abstractions
├─ Jpeg2000 ─────── Abstractions
├─ Webp ─────────── Abstractions
└─ Gif ──────────── Abstractions
Exif ─ Tiff Jpeg.IccInjector (no dependencies)All packages ship in lockstep under one version. No package uses another’s internals: code two packages need is shared as source, compiled into each.
Clean room
Written from the specs, natives only as black boxes
- Algorithms only from ITU-T / ISO specs (T.81, T.88, T.800, …), the PNG spec, the prose and tables of RFC 6386 / 9649, papers and CPU manuals
- Never reads libjpeg-turbo, libpng, libwebp, ImageSharp or any GPL code; every file is first-party MIT
- Native libraries serve only as black-box oracles, compared output by output in CI (REQUIRE_ORACLES=1)
- Hardened against hostile input: JBIG2 segment headers, EXIF IFDs, looping TIFF IFD chains
Credits
Derived from SharpAstro/Codecs (Unlicense); the JPEG marker and entropy layer is a port of the public-domain stb_image.
Known gaps are listed codec by codec in docs/known-gaps.md (in Chinese).
MIT licensed, fine for commercial use.

C#/.NET Computer Vision community
Scan to join the WeChat group. The QR code rotates weekly — if it has expired, join QQ group 579060605 instead.