A decode-only JPEG XL library, in the spirit of djvudec:
- plain C, no dependencies (vs. libjxl's C++ plus highway, brotli and skcms)
- simpler API — see
src/jxl.h - simple to integrate: copy
dist/jxl.handdist/jxl.cinto your project. That issrc/*amalgamated into a single translation unit, like sqlite - single-threaded by design
- verified against libjxl on a generated corpus of hundreds of
.jxlfiles
Sketch (full documentation in src/jxl.h):
jxl_ctx *ctx = jxl_ctx_new(NULL, NULL, on_error, NULL);
if (jxl_signature_check(data, len) == JXLDEC_SIG_CODESTREAM) { ... }
jxl_doc *doc = jxl_doc_open(ctx, data, len); /* data must outlive doc */
jxl_image_info info;
jxl_doc_info(doc, &info); /* size, depth, alpha, ... */
jxl_ctx_set_bgr(ctx, 1); /* emit BGRA for a Windows DIB */
jxl_image *img = jxl_frame_render(doc, 0, JXLDEC_FORMAT_RGBA32);
/* or render straight into your own buffer: */
jxl_frame_render_into(doc, 0, JXLDEC_FORMAT_RGBA32, dst, stride);Requires clang (or MSVC), bun, cmake, ninja and git.
cmd/get-deps.ts clones libjxl (the reference decoder and the oracle) and
jxl-oxide into deps/, then builds cjxl/djxl/jxlinfo. build.ts and
tests.ts call it automatically.
bun cmd/get-deps.ts # clone deps + build the libjxl tools
bun cmd/build.ts # build the decoder + jxl_test (MSVC by default)
bun cmd/build.ts -clang # ... with clang instead
bun cmd/tests.ts -all # generate the corpus, then verify against djxl
bun cmd/tests.ts -rand 20 # ... on 20 random corpus files
bun cmd/build-dist.ts # regenerate the dist/ amalgamation
jxl_test CLI (jbig2dec-flavored):
jxl_test -info in.jxl
jxl_test -frames in.jxl
jxl_test -out out.pam in.jxl
The output is binary PAM, the same thing djxl in.jxl out.pam writes, so the
two compare byte for byte.
See PROGRESS.md for the full feature matrix. In short,
bun cmd/tests.ts -all decodes 1245 corpus files and compares each against
djxl: 1245 match — 387 byte-exact, and no sample in the other 858 differs
by more than one 8-bit step.
- Byte-exact against libjxl: Modular lossless (every effort level, palette, squeeze, RCT), plus patches and reference frames.
- Within one 8-bit step: VarDCT including progressive/LF frames, and lossy Modular (XYB), for sRGB content.
- Also working: gaborish and EPF, splines, synthetic photon noise, 2x/4x/8x upsampling, all eight EXIF orientations, animation, embedded ICC profiles, non-sRGB primaries (P3 and Rec.2100, Bradford-adapted) with the 709 transfer function, and YCbCr/JPEG-transcoded frames in every chroma subsampling mode.
- Implemented but unverified: the PQ and HLG transfer functions.
bun cmd/coverage.tsshows that code never runs — nothing this build ofcjxlcan emit declares them on a lossy frame, so nothing checks it againstdjxl.
The 858 non-byte-exact files are lossy VarDCT paths where roughly a quarter of
the samples land one count either side of libjxl's. libjxl, jxl-oxide and this
decoder use different IDCT factorizations and different powf approximations,
so exact equality is not achievable — libjxl's own conformance testing uses a
tolerance too, and like it we gate on RMS and peak together rather than peak
alone (-rms, default 0.6; -tol, default 3).
Not implemented: encoding, JPEG reconstruction (jbrd boxes are located but
not applied), and multithreading. Two extra-channel cases are rejected with an
error rather than decoded wrongly: a channel whose upsampling factor differs
from the colour channels', and a spot-colour channel (which has to be mixed
into the colour channels). Neither is producible by cjxl, so neither can be
verified against djxl yet.
bun cmd/bench.ts -all links the dist/ amalgamation and libjxl's static
libraries into one process and times both single-threaded, best of N. Over the
1245-file corpus this decoder takes 1.91x libjxl's decode time. libjxl is
AVX2 throughout; this is scalar C apart from the SSE2 hot loops (noise,
upsampling, EPF, gaborish, both DCT passes and the colour conversion), so a
constant factor is
expected. Each keeps a scalar twin it is checked bit-identical against.
PROGRESS.md records what closed the gap and, just as usefully, which
optimizations were measured and thrown away.
An AI-assisted port of jxl-oxide (Rust),
cross-checked against libjxl for exact
tables and constants, and verified against libjxl's own djxl.