zenjxl-decoder

A JPEG XL decoder in safe Rust. Fork of libjxl/jxl-rs with security hardening, bug fixes, and parallel decoding.
Maintained by Lilith River at Imazen.
[]
= "0.3"
The Rust lib name is jxl, so you use jxl::... in code.
If you find a conformant JXL file that decodes incorrectly (or not at all), open an issue.
What's different from upstream
This fork fixes several decoding bugs, adds resource limits and cooperative cancellation, and wires up parallel decoding via rayon. All changes are BSD-3-Clause licensed under the Google CLA, intended for upstream contribution.
Bug Fixes
| Bug | Description |
|---|---|
| sRGB Transfer Function | Apply sRGB TF by default for XYB-encoded images (was outputting linear) |
| RCT Overflow Panic | Wrapping arithmetic to prevent panic on edge-case pixel values |
| Extra Channel Format Slots | Allocate format slots for all extra channels, not just first |
| Progressive AC Validation | Fix inverted last_pass validation (must be strictly increasing) |
| Extra Channel Bit Depth | Use extra channel's own bit_depth for modular-to-f32 conversion |
| Noise Seeding (upsampling > 1) | Separate RNG seeds per subregion for upsampled frames |
| CMYK Blending Order | Blend in CMYK space, then CMS-convert to RGB |
New Features
| Feature | Description |
|---|---|
| CMS-based CMYK to RGB | ICC profile-based CMYK conversion via optional moxcms backend |
| Cooperative cancellation | enough::Stop trait — cancel or timeout decoding from any thread |
| Resource limits | JxlDecoderLimits API — cap pixels, memory, ICC size, tree size, etc. |
| Memory tracking | max_memory_bytes budget with atomic, lock-free tracking |
| Fallible allocation | All significant allocations return TryReserveError instead of panicking |
| Parallel decoding | Rayon-based parallel group decode and render (opt-in via threads feature) |
Usage
Basic decode
use ;
let data = read?;
let mut decoder = new;
// ... process frames
Resource limits
use ;
// For untrusted input
let options = JxlDecoderOptions ;
| Limit | Default | Restrictive |
|---|---|---|
max_pixels |
2^30 (~1B) | 100M |
max_extra_channels |
256 | 16 |
max_icc_size |
256 MB | 1 MB |
max_tree_size |
4M nodes | 1M nodes |
max_patches |
(derived) | 64K |
max_spline_points |
1M | 64K |
max_reference_frames |
4 | 2 |
max_memory_bytes |
None | 1 GB |
All limits return Error::LimitExceeded { resource, actual, limit } when exceeded.
Cancellation
The decoder accepts any enough::Stop implementation:
use Stopper;
use Arc;
let stop = new;
let stop_clone = clone;
// Cancel from another thread
spawn;
let options = JxlDecoderOptions ;
Cancellation checks run inside parallel closures too, so a cancel request is noticed mid-batch.
Parallel decoding
Enable the threads feature (on by default in jxl_cli):
[]
= { = "0.3", = ["threads"] }
Set parallel: false on JxlDecoderOptions to force single-threaded mode at runtime.
Features
| Feature | Description |
|---|---|
threads |
Rayon-based parallel group decode and render |
cms |
moxcms CMS backend for ICC profile transforms |
jpeg |
JPEG reconstruction from JXL containers |
all-simd |
All SIMD backends (SSE4.2, AVX2, AVX-512, NEON) |
sse42 / avx / avx512 / neon |
Individual SIMD targets |
allow-unsafe |
Enable unsafe fast paths in the main crate (safe fallbacks used otherwise) |
Conformance
Against libjxl/conformance test suite (Level 5): 17/23 passing (74%, as of December 2025).
| Status | Tests |
|---|---|
| Pass | alpha_nonpremultiplied, alpha_triangles, bench_oriented_brg_5, bicycles, blendmodes_5, cafe_5, delta_palette, grayscale_5, grayscale_jpeg_5, grayscale_public_university, lz77_flower, noise_5, opsin_inverse_5, patches_5, patches_lossless, sunset_logo, upsampling_5 |
| Skip | animation_icos4d_5, animation_newtons_cradle, animation_spline_5 (animation rendering not yet supported; animation metadata parsing is available via JxlImageInfo::animation) |
| Fail | bike_5, progressive_5 (out-of-gamut/HDR values), spot (6-channel output not yet supported) |
Against codec-corpus (184 JXL files with djxl reference output): 184/184 passing (100%, as of December 2025).
Upstream Contributions
Bug fixes reported to libjxl/jxl-rs:
| # | Type | Status | Description |
|---|---|---|---|
| #607 | PR | Merged | ICC tag parsing overflow fix |
| #637 | PR | Merged | Extra channel bit_depth fix (from our #604) |
| #632 | PR | Merged | RCT wrapping arithmetic (from our #603) |
| #609 | Issue | Fixed | Progressive AC last_pass validation bug |
| #610 | Issue | Fixed | Noise seeding wrong when upsampling > 1 |
| #660 | PR | Open | Conformance test infrastructure |
| #602 | PR | Closed | Integer overflow handling (ICC part merged as #607; rest declined) |
Sister project
jxl-encoder — a multithreaded JPEG XL encoder in Rust, also by Imazen.
Image tech I maintain
| State of the art codecs* | 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 |
* 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
BSD-3-Clause. See LICENSE.
Upstream Contribution
This is a fork of libjxl/jxl-rs (BSD-3-Clause). We are willing to release our improvements under the original BSD-3-Clause license if upstream takes over maintenance of those improvements. We'd rather contribute back than maintain a parallel codebase. Open an issue or reach out.