A designer on my team asked last week why the gradient banner I had compressed looked banded. I said PNG is a lossless format, so that could not happen, then opened both files side by side and it very obviously had. I had been wrong about this for years, in a way that is easy to be wrong about: lossless describes the encoder, not the button you clicked.
Here is the split. The PNG encoder takes an array of pixels, folds it up by looking for repetition along each scanline, and gives you back exactly the same array when you decode it. Nothing is lost there. But almost every tool sold as a PNG compressor saves most of its bytes before the encoder ever runs, by converting the image to indexed color, PNG-8. It counts the colors in the image, picks at most 256 of them as a palette, and snaps every other pixel to the nearest one. That snap is the lossy step, and it happens first. The encoder then losslessly stores an image that has already been altered.
The cheapest way I found to see how much got thrown away is to take one row of pixels and count the distinct values in the red channel. On a 960×480 gradient I built for this, the original PNG is 53.5 KB and that row holds 200 distinct values. At 256 colors the file is 12.8 KB and the row is down to 16 values. At 64 colors, 7.7 KB and 10 values. At 16 colors, 5.0 KB and 5 values. A gradient is nothing but closely spaced neighboring colors, so once the palette runs out the transition has to round off, and the rounding edges are the bands you see.
Dithering does not buy what I thought it did
Most tools offer a dithering switch that scatters pixels of two palette colors along the boundary so your eye mixes them back into a gradient. It does look better. It also costs an absurd amount: the 16-color version went from 5.0 KB to 74.4 KB with dithering on, 14.9 times larger, and bigger than the untouched original. The 64-color version landed at 95.4 KB, 12.3 times its undithered size. My guess is that PNG earns its bytes from runs of identical values along a scanline, and dithering is precisely the thing that breaks those runs into noise.
The measurement surprised me more than the size did. PSNR compares the compressed image to the original pixel by pixel, and higher means closer. The undithered 16-color file scored 25.3, the dithered one 22.6. Dithering moved the result further from the original while making it easier to look at. Pleasant and accurate turn out to be separate axes, and I had been treating them as one.
Where reduction is basically free
None of this makes palette reduction a bad idea. I ran a 1280×800 UI screenshot I built for testing through ImgIng's image compression on defaults: 94.0 KB down to 37.6 KB, 60% saved, a 208-color palette, PSNR 60.7 and SSIM 1.000. Zoomed to 300%, the small text in the code block is essentially pixel-identical to the source. A UI screenshot is flat fills, borders and text, so it never had many colors to begin with and snapping to 256 costs it nothing. For comparison I tried the other common trick on the same file, halving it and saving as JPEG: 20.1 KB, smaller still, PSNR 27.0, and the code block visibly mushy. Screenshots exist to be read, so that trade is a bad one.
My rule now is about the material rather than the tool. Screenshots, flat icons and line art go through palette reduction. Photos, gradients, soft-edged and semi-transparent assets get exported as WebP or AVIF instead. The same shadowed transparent badge is 35.2 KB as PNG, 20.2 KB as WebP for 43% off, and 2.4 KB as AVIF for 93% off, with the edge transition intact; a soft-edged icon went from 11.0 KB to 5.4 KB as WebP. The AVIF numbers are tempting, but I did not test decoding on older devices this round, so anything I hand to someone else still ships as WebP.
One last thing, found by clicking around. That gradient, fed to the same compressor, got classified into the icon and line-art bucket and went through reduction anyway, and came out larger than the input. The tool did not hand me that file: the panel said the original was already the smallest one and kept it. Turning dithering off gave me 13.5 KB, a 75% saving, and the bands right back. So before compressing anything I now ask one question, whether the image contains a large smooth gradient, and afterwards I zoom to 300% and check the gradient area and the smallest text, because neither shows up in a thumbnail. The tool is at https://imging.ai/ and it runs in the browser. The screenshot, gradient, badge and icon above are all test images I made myself.
Top comments (0)