← Blog

JPEG, jpegli, and adaptive quality

JPEG was standardized as ITU-T T.81 in 1992 (ISO/IEC 10918-1 in 1994) and lives in every camera and every PDF with photographs. It still has headroom. pdfcompressor uses the modern jpegli implementation, which produces smaller files at the same visual quality. Google’s jpegli papers cite up to -35% at high quality; on real office PDFs we see -15–25% depending on content.

How JPEG works

Five steps:

  1. Split the image into 8×8 blocks. Everything else operates per block. This is JPEG’s foundation and its weakness: at low quality the block boundaries drift apart and produce visible squares.

  2. Convert RGB to YCbCr. Y is brightness, Cb and Cr are color. The eye discriminates brightness about 4× better than color — the observation the entire codec rests on.

  3. Subsample chroma. Since color matters less, store it at lower resolution. The schemes:

    • 4:4:4 — full color resolution;
    • 4:2:2 — half horizontal (~33% saving);
    • 4:2:0 — half on both axes (50% saving — half the chroma data already gone before main compression).
  4. Apply the discrete cosine transform. Each 8×8 block decomposes into 64 frequency coefficients. The first (DC) is the block’s average brightness; the remaining 63 (AC) describe how strongly different patterns from smooth to sharp are present. A flat block puts almost all its energy in DC; a textured one fills the high frequencies.

  5. Quantize and code. This is the only lossy step. Coefficients get divided by entries from a quantization table and rounded. Bigger divisors mean more rounding, less data, more loss. High-frequency divisors are larger than low-frequency ones — fine detail dies before overall brightness does.

Then ordinary Huffman coding, which is lossless.

“JPEG quality N” is essentially a multiplier on the quantization table. Quality 90 means small divisors and little loss; quality 30 means large divisors and visible artifacts.

Why jpegli beats libjpeg

Every JPEG decoder must produce the same output, so libjpeg, its fork libjpeg-turbo, and jpegli all decode the same files. But the spec leaves the encoder three degrees of freedom:

  1. Quantization tables. The spec recommends example tables; the encoder author can ship their own. The decoder reads the table from the file’s header and applies it.
  2. Huffman tables. Per-file. A better choice means fewer bits.
  3. Rounding strategy. The encoder can round to nearest, or adaptively, accounting for neighboring coefficients.

libjpeg still ships the default quantization tables from the original JPEG standard published in 1992, plus the simplest rounding. jpegli does better at every step:

The output files are fully compatible JPEGs. Every program that reads JPEG reads them.

Why quality 75 and not 85

On a typical photo, the size/quality curve looks roughly like this (illustrative; per-image spread is wide):

Quality Size relative to q=50 Approximate SSIM
50 100% ~0.98
75 ~160% ~0.995
85 ~230% ~0.997
95 ~400% ~0.999

Going from 75 to 85 grows the file by about 1.4× for an SSIM gain of 0.002 — invisible to the eye, very visible on disk. So pdfcompressor’s base is 75. With one important caveat.

Adaptive quality: where 85 actually comes from

GED (Gradient Energy Detection) decides per image whether to bump quality from 75 to 85. The procedure:

  1. Split the image into 16×16 blocks (twice the JPEG block, to capture surrounding context).
  2. Compute the average gradient energy per block — how sharply brightness changes between neighboring pixels. Smooth areas come out near zero; text or sharp transitions come out high.
  3. Sort blocks by that value and take the 95th percentile.
  4. If the 95th percentile is high, the image contains meaningful regions of sharp transitions that aggressive compression would damage. Raise quality to 85.
  5. If low, the image is smooth — a photo with no hard contours — and quality 75 won’t ruin it.

The result: a scan page combining a photograph and body text gets quality 85 and the text stays sharp. An ordinary photograph in a book gets quality 75 and saves 30% of disk.

Other knobs

Progressive JPEG vs baseline. Progressive loads a coarse version first and refines it — useful on the web. With the same tables, progressive JPEG is usually 2–10% smaller than baseline because the Huffman accounting works out more efficiently. PDF readers don’t render progressively, so the user sees no difference. pdfcompressor uses baseline.

EXIF, Photoshop, XMP metadata. A JPEG can carry capture date, GPS, camera settings. On the web that’s valuable; in a PDF it’s noise. Removed, saving a few hundred bytes to several KB per image.

ICC color profile. Embedded profiles affect color reproduction and are preserved. Color gets its own article.