feat(jpeg)!: migrate JPEG to gamut-jpeg; delete jpegli stack and img-parts - #52
Merged
Conversation
…parts JPEG decode moves from zune-jpeg to gamut_jpeg::JpegDecoder (baseline + progressive). Four-component streams route through gamut's Cmyk8 surface into a CMYK->RGB conversion verified bit-exact against the old zune backend; grayscale/YCbCr go through Rgb8; 16-bit scaling unchanged. probe_jpeg uses gamut's header-only info(), keeping the hand-rolled SOF scan solely for featureless builds where probing is documented to work. JPEG encode collapses jpeg-encoder AND the entire vendored jpegli stack onto gamut-jpeg: one JpegEncodeConfig with exactly gamut's knobs (quality, subsampling, progressive, restart interval, density); EncodeOptions::JpegJpegEnc/JpegJpegli collapse to Jpeg; codec id jpeg/gamut. Deleted: codecs/jpegli.rs + C++ shim, the build.rs jpegli slice (build.rs retains only cfg-alias machinery), the third_party/jpegli submodule and with it .gitmodules, deps zune-jpeg/jpeg-encoder, all four build-dependencies (bindgen/pkg-config/cc/cmake at the crate level), the jpeg backend features, and EncodeError::Jpeg/Jpegli. APP-segment metadata rides gamut both ways: extraction via gamut_jpeg::metadata() — which now also surfaces ICC and XMP into ImageMetadata where the old path read EXIF only — and embedding via the encoder's with_exif/with_icc_profile/with_xmp. That retired the last three img-parts consumers, so img-parts and the container-embed feature are fully deleted (audit: WebP muxes via its own libwebp RIFF path and never used it; AVIF splices in-crate; PNG/JXL embed via gamut encoders). Quality baseline vs the old backends (q90 re-encode of the three root fixtures): at the apples-to-apples 4:4:4 setting gamut matches the old output within ~1% size at comparable PSNR; gamut's 4:2:0 default trades ~35% smaller files. jpegli perceptual parity remains upstream (gamut#19/#29/#30). Known regressions documented on the config: quality 0 no longer remaps to 90, default subsampling is 4:2:0, and the old decoder's max-dimension DoS-guard knobs have no gamut equivalent yet (upstream ask filed; decode is also currently slower than zune — perf issue filed). BREAKING CHANGE: JPEG config/variant/codec-id renames; ExifContainer::Jpeg removed; container-embed feature and img-parts gone; jpegli vendored toolchain gone.
This was referenced Jul 18, 2026
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.
Closes #31.
Stacked on #51; merge order #42 → … → #51 → this. This was the last v1 hard-blocker migration (gamut#28 landed upstream 2026-07-17).
What is Delivered
Cmyk8surface into a CMYK→RGB conversion verified bit-exact against the old zune backend's formula; grayscale/YCbCr viaRgb8; probe via gamut's header-onlyinfo().JpegEncodeConfig(quality/subsampling/progressive/restart/density — exactly gamut's knobs);EncodeOptions::Jpeg; codec idjpeg/gamut. Deleted:codecs/jpegli.rs+ C++ shim, the build.rs jpegli slice, thethird_party/jpeglisubmodule (and with it.gitmodules),zune-jpeg/jpeg-encoderdeps, all four crate build-dependencies (bindgen/pkg-config/cc/cmake), and the CI submodule-checkout step.gamut_jpeg::metadata()(now surfacing ICC + XMP where the old path read EXIF only), embedding via encoder setters. That retired the last three img-parts consumers: img-parts and thecontainer-embedfeature are fully deleted (audited: WebP never used it; AVIF splices in-crate; PNG/JXL ride gamut encoders).test_jpeg_metadata_roundtrippins EXIF+ICC+XMP encode→extract.Quality baseline (issue requirement; q90 re-encode of the three root fixtures)
At the apples-to-apples 4:4:4 setting gamut matches the old backend within ~1 % output size at comparable PSNR; gamut's 4:2:0 default trades ~35 % smaller files. Full table in the work log. jpegli perceptual parity remains upstream (gamut#19/#29/#30).
Honest regressions, documented on the config
quality: 0no longer remaps to 90; default subsampling is now 4:2:0; the old decoder's max-dimension DoS-guard knobs have no gamut equivalent; gamut decode is currently slower than zune. The latter two are filed upstream (see follow-up issues).Verification Done
Run independently after implementation:
cargo test --workspace384/0;--features full541/0 (fixture suite 22/22 on the gamut path); serde config passes; clippy workspace + full-D warnings0+0;--no-default-features --features jpegstandalone clean; greps confirm zune-jpeg (direct), jpeg-encoder, img-parts, jpegli, and.gitmodulesare gone from tree, manifests, and lockfile. lefthook green.