Skip to content

Releases: Raster-Lab/JXLSwift

v1.3.0 — 16-bit lossy VarDCT for RGB/RGBA

Choose a tag to compare

@SureshKViswanathan SureshKViswanathan released this 01 Jul 11:54
c3862e6

16-bit lossy VarDCT. The lossy VarDCT codec now accepts 16-bit RGB/RGBA input (previously 8-bit only), matching the bit-depth range the lossless Modular codec has supported since v1.0. A lossy encode request on a 16-bit frame now routes to VarDCT instead of silently falling back to lossless Modular. Encoded bytes for existing 8-bit lossy VarDCT and all lossless paths are unchanged — byte-for-byte regression-checked against the pre-change build.

Added

  • 16-bit lossy VarDCT encode + decode, RGB and RGBA. VarDCTEncoder.forward accepts .uint16 frames; the sRGB↔linear conversion generalises to any bit depth via a maxValue parameter, with the 8-bit code path left untouched (no per-pixel branch added to the default hot path). VarDCTBitstreamWriter threads the real bit depth into ImageMetadata/ExtraChannelInfo instead of hardcoding 8; JXLDecoder's VarDCT reconstruction reads the declared bit depth and emits a uint16 ImageFrame when appropriate. Grayscale VarDCT remains unsupported at any bit depth (falls back to lossless Modular, unchanged from before).

Fixed

  • 16-bit RGBA alpha-channel corruption in VarDCT encodebuildFrameSections read the alpha channel with a byte stride only correct for 8-bit samples; a 16-bit RGBA frame would read the wrong bytes entirely. Alpha now round-trips bit-exact (verified against djxl).
  • VarDCTBitstreamWriter.encodeAnimation now rejects frames that disagree on bit depth, extending the pre-existing xsize/ysize/hasAlpha agreement guard, instead of silently misdeclaring the shared ImageMetadata.

Verification

  • djxl 0.11.2 oracle: our 16-bit RGB/RGBA/multi-group VarDCT bitstreams decode via djxl within 3–16 (out of 65535) max-diff of our own decoder on the same bytes, with the alpha channel bit-exact; djxl accepts every generated bitstream (exit 0).
  • Byte-for-byte regression check against a pre-change build (isolated git worktree): 8-bit lossy VarDCT, 8/16-bit lossless Modular, and multi-frame animation encode all produce identical output — zero drift on the existing default path.
  • swift build -c release clean. The full XCTest suite needs an Xcode host (this dev env has only CommandLineTools); the djxl byte-exact spec gate was exercised directly via CLI round-trips, and the new tests' logic was additionally verified by running the identical API calls through a throwaway executable target linked against the built library (then removed).

🤖 Generated with Claude Code

v1.2.0 — reverse JPEG recompression API + YCoCg-R lossless RCT

Choose a tag to compare

@SureshKViswanathan SureshKViswanathan released this 30 Jun 15:06
4b5af09

JPEG-XL transfer-syntax completion + lossless ratio. The reverse half of JPEG recompression is now public API, the lossless Modular path gains the YCoCg-R reversible colour transform (a large ratio win on RGB/RGBA), and the async facade reaches parity for the recompression pair. Encoded bytes change for RGB/RGBA lossless (RCT — see Changed); grayscale, the JPEG bridge, and every decode path are unchanged.

Added

  • Public reverse JPEG recompressionJXLDecoder.decodeLosslessJPEG(_:) throws -> Data, the symmetric mirror of encodeLosslessJPEG(_:). Reconstructs the byte-identical original JPEG from a JPEG-bridge JXL (baseline + common progressive; 4:4:4 / 4:2:2 / 4:2:0 / grayscale; APP / ICC metadata). The reverse pipeline's adapter/bridge types stay package; only the one-call wrapper is public. jxl transcode --mode reverse is now a thin client over it.
  • async overloads for encodeLosslessJPEG / decodeLosslessJPEG, completing the J2KSwift async-facade parity for the recompression syntax.

Changed

  • Lossless RGB/RGBA now uses the YCoCg-R reversible colour transform (§C.7.7), single- and multi-group. R, G, B are decorrelated to Y, Co, Cg before prediction and inverted by the decoder from the global-header transform list. Encoded bytes change for RGB/RGBA lossless (smaller); grayscale and all decode output are unchanged. Measured 21–66 % smaller on correlated RGB/RGBA versus the previous independent-channel coding.
  • RCT is cost-gated — applied only when its estimated residual cost beats raw R, G, B, so it can never inflate a frame (already-decorrelated / independent-channel content keeps the identity path). Round-trips stay pixel-exact either way.

Fixed

  • The modular gradient predictor's redundant [0, sampleHi] clamp is dropped in the residual paths — a no-op for in-range channels (existing byte-exact output preserved) but it diverged from the decoder's ClampedGradient for the RCT-transformed (signed / over-range) chroma channels.

Verification

  • RCT lossless output decodes byte-exact through reference djxl 0.11.2 across single/multi-group, 8/16-bit RGB/RGBA, correlated + random content.
  • JPEG recompression: cjxl --lossless_jpeg=1decodeLosslessJPEG → byte-identical original, and forward bridge → djxl → byte-identical, across 4:4:4 / 4:2:2 / 4:2:0 / grayscale.
  • swift build -c release clean. (Full XCTest suite needs an Xcode host; the djxl byte-exact spec gate was exercised via the CLI.)

🤖 Generated with Claude Code

v1.1.0 — Performance-programme release (7.1× default-effort encode)

Choose a tag to compare

@SureshKViswanathan SureshKViswanathan released this 12 Jun 14:07

Performance-programme release. Public API unchanged; encoded bytes change at effort ≥ 7 (retuned ladder), everything else bit-identical to v1.0.1.

Highlights

  • Default-effort lossless encode 7.1× faster (538 → 76 ms, 512² 16-bit reference); effort 1 is 2.1× faster.
  • Effort 8/9 now beat the old best ratio while encoding faster — greedy MA-tree moved to effort ≥ 8 with 16/32-leaf budgets (−0.4 % on 16-bit grayscale medical content, −8.7 % on 16-bit RGB vs the old effort 7/9), all djxl byte-exact.
  • jxl batch is parallel: a 64-slice CT stack encodes in ~1.7 s end-to-end (~20× vs v1.0.1 sequential).
  • Multi-group decode 4.5× faster (per-group parallel decode); single-image decode −10–20 %.
  • JPEG transcode: reverse transcode −36 %, JPEG decode −18 % (64-bit bit I/O + 8-bit Huffman fast path), byte-identical reconstruction.
  • Encoder cost gating is now analytic (exact sizes, winner-only emission); the MA-tree learner is radix-sorted; large-plate (> 4 M px) frames now get the learned tree at effort ≥ 8.

Validation

  • 694/694 tests (round-trip, conformance vectors, djxl byte-exact).
  • CID22: 49/49 lossless byte-exact through our decoder and djxl at the default effort.
  • Medical DICOM (823 stratified from 4 441 CT/MR/XA/CR/DX/PX files, 16-bit): 814 pass / 0 fail at effort 7 and effort 9, every pass byte-exact through both decoders.

Full methodology and the phase-by-phase record: Documentation/OPTIMISATION-PLAN-2026-06.md + CHANGELOG.md.

🤖 Generated with Claude Code

v1.0.0 — Production-ready lossless JPEG XL codec (medical-grade)

Choose a tag to compare

@SureshKViswanathan SureshKViswanathan released this 30 May 01:54

JXLSwift v1.0.0 — production-ready, medical-grade lossless JPEG XL codec

A pure-Swift implementation of JPEG XL (ISO/IEC 18181) — Swift-first (Swift 6.2, strict concurrency), zero runtime dependencies (swift-argument-parser is CLI-only). v1.0 commits to the public API surface as stable.

What "1.0" means

Lossless production-grade. 8/16-bit grayscale / gray+alpha / RGB / RGBA; arbitrary dimensions ≤ 16384 (multi-group + multi-DC-group); cost-gated predictors + multi-property MA-trees + learned activity-split thresholds + effort knob 1..9. Every emitted byte traces to a spec section.

Medical-grade validated. 2,867 / 0 byte-exact on a 30,329-file radiology DICOM corpus (CT, MR, CR, DX, MG, PX, US, XA — including JPEG / JPEG-LS encapsulated, multi-frame XA cine, colour-YBR ultrasound, MONOCHROME1, and radiographs up to 4784×3521). Every PASS reconstructed byte-identical by the libjxl reference decoder djxl. Reproducible via scripts/medical-dicom-validate.sh.

Non-DICOM validated. 49 / 0 byte-exact on the Cloudinary Image Dataset '22 (natural photographic). scripts/cid22-validate.sh.

Conformance gate. Official jxl-conformance vectors wired into the test harness; lossless subset green on the committed fixtures (lz77_flower, alpha_triangles) by default, plus the full corpus via JXL_CONFORMANCE_DIR. djxl is the pixel oracle (the project's correctness invariant).

Robustness hardening. A parameterised lossless sweep (400 cases × dims × depths × channels × content × the full effort ladder 1…9) plus a decoder fuzz pass over truncated and byte-mutated codestreams — 0 traps across 1,812 malformed inputs.

JPEG ⇄ JXL transcode (lossless): forward (≤ 2048 px / side) + reverse (byte-identical reconstruction of the source JPEG, including baseline / progressive / ICC).

VarDCT lossy decoder ships as a preview (djxl-matching, Phase R filters included).

Public API stable + Sendable. No force-unwraps in the public surface. Family-aligned with J2KSwift presets (.lossless, .highQuality, .balanced, .fast, .maxCompression) and CLI flag conventions (-i / -o).

Performance. Honest baseline + risk-assessed roadmap in Documentation/PERFORMANCE-ANALYSIS.md. The JXLPerfC SwiftPM C target is scaffolded as the prescribed boundary for the upcoming C BitWriter / rANS hot path (CLAUDE.md constraint 1, amended 2026-05 to permit C/C++ for measured hot paths).

Documented deliberate 1.0 limitations (post-1.0 roadmap)

  • Forward JPEG → JXL transcode capped at 2048 px / side. The lossless Modular path handles all sizes ≤ 16384, and reverse transcode is unaffected.
  • Reconciling fillModularProperties slots 6/7/8/11/13/14 with libjxl to widen the greedy MA-tree property set (a ratio lever, not a correctness gap).
  • Full lossy VarDCT encode (pixels → lossy JXL) — headline of v2.0.
  • libjxl-class encode speed — see the C/C++ entropy hot-path roadmap in PERFORMANCE-ANALYSIS.md.

Test gate

697 tests / 0 failures (swift test -c release, ~80 s) plus the medical DICOM (2 867) + Cloudinary CID22 (49) corpora — all djxl-byte-exact.

Family

JXLSwift is part of a Swift compression-library family alongside J2KSwift (JPEG 2000). Public API + CLI surface mirror, so callers can switch codecs without re-learning the syntax (see Documentation/FAMILY-API-PARITY.md).

🤖 Generated with Claude Code

v0.14.0 — Measured perf + family-parity preset alignment

Choose a tag to compare

@SureshKViswanathan SureshKViswanathan released this 30 May 00:54

v0.14.0 — Measured perf + family-parity preset alignment

A maintenance release on top of v0.13.0 (medical-grade lossless DICOM validation). All output is byte-identical to v0.13.0 on lossless paths; the perf wins below cost zero correctness.

Measured perf (byte-identical, full 695-test djxl-exact suite green)

  • BitWriter UInt64-accumulator — the most fundamental output primitive rewritten to a 64-bit accumulator that flushes whole bytes (was per-bit-chunk append + bounds-checked subscript). Emits the identical bit stream; bitCount and partial preserved exactly, so cost-gating is unaffected. ~5 % faster on the effort-1 fast path.
  • Shared WP per-pixel pass — the activity-split (effort ≥ 4) and greedy (effort ≥ 7) lossless candidates each re-ran the same full-image weighted-predictor pass; now computed once in buildSingleSection and handed to both via optional precomputed: params (other callers untouched). ~2.8 % faster at effort 7 (controlled A/B, 512² 16-bit: 763 → 741 ms).
  • Both wins are modest — and that's the documented headline finding of Documentation/PERFORMANCE-ANALYSIS.md: the per-candidate rANS entropy encoding is the irreducible floor under byte-identity. A larger speed-up requires either fewer candidates (would change the chosen output / ratio) or moving the entropy hot path to C/C++ with the scalar Swift staying as the reference — the prescribed next step (CLAUDE.md constraint 1, amended 2026-05).

Family parity with J2KSwift

  • Preset-quality alignment. JXLConfiguration.balanced 0.9 → 0.85; .fast 0.75 → 0.70; added .maxCompression = 0.50 mirroring J2KConfiguration.maxCompression. High-quality (0.95) and lossless presets were already aligned. Closes the last accidental preset-quality divergence flagged in FAMILY-API-PARITY.md.

Validation

  • Cloudinary CID22 (non-DICOM natural photos)49 / 49 byte-exact through our decoder and djxl, aggregate lossless ratio 40.6 %. Reproducible via scripts/cid22-validate.sh.
  • Medical DICOM re-validation — 978 / 0 byte-exact (sample re-run confirming the perf changes preserve v0.13.0's 2 867 / 0 baseline).

Public-API audit

All public types are Sendable; no force-unwraps in the public surface; doc comments in place. The API is stable in shape — pending an explicit v1.0 freeze.

🤖 Generated with Claude Code

v0.13.0 — Road to lossless v1.0 + medical-grade DICOM validation

Choose a tag to compare

@SureshKViswanathan SureshKViswanathan released this 29 May 17:05

v0.13.0 — Road to lossless v1.0 + medical-grade DICOM validation

A pure-Swift JPEG XL (ISO/IEC 18181) codec, lossless-first. This release builds the foundation for a lossless v1.0: a real conformance gate, robustness hardening, the convert CLI verb, a performance baseline — and medical-grade validation against a real radiology DICOM corpus. One genuine decode-correctness bug was found and fixed along the way.

Medical-grade validation (real DICOM corpus)

The lossless path was validated end-to-end against a 30,329-file radiology DICOM corpus (CT, MR, CR, DX, MG, PX, US, XA). JXLSwift stays DICOM-unaware by design — an external extractor (pydicom) pulls the raw pixel buffers and feeds them to the codec, and reconstruction is checked byte-exact through both the pure-Swift decoder and the libjxl reference decoder djxl. A stratified sample covering every distinct pixel configuration (modality × transfer-syntax × 8/16-bit × grayscale/colour × single/multi-frame, including JPEG/JPEG-LS-sourced and MONOCHROME1):

  • 2,867 images pass, 0 fail — every pass byte-exact via djxl. (62 non-image structured-report files have no pixel data and are skipped.)
  • Coverage: MR, CT, multi-frame XA cine, colour YBR-JPEG ultrasound, CR/PX incl. JPEG-LS & JPEG-lossless sources, MONOCHROME1, and radiographs up to 4784×3521.
  • A default-effort (effort 7, greedy MA-tree) subset over the common configs also passes 31/31, djxl-byte-exact.

Reproducible and PHI-safe via scripts/medical-dicom-validate.sh.

What's new

  • Lossless conformance gate — the official jxl-conformance vectors are wired into the test suite (env-gated for the full corpus + a committed lossless subset that runs green by default), with djxl as the pixel oracle. scripts/fetch-conformance.sh populates the full corpus.
  • Decode-correctness fix — decoded samples are now clamped to the declared bit depth [0, 2^bps−1]; a 9-bit RGBA image previously reconstructed out-of-gamut values where the reference decoder clamps. No-op for valid in-range streams and for 8/16-bit.
  • Robustness hardening — a parameterised lossless sweep (400 cases across dimensions × bit-depth × channels × content) + the full effort ladder, and a decoder fuzz pass: 0 traps across 1,812 malformed inputs (every one is cleanly rejected or decoded).
  • convert subcommand — PNM ↔ JXL and JPEG → PNM/JXL, format inferred by extension (family parity with J2KSwift's convert).
  • Performance baselinejxl-tool benchmark --mode lossless --effort N + scripts/benchmark-lossless.sh make the speed↔ratio ladder measurable; hot-path analysis recorded for the upcoming SIMD work.
  • Lossless completeness — confirmed the >8-cluster context-map and rANS full (complex) histogram paths are implemented and djxl-byte-exact (the prior deferral notes were stale); added pinning tests.

Known limitations (deliberate, documented for post-1.0)

  • Forward JPEG→JXL transcode is limited to ≤ 2048 px per side (multi-DC-group transcode is planned). The native lossless Modular path handles all sizes ≤ 16384, and reverse transcode is unaffected.
  • The greedy MA-tree property set is restricted to the djxl-verified subset (a ratio lever, not a correctness gap).

Notes

  • Lossless-first. Lossy encode (pixels → lossy VarDCT) remains deferred; the VarDCT lossy decoder ships as a preview.
  • Zero runtime dependencies beyond swift-argument-parser (CLI only).

🤖 Generated with Claude Code

v0.12.0 — lossless JPEG XL codec

Choose a tag to compare

@SureshKViswanathan SureshKViswanathan released this 29 May 13:42

A comprehensive, djxl-validated lossless JPEG XL implementation (ISO/IEC 18181) — pure Swift 6.2, strict concurrency, zero runtime dependencies beyond swift-argument-parser (CLI only).

Lossless encode (native Modular)

  • 8- and 16-bit grayscale, grayscale+alpha, RGB, RGBA
  • Arbitrary dimensions ≤ 16384 (single-group, multi-group, and multi-DC-group)
  • Multi-property MA-trees + learned activity thresholds
  • Encode-effort knob (EncodingEffort 1–9) to trade compression for speed
  • Every path byte-exact through djxl

Transcoding (lossless JPEG ⇄ JXL)

  • Forward JPEG → JXL (coefficient-faithful, ≤ 2048 px/side), djxl-valid
  • Reverse JXL → JPEG byte-identical: baseline + progressive, all chroma, grayscale, ICC profiles

Decode

  • Lossless Modular + VarDCT lossy decode (Phase R restoration: Gaborish + EPF), djxl-matching

Known limitations

  • Lossy encode (pixels → lossy JXL) is not implemented — deferred to a later phase. This is a lossless-first release.
  • Forward JPEG transcode is capped at 2048 px/side.
  • Decoder gaps: DCT128/256 transforms; Splines / Noise / Patches synthesis.
  • Dimensions > 16384 and bit depths > 16 are not supported.

688 tests, 0 failures. See CHANGELOG.md for per-milestone detail.

🤖 Generated with Claude Code