CompressDog

Progressive JPEG: 6.3% Smaller, Same Pixels, Slower Decode

On 320 photos, the progressive box saved 6.27% and changed zero pixels. It also made decoding 2.5 times slower. Both numbers, and when the trade makes sense.

Every export dialog has it: Progressive, sometimes called interlaced. It is a checkbox, it is off by default in most tools, and the tooltip does not tell you what it does.

We measured it on 320 photos. Same image, same quality 90, same chroma settings, same encoder, one parameter changed. The answer is more interesting than "smaller files".

What actually changes

A baseline JPEG stores the image top to bottom in one pass. A progressive JPEG stores the same data in several passes: a rough version first, then refinements. In a browser, that means you see a blurry whole image quickly instead of a top half.

The thing people miss: the coefficients are identical. Both modes quantise the same DCT blocks with the same tables. Progressive just writes them in a different order, in more scans, which lets the entropy coder do a better job. The decoder reassembles exactly the same numbers. Two things follow, and we can check them rather than assume them.

The numbers

320 JPEGs, resized to 768 px on the long edge, encoded at quality 90 with 4:4:4 chroma.

So the premise holds exactly. Six percent of your bytes come back, the image is not altered in any way, and you pay for it in decode time.

How much is 8.6 milliseconds

In absolute terms the penalty is 8.6 ms per 768 px image on the machine that ran this. That is nothing for a gallery page or a file you are emailing.

The penalty is small in absolute terms, but it stops being small in three situations:

Size change per file from turning on the progressive flag.

Why the checkbox is off by default

Progressive decoding needs a decoder that keeps partial state, so it is more code and more memory. In 1995 that mattered a great deal. The baseline mode was the safe lowest common denominator, and defaults calcified around it. Modern browsers handle progressive without difficulty and have for a long time.

There is one historical cost worth knowing if you serve ancient clients: some very old decoders, and a few embedded ones, only handle baseline. If you are targeting something exotic, baseline is still the safer answer.

What to do

The one thing it is not is a quality setting. It sits in the same dialog as the quality slider, which is why people assume it trades quality for bytes. It does not touch the quality at all.

Caveats

Encoder: Pillow's libjpeg, not mozjpeg or jpegli. The size saving would shift a little with a different encoder, but the zero-pixel-difference result is a consequence of the format, not of the implementation.

Decode timings are from one machine and one Python process, five runs per file with the median taken. Treat the multiplier as an order of magnitude, not a benchmark.

Quality 90 and 4:4:4 only. At lower quality the saving may differ, and with 4:2:0 chroma the absolute sizes change but the principle does not. 768 px images, one collection, already-compressed sources.

Reproducing it

tools/measure-progressive.py. It needs Pillow for the encode and the pixel comparison, and ssimulacra2 on the path for the score. About eight minutes for 320 images.

If your files behave differently, the about page has the address. All seven of our measurements are collected on one summary page, and the raw per-file numbers are on the datasets page.

Sources

Open the compressor