v1.2.0: public reverse JPEG recompression + YCoCg-R lossless RCT#83
Merged
Conversation
Add `public JXLDecoder.decodeLosslessJPEG(_:)` — the symmetric mirror of the existing public `encodeLosslessJPEG(_:)`. It chains the previously package-only reverse pipeline (parseJXLContainer -> extractJBRDBox -> JBRDBoxReader -> BrotliDecoder -> decodeJPEGBridgeData -> JXLToJPEGAdapter.reconstruct) so an external `import JXLSwift` consumer can reconstruct the byte-identical original JPEG. The adapter/bridge types stay `package`; only the one wrapper becomes public. - Refactor `jxl-tool transcode --mode reverse` into a thin client over the new public API (extract transcodeJXLtoJPEGFromSource / writeReverseOutput; keep the optional --source fallback). - Add `async` overloads for the recompression pair (encodeLosslessJPEG / decodeLosslessJPEG) to complete the J2KSwift async-facade parity. - Add PublicAPILosslessJPEGTests using a plain `import JXLSwift` (NOT @testable) so compilation alone pins external reachability, plus a forward -> reverse byte-identical round trip and a fail-safe negative case. Validated end-to-end through the built `jxl` binary: forward bridge -> reverse is byte-identical for 4:4:4 / 4:2:0 / 4:2:2 / grayscale; a JXL with no jbrd box is refused (no wrong bytes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p), cost-gated Apply the reversible YCoCg-R colour transform to RGB/RGBA channels before prediction in SpecModularEncoder, decorrelating R,G,B -> Y,Co,Cg so the chroma channels compress to near-nothing. `RCT.forwardPixel(.ycocgR)` is the exact inverse of the decoder's `SpecRCT.inverse(rctType: 6)`. The transform descriptor is emitted only in the section-0 / DC-global GroupHeader of every single- and multi-group candidate (baseline / activity-split / greedy); per-AC-group headers stay empty, so the decoder inverts on the assembled image (libjxl-canonical — transforms live in the global modular header). - Drop the redundant `[0, sampleHi]` clamp on the gradient predictor in the modular residual paths: a no-op for in-range data (existing byte-exact output is unchanged) but it diverged from the decoder's ClampedGradient (applyLibjxlPredictor id 5) for RCT's signed / over-range Co/Cg, which would have broken the round trip. - Cost-gate RCT (estimateGradientResidualBits): apply YCoCg-R only when its estimated residual bit-cost beats the raw R,G,B estimate, so RCT can never inflate a frame (already-decorrelated / independent-channel content keeps the identity path). - Add the testRCT_LosslessRGB_DecorrelatesIdenticalChannels regression guard (R=G=B => Co=Cg=0 => RGB ~ grayscale, not 3x) to catch a silent RCT-disable. Self-round-trip validated pixel-exact across single- and multi-group, 8/16-bit RGB/RGBA, efforts 3/7/9, correlated + adversarial random, grayscale no-regression. A/B vs a no-RCT build: 21-66% smaller on correlated content, equal on independent content. NOTE: the djxl byte-exact cross-validation (RobustnessTests DjxlSubset / ConformanceTests) must be run on a host with djxl + XCTest; it could not be run in the dev env where this was authored. The change is high-confidence (the decoder's RCT is already djxl-validated against cjxl RGB output) but should be confirmed before merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump JXLToolVersion 1.1.0 -> 1.2.0, Package.swift status, and add the CHANGELOG 1.2.0 entry. Highlights (full notes in CHANGELOG.md): - Public `JXLDecoder.decodeLosslessJPEG(_:)` — reverse JPEG recompression, the mirror of `encodeLosslessJPEG`; `jxl transcode --mode reverse` is now a thin client over it. Plus async overloads for the recompression pair. - Lossless RGB/RGBA YCoCg-R RCT (single + multi-group), cost-gated: 21-66 % smaller on correlated content, never inflates, decode unchanged. Verified via reference libjxl 0.11.2: RCT output djxl-byte-exact across single/multi-group 8/16-bit RGB/RGBA; JPEG recompression byte-identical both directions (cjxl->our reverse, our forward->djxl). The full XCTest suite needs an Xcode host (the dev env has only CommandLineTools); the djxl spec gate was exercised directly through the CLI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v1.2.0 — public reverse JPEG recompression + YCoCg-R lossless RCT
Completes JPEG-XL transfer-syntax exposure and adds a lossless ratio win.
Reverse JPEG recompression (public API)
public JXLDecoder.decodeLosslessJPEG(_:) throws -> Data— the symmetric mirror ofencodeLosslessJPEG; reconstructs the byte-identical original JPEG from a JPEG-bridge JXL. Adapter/bridge types staypackage.jxl transcode --mode reverserefactored into a thin client over it (kept the--sourcefallback).asyncoverloads for the recompression pair (J2KSwift facade parity).PublicAPILosslessJPEGTests(plainimport JXLSwift, not@testable) pins external reachability + a fail-safe negative case.Lossless YCoCg-R RCT — RGB/RGBA, single + multi-group
[0,sampleHi]gradient-predictor clamp in the residual paths (a no-op in-range; required so RCT's signed/over-range chroma round-trips).testRCT_LosslessRGB_DecorrelatesIdenticalChannelsregression guard.Verification (reference libjxl 0.11.2, via CLI)
jxl encode -l→djxldecode → original pixels byte-exact across single/multi-group, 8/16-bit RGB/RGBA, correlated + random.cjxl --lossless_jpeg=1→ ourdecodeLosslessJPEG→ byte-identical, and our forward bridge →djxl→ byte-identical (4:4:4 / 4:2:2 / 4:2:0 / grayscale).swift build -c releaseclean.🤖 Generated with Claude Code