Fix progressive JPEG AC refinement ZRL handling in Iris decoder - #981
Open
korale77 wants to merge 1 commit into
Open
Fix progressive JPEG AC refinement ZRL handling in Iris decoder#981korale77 wants to merge 1 commit into
korale77 wants to merge 1 commit into
Conversation
korale77
force-pushed
the
fix-progressive-jpeg-ac-refine-zrl
branch
from
September 5, 2026 03:54
4371e2d to
0a0db0f
Compare
jpeg_prog_decode_ac_refine treated ZRL (run=15, size=0) as run=16 and reused the skip loop, which kept consuming correction bits for non-zero coefficients after the 16th zero-history coefficient. Those bits belong after the next Huffman symbol (ITU T.81 G.1.2.3, libjpeg decode_mcu_AC_refine), so the bitstream desynchronised: many progressive JPEGs, including plain `cjpeg -progressive` output, failed to load or decoded with wrong pixels. Skip `run` zeros while refining non-zeros on the way, write the new value (0 for ZRL) at the next zero, and stop, matching libjpeg's structure. Add tests/test_image_decode with two cjpeg-generated fixtures that fail on the old decoder (wrong pixels for grayscale, NULL for 4:2:0) and are bit-exact with djpeg after the fix. Wire it into `make test`.
korale77
force-pushed
the
fix-progressive-jpeg-ac-refine-zrl
branch
from
September 6, 2026 04:13
0a0db0f to
100b53d
Compare
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.
Problem
ds4_image_decode_*failed on many progressive JPEGs, including plaincjpeg -progressiveoutput and files produced by common tools. Symptoms wereeither
jpeg_loadreturning NULL ("could not load image") or a successfuldecode with wrong pixels.
Root cause
In
third_party/iris/jpeg.h,jpeg_prog_decode_ac_refinemishandled theZRL symbol (run=15, size=0) in successive-approximation AC refinement scans
(Ah != 0). It set
run = 16and reused the skip loop, which:after the 16th zero, until it reached a 17th zero.
Per ITU T.81 G.1.2.3 (and libjpeg's
decode_mcu_AC_refine), the correctionbits for those trailing non-zero coefficients belong after the next Huffman
symbol. Consuming them early desynchronises the bitstream for the rest of the
block/scan: usually the next symbol decodes as an invalid size and the whole
image fails, otherwise the coefficients come out subtly wrong.
The fix unifies the ZRL and size==1 paths: skip
runzeros while refiningnon-zeros on the way, write
new_val(0 for ZRL) at the (run+1)-th zero, andstop. This matches libjpeg's structure exactly. The upstream Iris repository
(antirez/iris.c, jpeg.h at HEAD) still has the bug; I will send the fix there too.
Testing
Machine: Apple M5 Max, macOS 25.5, clang, default
make(Metal backend).New regression test
tests/test_image_decode(wired intomake test)decodes two committed fixtures generated with libjpeg-turbo 3.2.0
cjpeg(
tests/vision-fixtures/jpeg/generate.shrecreates them byte-identically):prog_ac_refine_zrl_gray.jpg(24x16 gray)prog_ac_refine_zrl_420.jpg(64x48 4:2:0)jpeg_loadreturns NULLdjpeg -nosmoothDifferential check of old vs new decoder against
djpegover 25 images(baseline, progressive 4:4:4/4:2:2/4:2:0, grayscale, restart intervals,
q50-q100, custom multi-refinement scan scripts, and the 1600x309 image that
triggered the report):
cjpeg -progressive4:2:0 encode of the earth fixture, every 4:2:2/4:2:0restart-interval variant, and the reporting image). New decoder decodes all 25.
refinement ZRL, old and new outputs are byte-identical (no behaviour change
for baseline or non-affected progressive files).
djpeg: grayscale is bit-exact; colour images differ byat most 1/255 vs
djpeg -nosmooth(Iris uses box chroma upsampling; theremaining delta is the pre-existing upsampler/IDCT difference, identical to
what baseline JPEGs already show).
variants of the fixtures: no crashes or hangs introduced; the two UBSan
reports found (shift exponent in
jpeg_get_bits,jpeg_zigzag[64]when acorrupt SOS sets Se > 63) reproduce identically on the unpatched decoder and
are left for a separate PR.
make testpasses, including the model-backedds4_testruns against the local DeepSeek 4 Flash GGUF.-Wall -Wextra -std=c99: no new warnings.🤖 Generated with Claude Code