Your images are processed in your browser — Cloud HD (Pro, opt-in) is the only exception
← Blog

Canvas Quietly Damages Transparent Pixels — and an Exactness Test Cannot See It

By Gaurav Bhowmick·

If you load a PNG onto an HTML canvas and read the pixels back, some of them come back different. Not because of compression — you have not compressed anything yet. The damage happens in the round trip, it only affects semi-transparent pixels, and the obvious test for "did the pixels survive" is structurally incapable of detecting it.

What the browser does. Browsers store canvas image data withpremultiplied alpha. The WHATWG canvas pixel-manipulation section is explicit that converting to and from premultiplied alpha loses precision, and that a value written and read back may therefore differ.

Premultiplied means each colour channel is stored already multiplied by the alpha value. A pixel with red 200 at 50% opacity is not kept as (200, α=128); it is kept as roughly (100, α=128). When you ask for it back through getImageData(), the browser divides the colour by the alpha again to undo it.

Multiply, round to an 8-bit integer, divide, round again. The two roundings do not cancel. Colour information is destroyed in proportion to how transparent the pixel is — and at very low alpha, the division amplifies whatever rounding error the multiplication left behind.

Fully opaque pixels are completely safe. At alpha 255 the multiply and the divide are both by one. Fully transparent pixels are safe too, in the sense that nobody can see them. Everything in between is where the loss lives — soft shadows, anti-aliased edges, feathered logos, glass effects. Exactly the parts of an image a designer cares most about.

The part that cost us a release. We had a test asserting that our lossless PNG path produced pixels identical to the source. It passed. The path was not lossless.

The test decoded the original through a canvas to get its reference pixels, then decoded our output through a canvas to compare. Both sides went through the same premultiply round trip, so both sides were damaged in exactly the same way, and they matched perfectly. The test was not measuring our encoder. It was measuring whether the browser is deterministic, which it is.

This is a general trap, not a canvas one: if your reference passes through the same lossy step as the thing you are testing, your test proves nothing. You need a reference from outside that path — the raw file bytes, or a decoder that does not premultiply.

The fix. Do not decode at all when you do not have to. Genuinely lossless PNG work means operating on the compressed byte stream — repacking the existing image data with better settings and never turning it into pixels. That is what our lossless path does now: the image is never decoded or repainted, so every pixel comes back identical. The file itself may not be byte-identical — repacking is the point, and metadata chunks are stripped — but nothing about the picture changes.

If you cannot avoid the canvas. There is one real escape, and one that looks like an escape and is not. createImageBitmap() takes a premultiplyAlpha: 'none' option — but that only helps if you then upload the bitmap as a texture. Draw it into a 2D context and it is re-premultiplied on the way in, and you have lost exactly what you lost before. The escape that actually works is WebGL or WebGPU, where you control the texture upload and can read unpremultiplied bytes back. It is considerably more work than a 2D context, which is why the byte route above is usually the better answer.

And if you are only handling fully opaque photographs — most JPEGs, most camera output — none of this applies to you at all. The trap is specifically transparency, which is why it can sit in a codebase for months without anybody noticing.

Related tools

lossless-image-compressionpng-transparent-backgroundCompress PNG

More from the blog

Lossy vs Lossless Compression: When to Use EachWhy Transparent PNG Turns Black When Converted to JPGWhat Is Image Compression and Why Does It Matter?
🔧
Try MiniPx — free, no signup

Compress, convert, and resize images in your browser. Nothing gets uploaded.

Open MiniPx →