You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
New test encodes full Kodak corpus with Rust and C mozjpeg across all
meaningful encoder configurations: Baseline, Baseline+Trellis, Full Baseline,
Progressive, Progressive+Trellis, Full Progressive, and Max Compression.
Quality levels: Q55, Q65, Q75, Q85, Q90, Q95.
Uses raw mozjpeg-sys FFI for the C side (the mozjpeg crate is missing
trellis/deringing setters). Assertions: <1% avg delta, <3% per-image.
Results: all non-optimize_scans configs within ±0.7%. Trellis at Q55-Q75
produces smaller files than C. Max Compression (optimize_scans) shows
+0.28% to +0.75% due to scan search heuristic differences.
README and CLAUDE.md updated with the full parity table. Added scans-lq.md
as investigation handoff for the optimize_scans divergence at Q<55.
**Configs:** Baseline = huffman opt only. +Trellis = AC trellis. Full = AC trellis + DC trellis + deringing. Max Compression = Full + `optimize_scans: true`. All others use `optimize_scans: false`. All use `force_baseline: true`.
64
91
65
92
**Key findings:**
66
-
- Rust **matches or beats** C at all quality levels when using the same scan script
67
-
- With trellis, Rust consistently finds slightly better R-D tradeoffs at Q75
68
-
- The small gap at Q90-Q95 (+0.1%) is from `fast-yuv` color conversion ±1 rounding
69
-
- Without `fast-yuv`, Rust **beats C** at all quality levels (up to -0.5%)
70
-
- Visual quality is equivalent (verified via SSIMULACRA2 and Butteraugli)
71
-
72
-
**Previous results (before Feb 2025) showed inflated gaps (up to +5.36%) due to a
73
-
measurement bug: C's `optimize_scans` was not explicitly disabled, so C used an
74
-
optimized 12-scan script while Rust used the fixed 9-scan JCP_MAX_COMPRESSION script.**
93
+
- With trellis at Q75, Rust produces **smaller** files than C (-0.15% to -0.24%)
94
+
- Without trellis, consistent +0.21% gap from `fast-yuv` color conversion ±1 rounding
95
+
- Without `optimize_scans`, all configs within ±0.25% average, worst-case per-image deviation under 1%
96
+
- With `optimize_scans` (Max Compression), within +0.6% average — different scan search heuristics
97
+
- Visual quality equivalent (SSIMULACRA2 and Butteraugli verified)
75
98
76
99
**Mode explanations:**
77
-
-**Baseline** (`progressive(false)`): Sequential DCT with trellis quantization
-**Max Compression**: Rust matches or beats C at Q50-Q80, within 2.2% at all quality levels
80
-
-**Progressive**: Rust beats C at Q50-Q70, within 5% at all levels
81
-
-**Baseline**: Larger gap due to trellis quantization differences at high quality
82
-
83
-
Visual quality (SSIMULACRA2, Butteraugli) is virtually identical at all quality levels.
44
+
Tested on full [Kodak](http://r0k.us/graphics/kodak/) corpus (24 images), 4:2:0 subsampling, `fast-yuv` enabled. Six encoder configurations across four quality levels. Positive delta = Rust files are larger; negative = Rust files are smaller.
45
+
46
+
Reproduce with: `cargo test --release --test parity_benchmark -- --nocapture`
47
+
48
+
| Config | Q | Avg Rust | Avg C | Delta | Max Dev |
**Configs:** Baseline = huffman opt only. +Trellis = AC trellis. Full = AC trellis + DC trellis + deringing. Max Compression = Full + `optimize_scans: true`. All others use `optimize_scans: false`. All use `force_baseline: true`.
80
+
81
+
**Key findings:**
82
+
- With trellis at Q75, Rust produces **smaller** files than C (-0.15% to -0.24%)
83
+
- Without trellis, the consistent +0.21% gap comes from `fast-yuv` color conversion (±1 level rounding)
84
+
- Without `optimize_scans`, all configs stay within ±0.25% average, worst-case per-image deviation under 1%
85
+
- With `optimize_scans` (Max Compression), within +0.6% average — different scan search heuristics
86
+
- Visual quality (SSIMULACRA2, Butteraugli) is equivalent at all settings
|**Trellis EOB optimization**| Available (opt-in) | Available (rarely used) |
223
226
|**Smoothing filter**| Available | Available |
@@ -237,21 +240,18 @@ C mozjpeg's multipass option makes trellis quantization "scan-aware" for progres
237
240
238
241
Multipass produces larger files, is slower, and provides no perceptible quality improvement.
239
242
240
-
### Why the file size gap at high quality?
243
+
### Where does the remaining gap come from?
241
244
242
-
At quality levels above Q85, there's a small gap (1-3%) due to differences in the progressive scan structure:
245
+
The consistent +0.21% gap in non-trellis modes comes from the `fast-yuv` feature, which uses the `yuv` crate for SIMD color conversion (AVX-512/AVX2/SSE/NEON). It has ±1 level rounding differences vs C mozjpeg's color conversion, producing slightly different DCT coefficients. This is invisible after JPEG quantization. Without `fast-yuv`, Rust matches or beats C at all quality levels.
243
246
244
-
-**C mozjpeg** uses a 9-scan successive approximation (SA) script that splits coefficient bits into coarse and fine layers
245
-
-**mozjpeg-rs** uses a 4-scan script (DC + full AC for each component) with per-scan optimal Huffman tables
246
-
247
-
With `optimize_scans=true` (enabled in `max_compression()`), mozjpeg-rs matches or beats C mozjpeg at Q50-Q80.
247
+
With trellis enabled, Rust's trellis optimizer finds slightly better rate-distortion tradeoffs at Q75, producing smaller files than C.
248
248
249
249
### Matching C mozjpeg output exactly
250
250
251
-
For exact byte-identical output to C mozjpeg, you would need to:
252
-
1. Use baseline (non-progressive) mode
253
-
2. Match all encoder settings exactly
254
-
3. Use the same quantization tables (Robidoux/ImageMagick tables)
251
+
For near byte-identical output to C mozjpeg, use baseline mode with matching settings:
252
+
1. Use baseline (non-progressive) mode with Huffman optimization
253
+
2. Match all encoder settings via `TestEncoderConfig`
254
+
3. Use the same quantization tables (Robidoux/ImageMagick, the default for both)
255
255
256
256
The FFI comparison tests in `tests/ffi_comparison.rs` verify component-level parity.
0 commit comments