Image decoders and encoders in Zig with no C: PNG, JPEG, GIF, WebP, TIFF, JPEG XL, and more.
Wuffs showed that image decoders can read hostile files safely without giving up speed. This package reads Wuffs's image formats in Zig, adds three that Wuffs does not read (TIFF, JPEG XL, and ICO), and has an encoder for every one. Nothing allocates. A decoder reads the header, says how much work memory it needs in Config.work_len, and writes pixels into your buffer in any of 24 pixel formats.
Each decoder is tested against its reference library on the same files. Lossless formats give identical pixels. JPEG matches libjpeg-turbo and lossy WebP matches libwebp bit for bit. The JPEG and QOI encoders write the same bytes as libjpeg-turbo and qoi.h.
var decoder: image.Decoder = try .initDetect(&reader, .{});
const config = try decoder.decodeConfig();
const len = image.PixelBuffer.len(.rgba8, config.width, config.height).?;
const pixels = try gpa.alloc(u8, len);
const work = try gpa.alloc(u8, config.work_len);
const dst = try image.PixelBuffer.init(pixels, .rgba8, config.width, config.height);
_ = try decoder.decodeFrame(dst, work) orelse return error.NoFrames;Releases are tagged, from v0.1.0. Before 1.0 a minor release can change the API; CHANGELOG.md says what to change.
| Namespace | Format | Decode | Encode |
|---|---|---|---|
png |
PNG and APNG | every color type and bit depth, Adam7, APNG frames | smallest lossless color type and depth, filter choice, APNG |
jpeg |
JPEG | baseline, 12-bit, progressive, arithmetic, lossless; CMYK and YCCK; 1/2, 1/4, and 1/8 size | baseline and progressive, 4:4:4, 4:2:2, 4:2:0, optimized Huffman tables |
gif |
GIF87a and GIF89a | stills and animations, interlacing, transparency | stills and animations, quantized and dithered past 256 colors |
webp |
WebP | lossless, lossy with alpha, animations | lossless (encode) and lossy (lossy.encode) |
tiff |
TIFF and BigTIFF | every page; strips and tiles; none, PackBits, LZW, Deflate, CCITT G3 and G4, JPEG; 1- to 32-bit integer and 16- to 64-bit float samples | every page; none, PackBits, LZW, or Deflate, with Predictor 2 |
jxl |
JPEG XL | VarDCT and Modular, animations, extra channels, the container; the original JPEG rebuilt from jbrd (JpegReconstructor) |
lossless (Modular) |
bmp |
BMP | 1- to 32-bit, RLE4 and RLE8, bitfields, V4 and V5 headers | 24-bit, 32-bit with alpha, 8-bit indexed |
ico |
ICO and CUR | every entry, BMP and PNG data, hotspots | BMP and PNG entries |
tga |
TGA | every image type, RLE, color maps, the 2.0 footer | uncompressed and RLE |
netpbm |
PBM, PGM, PPM, PAM, PFM | plain and raw, several images as frames | every kind |
wbmp |
WBMP | type 0 | type 0 |
qoi |
QOI | the specification | the specification, byte for byte as qoi.h |
image.detect names any of these formats from its first 18 bytes, image.Decoder decodes any of them behind one interface, image.convert converts between every pair of pixel formats, and image.resize scales from any size and pixel format to any other, with the usual filters from nearest neighbor to Lanczos. Metadata is reported and never applied: ICC, EXIF, and XMP bytes go to your BlobSink, and Config.metadata has the orientation, transfer function, primaries, and density; image.resize.orient turns an image upright by its orientation when you ask.
Not implemented:
- AVIF and HEIC:
image.Formathas no tag for them. - Color management:
Metadata.iccis the profile's bytes, and pixels are never converted with it. - Lossy JPEG XL encoding:
jxl.encodewrites lossless Modular images only. - Animated WebP and JPEG XL encoding:
webp.encode,webp.lossy.encode, andjxl.encodewrite one image. - Hierarchical JPEG and lossless arithmetic-coded JPEG:
error.Unsupported, as in libjpeg-turbo. - Threads: every call runs on the calling thread.
zig fetch --save https://baskingspot.dev/image/v0.1.0.tar.gzAdd the module to your build:
const image = b.dependency("image", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("image", image.module("image"));image builds with Zig 0.16 only.
- The guide says which piece to use for a task, with examples that compile.
- The API reference documents every symbol.
docs/examples/is the code the guide shows, compiled and run byjust test.CHANGELOG.mdrecords every change.bench/README.mddescribes the benchmark harness and its arms.
just cinix develop provides the toolchain, the reference libraries, the comparison
arms, and the site tools. just ci runs what CI runs: the tests and the
guide's examples in Debug and ReleaseSafe and on a big-endian target, each
fuzz target over its seeds, the linters, the differential comparison against
the reference decoders, and the site build. just fuzz, just oracle, and
just bench-compare run the longer versions. just corpus fetches the
conformance suites (see corpus/README.md and
THIRD_PARTY.md). just guide serves the guide with live
reload, and just doc builds the site into zig-out/site/image/.
MIT. See LICENSE. src/ has no third-party code;
THIRD_PARTY.md lists the test images and libraries the
tests and benchmarks use. The logo files in docs/static/ are baskingspot
marks and are not covered by it. See
docs/static/LICENSE.
Built with LLM assistance. How, in detail.