Skip to content

v1.4.0: grayscale/oversized/int16 VarDCT + broader JPEG decode#85

Merged
SureshKViswanathan merged 3 commits into
mainfrom
feature/vardct-coverage-and-jpeg-decode
Jul 6, 2026
Merged

v1.4.0: grayscale/oversized/int16 VarDCT + broader JPEG decode#85
SureshKViswanathan merged 3 commits into
mainfrom
feature/vardct-coverage-and-jpeg-decode

Conversation

@SureshKViswanathan

Copy link
Copy Markdown
Contributor

Closes four coverage gaps. Existing 8-/16-bit RGB/RGBA VarDCT and all lossless output is byte-for-byte unchanged; full XCTest suite green (717 tests, 0 failures). Verified against reference libjxl 0.11.2 (djxl) and libjpeg-turbo 3.1.4 (djpeg).

Grayscale lossy VarDCT

1-channel grayscale and 2-channel grayscale+alpha, 8/16-bit. Encoded the way libjxl does it — a 3-channel XYB frame whose X plane is ≈ 0 (R = G = B after inverse opsin) carrying a grayscale ColorEncoding; the decoder collapses RGB→gray on output. Multi-group + animation covered; djxl decodes the output as grayscale. (Corrects the prompt's premise that grayscale needed a separate non-XYB path.)

Oversized VarDCT

Encoder size cap raised 8192 → 16384 px/side (a resource-safety limit, not correctness — the multi-AC/DC-group machinery indexes with Int). Verified wide (8704×256), tall (256×8448), and square (8256×8256).

Signed 16-bit (PixelType.int16)

The DICOM CT case. Encoder level-shifts .int16 → unsigned offset-binary up front so the VarDCT/Modular core is untouched; JXLDecoder.decode(_:signedOutput:) reconstructs the signed frame. Byte-exact through the lossless Modular path.

Broader JPEG decode

JPEGDecoder.decode now covers progressive (SOF2), extended-sequential (SOF1), 12-bit (→ .uint16), and SOF3 lossless (new JPEGLosslessDecoder: 7 predictors, 2–16-bit, gray/RGB, interleaved/non-interleaved, restart). Byte-exact vs djpeg for lossless; ≤ 6 LSB for the DCT family.

Scope note: 12-bit and SOF3 JPEGs cannot be losslessly recompressed to JXL — the jbrd format is 8-bit-DCT-only and libjxl's own cjxl rejects them. This PR delivers pixel decode (JPEG → ImageFrame → re-encodable as a normal JXL), not recompression.

Also fixed

Multi-frame VarDCT reported wasLossless=true, mislabelling every animation as lossless.

🤖 Generated with Claude Code

Ranjithkumar and others added 3 commits July 6, 2026 15:07
Close three lossy-encode gaps that previously fell back to lossless
Modular, plus a signed-pixel type. Existing RGB/RGBA VarDCT and all
lossless output is byte-for-byte unchanged.

- Grayscale lossy VarDCT (1ch + 2ch gray+alpha, 8/16-bit): encoded as a
  3-channel XYB frame with X ≈ 0 and a grayscale ColorEncoding — the
  representation libjxl uses; the decoder collapses RGB→gray on output.
  Multi-group and animation covered; djxl decodes it as grayscale.
- Oversized VarDCT: raise the encoder size cap 8192 -> 16384 px/side
  (a resource-safety limit, not correctness). Verified wide/tall/square
  beyond the old cap; the group indexing was audited for large numGroups.
- PixelType.int16 (DICOM CT): the encoder level-shifts to unsigned
  offset-binary up front so the VarDCT/Modular core is untouched;
  JXLDecoder.decode(_:signedOutput:) reconstructs the signed frame.
- Fix: multi-frame VarDCT reported wasLossless=true, mislabelling
  every animation as a lossless encode.

Adds regression tests (grayscale, oversized, int16) that would have
caught the previous silent fallbacks. Full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Broaden the pure-Swift JPEG pixel decoder (JPEGDecoder.decode) beyond
baseline 8-bit. Note: 12-bit and lossless JPEGs cannot be losslessly
recompressed to JXL — the jbrd format is 8-bit-DCT-only (libjxl's own
cjxl rejects them) — so this is pixel decode (JPEG -> ImageFrame), not
recompression.

- decode(_:) now delegates to decodeToCoefficients (already SOF1/SOF2
  capable) plus dequant/IDCT/colour, covering progressive (SOF2),
  extended-sequential (SOF1) and 12-bit precision (-> .uint16 frame with
  raw 0..4095 values), with a precision-generalised YCbCr->RGB. Verified
  <= 6 LSB vs djpeg -nosmooth.
- New JPEGLosslessDecoder (SOF3, ITU-T T.81 Annex H) for DICOM Lossless
  JPEG (.70): all 7 predictors, 2..16-bit, grayscale and 3-component,
  interleaved and non-interleaved scans, restart intervals (incl. the
  first-line-after-reset horizontal-prediction rule). decode(_:) routes
  SOF3 automatically. Byte-exact vs djpeg across the full sweep.
- Converts the stale "rejects progressive" pixel-decode test into a
  positive DCT-family test; adds a byte-exact lossless test.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Bump JXLToolVersion 1.3.0 -> 1.4.0, Package.swift status prose, and
finalise the CHANGELOG 1.4.0 entry.

Highlights (full notes in CHANGELOG.md):
- Grayscale / grayscale+alpha lossy VarDCT (8/16-bit), the way libjxl
  stores it; djxl decodes the output as grayscale.
- Oversized VarDCT: size cap raised 8192 -> 16384 px/side.
- Signed 16-bit pixels (PixelType.int16) for DICOM CT; encoder
  level-shifts, decode(_:signedOutput:) reconstructs the signed frame.
- Broader JPEG decode: progressive, extended-sequential, 12-bit, and
  SOF3 lossless (byte-exact vs djpeg).

Verified against reference libjxl 0.11.2 (djxl) and libjpeg-turbo 3.1.4
(djpeg); full XCTest suite green (717 tests, 0 failures).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@SureshKViswanathan SureshKViswanathan merged commit 760697a into main Jul 6, 2026
3 of 4 checks passed
@SureshKViswanathan SureshKViswanathan deleted the feature/vardct-coverage-and-jpeg-decode branch July 6, 2026 09:39
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.

1 participant