Skip to content

Add jxl-rs decoder - #199

Open
iamcarbon wants to merge 4 commits into
saucecontrol:masterfrom
carbon:feature/jxl-rs-decoder
Open

Add jxl-rs decoder#199
iamcarbon wants to merge 4 commits into
saucecontrol:masterfrom
carbon:feature/jxl-rs-decoder

Conversation

@iamcarbon

@iamcarbon iamcarbon commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds an experimental PhotoSauce.NativeCodecs.JxlRs decoder plugin backed by the pure-Rust jxl-rs decoder.

The change is additive and does not remove the existing libjxl implementation or encoder. Applications can opt in with:

CodecManager.Configure(codecs => {
    codecs.UseJxlRs();
});

Implementation

  • Supports JPEG XL codestreams and container files.
  • Supports still images, animation, orientation, ICC profiles, alpha, and uncompressed Exif boxes.
  • Outputs BGR/BGRA directly, avoiding an RGB-to-BGR swizzle.
  • Uses jxl-rs SIMD support through its default features.
  • Builds a small Rust cdylib through Cargo and copies it alongside the managed assembly.
  • Targets net8.0 and net472.

Input and decoding

  • Contiguous MemoryStream and UnmanagedMemoryStream inputs are consumed in place.
  • Other seekable streams use bounded callbacks and jxl-rs's NeedsMoreInput state machine.
  • Still-image inspection stops after image/ICC metadata and retains the primed decoder for the first pixel request.
  • A first full-frame request decodes directly into MagicScaler's caller-provided destination pointer.
  • Partial or repeated requests use one pooled frame buffer.
  • Animation frames are decoded on demand using jxl-rs visible-frame seek targets rather than storing all frames.

EXIF

jxl-rs does not currently expose arbitrary container boxes, so this adds a small allocation-free JPEG XL box parser.

The parser:

  • Reads only 8- or 16-byte box headers using stack memory.
  • Seeks over unrelated payloads.
  • Stores only the EXIF stream offset and length.
  • Copies EXIF directly into the caller-provided IExifSource destination when requested.
  • Preserves the source stream position.
  • Validates extended sizes, offsets, truncation, and integer overflow.

Brotli-compressed brob metadata boxes are not supports on .NETFramework (is it time to consider dropping .NETFramework ? :)) .

4K decode benchmark

Windows x64, .NET 10.0.10, 64 logical processors. The input was a deterministic 3840×2160 mixed-detail BGR image encoded by libjxl at distance 1.0, effort 5 (Hare), producing a 6.90 MiB payload.

The benchmark performs a complete MagicScaler open-and-full-frame-copy operation, with three warmups and twelve measured iterations in alternating order.

Decoder Median Mean Min Max Throughput
libjxl 0.11.1 407.49 ms 406.79 ms 397.42 ms 419.38 ms 20.35 MP/s
jxl-rs 0.5.1 461.54 ms 461.69 ms 449.36 ms 476.26 ms 17.97 MP/s

On this workload, jxl-rs had 1.13× libjxl's median latency (+13.3%) and 0.88× its throughput.

The decoded outputs differed only by rounding:

  • Mean absolute channel error: 0.244508
  • Maximum channel delta: 1

These are single-machine directional results rather than a comprehensive cross-platform benchmark.

Validation

  • cargo test --locked: 3 passed, 0 failed.
  • Managed decoder project compiled for net8.0 and net472.
  • EXIF parser checks cover:
    • Standard boxes
    • Extended-length boxes
    • Boxes extending to EOF
    • Non-zero stream origins and TIFF offsets
    • Truncated and malformed input
    • Unsupported brob boxes
    • Stream-position preservation
  • 10,000 EXIF scan-and-copy iterations reported zero managed allocations.

Current limitations

  • Decoder only; encoding remains with the existing libjxl plugin.
  • Input streams must be seekable.
  • brob-compressed EXIF is not exposed.
  • The current source build produces the Rust native library for the host platform and architecture; RID-specific packaging would require additional build infrastructure.

Built with the assistance of GPT-5.6 Sol.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants