Regressive JPEGs: Turning Progressive JPEG Scans into Frame‑by‑Frame Animation
Regressive JPEGs: Turning Progressive JPEG Scans into Frame‑by‑Frame Animation
Takeaway
Maurycy Cyz’s Regressive JPEGs technique concatenates multiple JPEG scans into a single file so that each progressive scan overwrites the previous one, causing the image to change frame‑by‑frame while it downloads. The result is a standards‑compliant JPEG that behaves like a rudimentary video, with playback speed dictated solely by network latency.
How Progressive JPEG Scans Work
A progressive JPEG stores image data in a series of scans. Each scan declares:
- Which color channels (Y, Cb, Cr) it contains.
- The DCT coefficient range (e.g., DC‑only, low‑frequency AC, high‑frequency AC).
- The precision of the coefficients (full, half, quarter).
The first scan (scan #0) typically contains only the DC (lowest‑frequency) coefficient for all three channels, giving a blurry preview. Subsequent scans add higher‑frequency detail, refining the image without restarting the download.
Example Scan Table from the Project
| Scan | Channels | DCT Bin Range | Precision |
|---|---|---|---|
| 0 | Y Cb Cr | 0‑0 | Half (‑1 bit) |
| 1 | Y | 1‑5 | Quarter (‑2 bits) |
| 2 | Cb | 1‑63 | Half |
| 3 | Cr | 1‑63 | Half |
| 4 | Y | 6‑63 | Quarter |
| 5 | Y | 1‑63 | Half |
| 6 | Y Cr Cb | 0‑0 | Full |
| 7 | Cr | 1‑63 | Full |
| 8 | Cb | 1‑63 | Full |
| 9 | Y | 1‑63 | Full |
Scans 0‑5 provide a low‑precision preview; scans 6‑9 bring the image to full quality.
Overwriting Existing Image Data
Because each scan explicitly sets its spectral range, a later scan can replace pixels that were already displayed. Maurycy realized that by concatenating several JPEG files—stripping the SOI (Start‑of‑Image), SOF (Start‑of‑Frame), and EOI (End‑of‑Image) markers—and joining the remaining scan data, the decoder will treat the combined file as a single progressive JPEG with many more scans.
Simple Construction Steps
- Create individual frames as baseline JPEGs (single‑scan files). Baseline JPEGs are easier because they contain only one scan.
- Strip the JPEG markers (
FF D8,FF C0,FF D9). This leaves only the scan data. - Concatenate the stripped streams in the desired order.
- Optionally add a final EOI marker to make the file a valid JPEG.
A quick C program (merge.c in the project) automates this process.
Limitations of Progressive Mode
Progressive JPEGs cannot mix DC and AC coefficients in the same scan. The smallest progressive scan that is standards‑compliant contains only DC data, which yields an image at 1/16 of the original resolution (because the DCT operates on 16×16 blocks). This limitation caps the number of usable scans before most decoders abort to avoid zip‑bomb‑style attacks.
Decoder Abort Thresholds
- Chrome stops after roughly 90 scans.
- Firefox tolerates more scans, but practical limits still exist.
To stay within these limits, each frame must be represented by a single DC‑only scan.
Creating a DC‑Only Frame
A DC‑only scan is a perfectly valid progressive JPEG that contains only the lowest‑frequency coefficient for each channel. It can be generated with jpegtran:
cat > frame.scans <<EOF
# DC only scan: 0,1,2:0-0,0,0; # and nothing else
EOF
jpegtran -scans frame.scans -outfile frame.jpg source.jpg
The resulting frame.jpg is a tiny, low‑resolution preview that the decoder will display instantly.
Assembling a Video‑Like JPEG
By concatenating dozens of DC‑only frames, Maurycy produced a single JPEG that morphs through a sequence of images as the bytes arrive. Two demos illustrate the effect:
- Cat Switch Demo – the image flips between a cat on grass and the same cat on concrete while loading.
- Walking Cat Demo – a black cat appears to walk toward the camera over ~90 frames.
Both are hosted on the original project site and work in browsers that honor progressive scans.
Practical Considerations
- No Timing Information – playback speed depends entirely on network latency. There is no embedded frame‑rate metadata.
- Server‑Side Chunking – to gain deterministic timing, a server could stream the concatenated JPEG in timed chunks (as suggested by commenters). This mimics the behavior of animated GIFs but without a dedicated container format.
- Steganography Potential – because intermediate scans are discarded by most image analysis tools, one could hide data that only appears during the loading phase.
- Browser Compatibility – Safari freezes until the entire image loads, while Chrome and Firefox display the frame‑by‑frame effect.
Community Reactions
"That is 1. Cursed, and 2. Definitely in the right place here." – robbak
"Safari just freezes in place until the image is entirely finished downloading." – LoganDark
"Nice! I think you can approximate timing somewhat, by making your web server create the JPEG on the fly and send it to the client in timed chunks." – cousin_it
"Excellent hack! Should definitely be possible to make an animated GIF‑to‑JPEG converter." – xnx
"I wonder if you can do this in JPEG‑XL. It has actual animation support, but this would be a different thing." – vanderZwan
These comments highlight both the novelty of the hack and its limitations across browsers.
Conclusion
Maurycy’s Regressive JPEGs demonstrate that the progressive JPEG format can be repurposed to embed a sequence of frames in a single, standards‑compliant image. By limiting each frame to a DC‑only scan and concatenating many such scans, the resulting file behaves like a low‑fidelity video whose playback speed is dictated by network conditions. While not a practical replacement for true video formats, the technique offers a playful exploration of JPEG internals, potential steganographic uses, and a reminder that many image standards contain hidden capabilities waiting to be discovered.