Match C mozjpeg's optimize_scans output exactly (0% file size difference).
Result: Max Compression mode (progressive + optimize_scans + trellis) matches C mozjpeg within ±0.15% at all quality levels. At Q75 and below, Rust beats C.
Note (Feb 2025): The previous ±2.2% claim was inflated because C test wrappers
didn't explicitly disable optimize_scans. C mozjpeg's default JCP_MAX_COMPRESSION
profile enables optimize_scans=TRUE, so C was using an optimized ~12-scan script
while Rust used the fixed 9-scan script. All C wrappers now properly control this.
Trial encoding for refinement scans was done independently (no state between scans), producing garbage sizes for Ah>0 scans. The optimizer always picked Al=0 (no successive approximation), losing 2-4% compression.
-
ScanTrialEncoder(src/scan_trial.rs) — Sequential trial encoding with state tracking between scans. MaintainsBlockStateper block (DC coded status, AC state). Refinement scans now produce correct sizes because they execute after their first scans. -
Per-scan Huffman tables — Each trial-encoded AC scan builds its own optimal Huffman table via two-pass encoding (count frequencies → build table → encode). This matches C mozjpeg's behavior when
optimize_scans=true. -
Re-encoding for output — After selection, the chosen scan configuration is re-encoded from scratch (unlike C which copies pre-encoded buffers). This produces equivalent results since the Huffman tables are rebuilt from the same data.
- Encode all 64 candidate scans in sequence, storing bytes in buffers
select_scans()called after each scan, uses early terminationcopy_buffer()copies selected pre-encoded scan bytes to output
ScanTrialEncoder::encode_all_scans()encodes 64 scans in sequence with stateScanSelector::select_best()processes all sizes, matching C's algorithm exactlybuild_final_scans()generates optimal scan script, encoder re-encodes from scratch
- Rust re-encodes selected scans instead of copying pre-encoded buffers
- This is functionally equivalent but slightly less efficient at encode time
- Could be optimized in the future by using stored scan buffers from trial encoding
src/scan_trial.rs—ScanTrialEncoder(sequential trial encoding with state)src/scan_optimize.rs—ScanSelector,ScanSearchConfig,generate_search_scans()src/encode.rs:optimize_progressive_scans()— Wires it all together
jcmaster.c:select_scans()(lines 773-962) — Selection with early terminationjcmaster.c:copy_buffer()(lines ~902-956) — Buffer-based output assemblyjcparam.c:jpeg_search_progression()(lines 733-852) — 64 candidate scan generation
# Run benchmark comparison
cargo test --release --test benchmark_runner -- --nocapture
# Enable debug output in scan_optimize.rs
# const DEBUG_SCAN_OPT: bool = true;