zenbitmaps

PNM/PAM/PFM, BMP, and farbfeld image format decoder and encoder.
no_std compatible (with alloc), forbid(unsafe_code), panic-free. All arithmetic is checked. Suitable for server and embedded use.
Getting started
[]
= "0.1.0" # PNM + farbfeld
= { = "0.1.0", = ["bmp"] } # + full BMP support
= { = "0.1.0", = ["rgb"] } # + typed pixel API (RGB8, RGBA8, etc.)
= { = "0.1.0", = ["imgref"] } # + ImgVec/ImgRef 2D buffers (implies rgb)
= { = "0.1.0", = ["all"] } # everything
Quick example
use *;
use Unstoppable;
// Encode pixels to PPM
let pixels = vec!; // 2 RGB pixels
let encoded = encode_ppm?;
// Decode (auto-detects PNM/BMP/farbfeld from magic bytes)
let decoded = decode?;
assert!; // zero-copy for PPM with maxval=255
assert_eq!;
# Ok::
Format detection
detect_format() identifies the format from magic bytes without decoding:
use *;
match detect_format
decode() uses this internally. You only need detect_format() if you want to inspect the format before committing to a full decode, or if you need to route data to format-specific functions.
Supported formats
PNM family (always available):
- P5 (PGM binary) — grayscale, 8-bit and 16-bit
- P6 (PPM binary) — RGB, 8-bit and 16-bit
- P7 (PAM) — arbitrary channels (grayscale, RGB, RGBA), 8-bit and 16-bit
- PFM — floating-point grayscale and RGB (32-bit per channel)
- Auto-detected by
decode()viaP5/P6/P7/Pf/PFmagic
Farbfeld (always available):
- RGBA 16-bit per channel
- Auto-detected by
decode()via"farbfeld"magic
BMP (bmp feature, opt-in):
- All standard bit depths: 1, 2, 4, 8, 16, 24, 32
- Compression: uncompressed, RLE4, RLE8, BITFIELDS
- Palette expansion, bottom-up/top-down, grayscale detection
BmpPermissivenesslevels: Strict, Standard (default), Permissive- Native byte order decoding via
decode_bmp_native()(skips BGR→RGB swizzle) - Auto-detected by
decode()via"BM"magic
Zero-copy decoding
PNM files with maxval=255 (the common case) decode to a borrowed slice into your input buffer. No allocation, no copy. Formats requiring transformation (BMP row flip, farbfeld endian swap, 16-bit, non-255 maxval, PFM) allocate.
With the rgb feature, as_pixels() gives you a zero-copy typed view:
let decoded = decode?;
let pixels: & = decoded.as_pixels?; // zero-copy reinterpret
With the imgref feature, as_imgref() gives you a zero-copy 2D view:
let decoded = decode?;
let img: ImgRef = decoded.as_imgref?; // zero-copy 2D view
to_imgvec() is also available when you need an owned copy.
BGRA pipeline
BMP files store pixels in BGR/BGRA order. Use decode_bmp_native() to skip the BGR→RGB swizzle and work directly in native byte order:
let decoded = decode_bmp_native?;
// decoded.layout is Bgr8, Bgra8, or Gray8
// All encoders accept BGR/BGRA input — swizzle happens automatically
let pam = encode_pam?;
PGM, PPM, PAM, farbfeld, and BMP encoders all accept Bgr8, Bgra8, and Bgrx8 input.
Typed pixel API (rgb feature)
With the rgb feature, you get type-safe pixel encode/decode using the rgb crate's types:
use *;
use Unstoppable;
let pixels = vec!;
let encoded = encode_ppm_pixels?;
let = ?;
# Ok::
Available types: RGB8, RGBA8, BGR8, BGRA8 (type aliases for rgb crate types).
ImgRef/ImgVec API (imgref feature)
With the imgref feature (implies rgb), you can work with 2D image buffers that handle stride/padding:
let img = new;
let encoded = encode_ppm_img?;
let decoded_img = ?;
decode_into() decodes directly into a pre-allocated ImgRefMut buffer, handling arbitrary stride.
Cooperative cancellation
Every function takes a stop parameter implementing enough::Stop. Pass Unstoppable when you don't need cancellation. For server use, pass a token that checks a shutdown flag — decode/encode will bail out promptly via BitmapError::Cancelled.
Resource limits
use *;
use Unstoppable;
let limits = Limits ;
# let data = encode_ppm.unwrap;
let decoded = decode_with_limits?;
# Ok::
Features
| Feature | What it adds |
|---|---|
| (default) | PNM (P5/P6/P7/PFM) + farbfeld decode/encode |
bmp |
Full BMP decode/encode (all bit depths, RLE, bitfields) |
rgb |
Typed pixel API (RGB8, RGBA8, as_pixels(), encode_*_pixels()) |
imgref |
2D buffer API (ImgVec/ImgRef, as_imgref(), decode_into()) — implies rgb |
std |
Enable std support (not required — no_std + alloc by default) |
zencodec |
zencodec trait integration (implies rgb + imgref) |
zennode |
zennode node definitions (EncodeBmp schema with RIAPI keys) — temporarily disabled, not yet published |
all |
bmp + rgb + imgref |
API
All public functions are flat, one-shot calls at crate root.
Decode (auto-detect):
detect_format(data)— identify format from magic bytesdecode(data, stop)— auto-detect and decodedecode_with_limits(data, limits, stop)
Decode (format-specific):
decode_farbfeld(data, stop)/decode_farbfeld_with_limits(...)decode_bmp(data, stop)/decode_bmp_with_limits(...)— RGB output (bmp)decode_bmp_native(data, stop)/decode_bmp_native_with_limits(...)— BGR output (bmp)decode_bmp_permissive(data, permissiveness, stop)/..._with_limits(...)(bmp)
Encode (raw bytes):
encode_ppm,encode_pgm,encode_pam,encode_pfm— PNM familyencode_farbfeld— farbfeldencode_bmp,encode_bmp_rgba— BMP (bmp)
Typed pixel (rgb): decode_pixels, encode_ppm_pixels, encode_pam_pixels, etc.
ImgRef/ImgVec (imgref): decode_img, decode_into, encode_ppm_img, etc.
Types:
DecodeOutput<'a>— decoded image (.pixels(),.width,.height,.layout,.is_borrowed(),.as_pixels(),.as_imgref(),.to_imgvec())ImageFormat— format enum (Pnm, Bmp, Farbfeld)PixelLayout— pixel format (Gray8, Gray16, Rgb8, Rgba8, Rgba16, Bgr8, Bgra8, Bgrx8, GrayF32, RgbF32)BmpPermissiveness— decode strictness (Strict, Standard, Permissive) (bmp)Limits— resource limits (max width/height/pixels/memory)BitmapError— error type,#[non_exhaustive]
Credits
- PNM: draws from zune-ppm by Caleb Etemesi (MIT/Apache-2.0/Zlib)
- BMP: forked from zune-bmp 0.5.2 by Caleb Etemesi (MIT/Apache-2.0/Zlib)
- Farbfeld: forked from zune-farbfeld 0.5.2 by Caleb Etemesi (MIT/Apache-2.0/Zlib)
AI-Generated Code Notice
Developed with Claude (Anthropic). Not all code manually reviewed. Review critical paths before production use.
Image tech I maintain
| State of the art codecs[1] | zenjpeg · zenpng · zenwebp · zengif · zenavif (rav1d-safe · zenrav1e · zenavif-parse · zenavif-serialize) · zenjxl (jxl-encoder · zenjxl-decoder) · zentiff · zenbitmaps · heic · zenraw · zenpdf · ultrahdr · mozjpeg-rs · webpx |
| Compression | zenflate · zenzop |
| Processing | zenresize · zenfilters · zenquant · zenblend |
| Metrics | zensim · fast-ssim2 · butteraugli · resamplescope-rs · codec-eval · codec-corpus |
| Pixel types & color | zenpixels · zenpixels-convert · linear-srgb · garb |
| Pipeline | zenpipe · zencodec · zencodecs · zenlayout · zennode |
| ImageResizer | ImageResizer (C#) — 24M+ NuGet downloads across all packages |
| Imageflow | Image optimization engine (Rust) — .NET · node · go — 9M+ NuGet downloads across all packages |
| Imageflow Server | The fast, safe image server (Rust+C#) — 552K+ NuGet downloads, deployed by Fortune 500s and major brands |
[1] as of 2026
General Rust awesomeness
archmage · magetypes · enough · whereat · zenbench · cargo-copter
And other projects · GitHub @imazen · GitHub @lilith · lib.rs/~lilith · NuGet (over 30 million downloads / 87 packages)
License
MIT OR Apache-2.0