# Saving

> The picture flattened onto white paper and written as a PNG. Each row gets whichever of the five filters makes its bytes smallest.

Save PNG in the studio writes the picture as you see it: every visible layer over white paper.

The [Writing a PNG](https://www.ziglang.in/learn/graphics/png/) chapter builds a PNG writer by hand: the signature, the chunks, a CRC on each, and zlib compression. Read it first. This chapter is what changes for a real document.

Two things change. The writer takes an allocator, and it chooses a filter for every row.

## Flattening

A PNG could store the layers' transparency. But the picture people expect to save is the one on the screen, with the white paper behind it.

The screen buffer already holds exactly that, so saving starts from it:

<PaintSource file="src/engine/document.zig" decl="writePng" />

It marks the whole screen stale and renders it first. Normally the engine redraws only what changed, and a saved file must not depend on what happened to be drawn last.

## An allocator, not globals

The chapter's writer kept its buffers in global variables. That works for one fixed size of picture.

A document can be any size, so this writer takes an allocator and asks for what it needs:

- the compressor's state, about 224 KB, which is too big for a small stack
- the 64 KB window the compressor needs
- two rows of pixels and five filtered copies of a row
- the compressed output, which grows as it is written

Each allocation has a `defer` right below it, so every one is freed on every path out of the function, including the error paths.

## A filter for every row

The chapter tried two filters on the whole picture and kept the smaller file. A gradient shrank from 3801 bytes to 133 with Sub. The flat test scene got bigger.

A real picture is both. The sky in a painting is smooth and the edge of a stroke is not. So the best filter changes from row to row.

There are five filters. Each one predicts a byte from bytes already written, and stores the difference:

- **None** predicts 0, so it stores the byte as it is.
- **Sub** predicts the byte to the left.
- **Up** predicts the byte above.
- **Average** predicts the average of left and above.
- **Paeth** predicts whichever of left, above and above-left is closest to `left + above − above-left`.

<PaintSource file="src/engine/png.zig" decl="paeth" />

## Choosing

The writer tries all five on every row and keeps one.

It cannot afford to compress every row five ways, so it uses a guess. A good prediction leaves differences near zero. So it adds up the differences, reading each byte as a signed number from −128 to 127, and keeps the filter with the smallest total.

<PaintSource file="src/engine/png.zig" decl="write" />

The guess is the one the PNG specification suggests and libpng uses. It is cheap: one pass over the row for each filter.

`inline for` runs the loop body once for each of the five filters, at compile time. The `switch` on the filter inside it is then decided by the compiler, so each copy of the body does one kind of prediction with no branch in the inner loop.

## Handing the file to the browser

The engine writes the PNG into its own memory. The studio copies the bytes out, wraps them in a `Blob`, and starts a download:

```ts
const length = e.paint_export_png();
const bytes = new Uint8Array(e.memory.buffer, e.paint_png(), length).slice();
const url = URL.createObjectURL(new Blob([bytes], { type: "image/png" }));
```

The `.slice()` copies the bytes. Without it, the `Blob` would point into the engine's memory, which the next export frees and reuses.

## Checks

A picture with flat rows, smooth rows and noise. It must come back exactly, and the writer must have chosen more than one filter for it:

<PaintSource file="src/engine/png.zig" decl="pixels survive the round trip, whatever filters were chosen" />

The tie-breaking rule for Paeth, which the specification fixes. A decoder that broke ties differently would decode a different picture:

<PaintSource file="src/engine/png.zig" decl="paeth breaks ties in the order the specification gives" />

The engine's own reader agreeing with its own writer does not prove much. They could share a mistake. So the site's browser test presses Save PNG and decodes the file with a separate decoder, written for the site's tests from the PNG specification, with zlib from Node. Its pixels must match the canvas.

## Explain before reading the answer

A row is 640 pixels of one flat colour, directly under a row of another flat colour.

Which filter wins?

Sub or Paeth. After the first pixel, both store zeros: Sub predicts the byte to the left, and Paeth picks the left byte too, because the row above is flat.

They differ only on the first pixel. It has nothing to its left, so Sub stores its colour as it is, while Paeth predicts the pixel above and stores the difference. Whichever of those three bytes is closer to zero decides it.

Up loses. It stores the difference between the two colours on every one of the 1,920 bytes.
