koni_jxl
A pure-Dart JPEG XL (.jxl) codec — decode and encode, lossless or
lossy, with zero native dependencies and zero runtime package
dependencies. Works on every platform Dart runs on: Android, iOS, macOS,
Windows, Linux, and (with reduced lossy speed) the web.
Correctness is verified bit-exact against libjxl's djxl on a
generated corpus, the official conformance suite, and real-world
JPEG-transcoded manga chapters.
import 'package:koni_jxl/koni_jxl.dart';
// Cheap header-only inspection (reads a few hundred bytes):
final info = JxlInfo.parse(bytes);
print('${info.width}x${info.height}, ${info.bitsPerSample}-bit');
// Full decode to interleaved RGBA (sRGB, EXIF-oriented):
final image = JxlDecoder.decode(bytes);
final rgba = image.toRgba8();
// All frames of an animation:
final anim = JxlDecoder.decodeAnimation(bytes);
// Lossless encode from raw RGBA:
final jxl = JxlEncoder.encodeLossless(rgba,
width: image.width, height: image.height, hasAlpha: true);
// Lossy (VarDCT) encode from raw RGB, cjxl-like distance knob:
final lossy = JxlEncoder.encodeLossy(rgb,
width: image.width, height: image.height, distance: 1.0);
For Flutter widgets (JxlImageProvider, animation and progressive
playback, background-isolate encode), use
koni_jxl_flutter.
Features
Decoding
- Modular (lossless) still images — bit-exact vs libjxl
- VarDCT (lossy) still images — within ~1 RMSE of libjxl
- Animation (all frames, durations, loop count)
- Splines, progressive DC (LF) frames, multi-pass AC
- Streaming decode with a 1:8 DC preview
(
JxlStreamingDecoder) for blurry-then-sharp display - Grayscale/RGB, palette, alpha, 8/16-bit, EXIF orientation
- Embedded ICC profiles; header-only
JxlInfo.parse
Encoding — lossless (bit-exact)
JxlEncoder.encodeLossless/encodeLossless16from raw pixelsJxlEncoder.encodeImagefor JXL→JXL transcodes- Per-image learned context tree, palette / YCoCg RCT, and the smallest
of four entropy modes ({plain, LZ77} x {prefix, ANS}); every output is
verified bit-exact through this decoder and
djxl
Encoding — lossy (VarDCT, JxlEncoder.encodeLossy)
- RGB 8-bit input, any width/height (padded internally to the 8-pixel block grid VarDCT requires)
- Real HF coefficient context model, adaptive per-block quantization, per-region chroma-from-luma, a learned DC context tree, gradient/ weighted-predictor DC prediction, a per-AC-coefficient rate-distortion search (RDOQ, on by default), adaptive 8x8/16x16 transform-size selection (on by default), multi-group and multi-LF-group support
- All 27 VarDCT transform types now exist and are
djxl-verified correct: every square DCT size up to 256x256 (maxTransformSize), all 12 rectangular types (enableRectangularTransforms), and all 9 bespoke types — DCT4x4, DCT2x2, Hornuss, DCT4x8/DCT8x4, AFV0-3 (enableBespokeTransforms). Only 8x8/16x16 selection is on by default: Gaborish/EPF filters measurably help photographic content but hurt manga's screentone/line-art content; the larger/rectangular/ bespoke transforms were found, against real manga chapter pages, to win only -0.0% to -0.86% there (best combination, at 6.1x baseline encode time) — real but small next to the cost, so every one of these knobs stays off by default (see doc/spec_notes.md in the repository for the full numbers) - Correctness is djxl-verified; compression efficiency is a work in
progress, with real wins already banked — on manga-typical
screentone content at low-to-mid
distance(0.5-2.0), files are already smaller thancjxl -e1(0.81-0.94x, measured); on smooth photographic content the gap is larger (1.5x+), mostly structural (no transform-type or per-block quantization rate-distortion search yet). See doc/BENCHMARKS.md in the repository for the full, reproducible comparison
Robustness — all decode surfaces throw only JxlException on
malformed input (mutation-fuzz verified); JxlLimits caps
header-driven allocations.
Not yet supported (decoding throws JxlUnsupportedException with a
stable feature id): spot-color rendering, JPEG bitstream reconstruction,
and ICC-driven output transforms (files whose color is described only by
an embedded ICC profile decode as sRGB). Modular-mode float (HDR) sample
decoding is supported; encoding one is not.
Performance
Apple M1, AOT, single-threaded: a 1536×2200 manga-style (screentone) page decodes losslessly in ~60–410 ms depending on effort; smooth/ photographic content is slower to decode losslessly (harder for the predictor and context model — up to ~750 ms measured). Typical lossy pages decode in ~0.3–0.5 s, using Float32x4 SIMD across the lossy pipeline (native on AOT targets; emulated on the web).
Full methodology, reproducible tables (decode speed and compression vs.
cjxl, by content type), and exact commands:
doc/BENCHMARKS.md.
Command-line tools
dart run koni_jxl:jxl_info image.jxl # header info
dart run koni_jxl:jxl_dec image.jxl out.ppm # decode to PNM/PAM
dart run koni_jxl:jxl_enc in.ppm out.jxl # lossless encode
License
MIT. Ported from jxlatte (MIT);
see NOTICE in the repository.
Libraries
- koni_jxl
- A pure Dart JPEG XL (JXL) image decoder — no native dependencies.