# Thaw · Malte Therkildsen

> DEFLATE, zlib and PNG from the specifications, held against zlib and against Chromium pixel for pixel. Every image in the page is decoded twice, by Thaw and by your browser.

Source: https://therkildsen.dev/writing/thaw

[Writing](https://therkildsen.dev/writing)/Thaw
W04NotesEngines from nothing

# Thaw, a PNG decoder written from nothing

Thaw turns the bytes of a PNG back into pixels without the browser’s help: the chunks and their checksums, the DEFLATE stream inside them, and the filters on every row. It was written from an empty file and the specifications. Below, every image is decoded twice, once by Thaw and once by your browser, and the two are compared pixel for pixel.

PostedKind**Notes**Series**Engines from nothing**

        Thaw
        Your browser

          ·

            The filter on each row

            None Sub Up Average Paeth

        Open a PNG of your own
        Check all of PngSuite
        PngSuite is the standard set of test images for PNG decoders. Pick one below.

    Your browser’s pixels are read back through WebGL with color management and premultiplied alpha turned off, so both sides show the numbers stored in the file. Gamma and color profiles are left out on both sides. A file of your own is read here and goes nowhere.

## Copies and codes

      The pixels of a PNG are compressed with DEFLATE, the same method as in a ZIP file and in much of the web’s traffic. DEFLATE stacks two ideas. The first is : when a run of bytes appeared earlier, write “go back so far and copy so many” instead of the bytes. The second is Huffman coding: give the symbols that are used often short codes and the rare ones long codes, so the whole takes fewer bits.

      Type below. Your browser compresses the text with its own DEFLATE, and Thaw decodes it again and shows what it read. Plain letters were sent as they are. The marked ones came from a copy: point at one to see where it was copied from.

        It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity.

        ·

      The code is sent at the start of each block, as a list of lengths alone: given the lengths, the codes themselves follow by a fixed rule. The list is compressed in turn with a third, smaller code, and its own lengths are sent in an order chosen so the unused ones fall at the end and can be left off.

## Each row against the row above

      A photograph rarely repeats a run of bytes exactly, so before compressing, PNG filters each row. A byte is stored as its difference from a guess: the pixel to its left (Sub), the one above (Up), the average of the two (Average), or whichever of left, above and above-left is closest to left plus above minus above-left (Paeth, after Alan Paeth). A smooth picture becomes a stream of small numbers, which compresses well. The encoder picks a filter per row, and the strip beside the image above shows which.

      Undoing them is a few lines each, but the order matters: a row can only be undone after the row above it, and each byte after the one to its left. An interlaced PNG adds , after Adam Costello: the image is sent in seven passes, first every eighth pixel in both directions and last every other row, so a browser can draw a coarse picture early. Each pass is filtered as an image of its own.

## Held against zlib and Chromium

      The DEFLATE decoder is held against zlib, the library nearly every program uses. Random data of four kinds, from noise to a repeated phrase, is compressed by zlib with every level, strategy and window size, and Thaw must give back every byte. Then the streams are damaged at random, by a flipped bit, a cut end or overwritten bytes, and read without a checksum to catch it. Thaw must reject exactly the streams zlib rejects, and read the same bytes from the rest.

      $ node web/notes/thaw/zlib.mjs
20,000 streams from zlib 1.3.1-470d3a2, 199.7 MB, every one given back byte for byte
20,000 damaged streams: zlib and Thaw both refused 11,251 and read the same bytes from the other 8,749
in 11.6 s
      The PNG decoder is held against Chromium’s, pixel for pixel, on all of PngSuite and on random files written by a separate encoder in the command: random sizes, color types, bit depths, palettes, transparency, interlacing and a random filter on every row, cut into random chunks.

      $ node web/notes/thaw/suite.mjs
PngSuite against Chromium 141.0.7390.37: 175 files, 174 the same, 13 of them refused by both
  xcsn0g01.png: Chromium shows it, Thaw refuses it
2,000 random PNGs: 2,000 the same, pixel for pixel
in 23.3 s

## Where Chromium and the specification part

      The first runs disagreed on every image with sixteen bits per sample. A screen shows eight, so a decoder has to drop half, and there are several ways to do it. Chromium, read back through WebGL, keeps the high byte of a gray sample, but turns a color sample into a half-precision float first and rounds from that, which lands one step lower on some values. Thaw now does the same, and agrees on every sample of every such file in PngSuite. Firefox and Safari may do it differently, and the check above will show it.

      One file still differs, on purpose. `xcsn0g01.png` has a wrong checksum on its image data. Under the specification’s rules for errors, a bad checksum on a critical chunk is not one to read past, and Thaw refuses the file. Chromium shows it.

## What it does not have

      Thaw reads the numbers in the file and stops there. It ignores gamma, color profiles and chromaticities, which a browser applies when it shows an image, and that is why the comparison above turns them off on the browser’s side too. It does not play animated PNGs, draws nothing until the whole file has been read, and has no limit on how large a file may unpack to. It is written to be read and to be right.

      The source is in the portfolio’s repository under `web/notes/thaw/`: `thaw.js` for DEFLATE, zlib and PNG, `browser.js` for reading the browser’s pixels, `zlib.mjs` and `suite.mjs` for the commands, `thaw.test.mjs` for the tests. PngSuite is Willem van Schaik’s, free to use for any purpose.

[Newer · W05Glyph, a font renderer written from nothing](https://therkildsen.dev/writing/glyph)[Older · W03Comb, a regex engine that never backtracks](https://therkildsen.dev/writing/comb)
