An indexed PNG recolours for free

Most of what this site does is arithmetic over millions of pixels. For one family of files it is arithmetic over a few hundred bytes, and the pixels are never read at all.

What colour type three actually stores

A PNG file is a signature followed by a list of chunks, each one a length, a four-letter type, its data, and a checksum. The header chunk carries the dimensions, the bit depth and a colour type. Colour type two means each pixel holds its own red, green and blue. Colour type six adds an alpha channel. Colour type three means something different in kind: the file carries a table of colours in a chunk called PLTE, at most 256 entries of three bytes each, and every pixel is an index into that table.

Screenshots of interfaces, logos exported from a vector editor, icons, diagrams, charts and anything else made of flat regions are frequently written this way, because a table of a few dozen colours plus one small index per pixel compresses far better than three full channels. The format is doing exactly what it was designed for.

It also means that recolouring such a file is not an image operation. The colours are not in the pixel data. They are in a table that is, at most, 768 bytes long.

A 1200 by 1200 logo, 48 palette entries
What has to changeBytesPixel data touched
the palette path144 of 144 read, up to 144 writtennone
the truecolour path5,760,000 read and writtenall of it

What the fast path does, step by step

When a file opens, the first sixty-four kilobytes are examined. The signature is checked, the chunk list is walked, the header is read, and if the colour type is three and a palette chunk is present with a length divisible by three, the file is classified as indexed and the rail says so with the entry count. Nothing else about the file is parsed, and the decoded picture you are working on is the ordinary decoded picture — the classification only changes what happens at the export.

On export by that route, the whole file is read. The palette is lifted out as a run of RGBA values and the operation stack runs over it exactly as it would over an image one pixel tall, which means the weighted mean chroma and mean lightness a hue shift needs are taken across the entries the selection caught. The rewritten entries are written back into a copy of the original bytes, the checksum of that one chunk is recomputed, and the file is handed over.

Why the result is exact and not merely good

Every byte outside the palette chunk and its four checksum bytes is the byte that was there before. The compressed pixel data is not decompressed, not re-filtered and not re-deflated; it is copied. The transparency chunk, the gamma chunk, the text chunks and the header are copied. The file comes out the same length it went in, it is still an indexed PNG, and opening it in any decoder gives the original indices pointing at the new colours.

Compare that with the photographic path. There, the file is decoded to truecolour, every pixel is converted into OKLab, weighed, remapped and converted back, and the result is re-encoded as a new PNG. The visible output can be very close. The file is not: it is typically several times larger because it is no longer indexed, the flat regions now contain whatever the resampler and the encoder produced, and any chunk the original carried is gone.

When it does not apply

The path needs the original file, so it is offered only while the file you opened is still the file being exported. It is refused rather than approximated for a PNG of any other colour type, for a palette chunk with a length that is not a multiple of three, and for every other format — a GIF has a comparable structure and is not handled here, because the browser hands over a decoded first frame rather than the container.

There is one behaviour worth predicting. A palette holds distinct colours, not distinct objects, so an entry used by two unrelated parts of a logo changes in both places at once. On a photograph converted to indexed colour — which some screenshot tools do — the palette is a sampled approximation of a continuous image, and rewriting a handful of its entries produces visible banding where the original had a gradient. That is not a defect in the rewrite; it is what the file already was, made visible. For that kind of source, the truecolour path is the right one.

The short rule: if the file is a logo, an icon, a chart or a screenshot of an interface, keep it indexed and let the table do the work. If it is a photograph that happens to be stored indexed, do not.

Back to the swatches

Every pixel is remapped locally — this page has no server to send to.