← Blog

PNG compression — deflate plus row filters

Each row picks one of five filters; the filtered residuals deflate better than raw pixels Five filter types per row 0 None store raw byte 1 Sub store byte − left neighbor 2 Up store byte − pixel above 3 Average store byte − (left + above) / 2 4 Paeth store byte − Paeth(left, above, upper-left) Why this matters A flat-color row produces all-zero residuals after Sub or Up; deflate compresses long zero runs to a few bytes

PNG's compression is deflate (the zlib/gzip algorithm) applied to a row-by-row pre-processed stream. The pre-processing — five "filter" types that turn pixel bytes into residuals against neighboring pixels — is what lets PNG outperform raw deflate by a substantial margin on image data. PDF's /FlateDecode with PNG predictor parameters supports the same scheme.

Why raw pixels deflate poorly

Deflate finds repeated byte sequences and replaces them with back-references. For text or code this works brilliantly: long stretches of common words and identifiers are compressed efficiently. For pixel data it works less well: even a smooth gradient produces a continuous progression of values (120, 121, 122, 123, ...) that has no exact byte repeats.

If you transform that gradient row into differences from each previous pixel, you get (120, 1, 1, 1, 1, ...) — a single starting value followed by hundreds of identical 1-byte differences. Deflate compresses that to almost nothing.

The five filter types

For each row of pixels, the encoder picks one of five filters and writes a 1-byte filter selector before the row's data:

The encoder typically tries all five filters per row and picks the one whose residuals deflate smallest. Multi-pass lossless PNG optimizers do this exhaustively, producing the smallest PNGs at the cost of much longer encoding times.

How PDF handles this

PDF's /FlateDecode filter optionally takes a predictor parameter:

/DecodeParms <<
  /Predictor 15        % 15 = "PNG predictor: any" (decoder picks per row)
  /Columns  1920       % image width in pixels
  /Colors   3          % channels (3 for RGB, 1 for gray)
  /BitsPerComponent 8
>>

Predictor values 10–14 specify a single PNG filter (None / Sub / Up / Average / Paeth) for the entire image. Predictor 15 means "each row picks its own filter, prefixed by the filter byte" — exactly how PNG's IDAT stream works.

An ideal PNG-to-PDF tool would set /Predictor 15 and copy the IDAT data unchanged — true pass-through. In practice, most general-purpose PDF libraries don't go that far: they take the decoded pixels and re-encode them with plain /FlateDecode inside the PDF. The decoded pixels are identical to the input; output size is similar but not byte-identical to the IDAT bytes. For maximum size efficiency on synthetic content, specialized tools that pass IDAT through produce slightly tighter output.

Size impact of filter selection

For a typical 1920×1080 photo:

The savings are larger for synthetic images (UI screenshots, line art) and smaller for noisy photos.

Why PNG loses to JPEG for photos

Even with optimal filter selection, PNG is lossless: every pixel value is preserved exactly. JPEG is lossy: small high-frequency details are quantized away. For photographic content, JPEG at quality 85 is typically 5–10× smaller than the same image as PNG. The difference is invisible on most screens at typical viewing distance.

This is why most cameras save JPEG by default and most screenshot tools save PNG: cameras prioritize file size for content where minor losses don't matter; screenshot tools prioritize pixel-exact preservation for content where losses would be obvious (sharp edges of text, UI elements).

PNG2PDF does not convert PNG to JPEG. A lossless input becomes a lossless PDF page. If you want JPEG-sized output, convert the PNGs to JPEGs first, then run JPG2PDF.

zlib compression levels

The deflate spec has compression levels 0–9. PNG encoders typically default to level 6 (a balance of size and speed). Level 9 produces 1–3% smaller files at 4× the encoding time. Aggressive deflate searchers ignore the standard levels entirely and pursue optimal back-reference choices, achieving 3–7% better compression at 100× the time.

PNG2PDF runs a lossless re-compression pass on PNG inputs by default — the optimizer tries different filter and zlib choices and keeps the smaller result before the file is embedded in the PDF. The savings are usually 5–25% on synthetic content. For even smaller output, run a multi-pass PNG optimizer with aggressive settings before upload, or pre-quantize truecolor screenshots to a 256-entry palette.

When PDF readers do not implement PNG predictor

The vast majority of PDF readers handle Predictor 15 correctly — it's been in the spec since PDF 1.5. But some legacy or simplified readers don't:

If a PDF rendered correctly in mainstream readers shows garbled images in a specific tool, predictor support is the likely culprit. The fix is to re-save the PDF through a different tool: most PDF editors offer a "save without compression" or "uncompress streams" option that re-writes images without the predictor.

This is rarely relevant in practice, but worth knowing if you encounter PDFs that fail in legacy readers.