A background-removed PNG dropped onto a dark page often shows a thin grey-white rim. The usual advice is to composite with premultiplied alpha. I expected that to help, measured it, and it changed nothing: on my test cutout the fringe averaged a luminance of 75.4 over black with the straight-alpha formula, and exactly 75.4 again after converting to premultiplied first. The white is not a compositing artifact. It is stored in the edge pixels.
Setup and what I can and cannot speak to
The samples are two white-background photos from Wikimedia Commons: a puppy by George Hodan (CC0) and ficus cuttings in a glass bottle by Biusch (CC BY-SA 3.0). For the cutouts I used ImgIng (https://imging.ai/) on its fast tier, ISNet INT8, exporting transparent PNGs and compositing them onto black for measurement. The model runs in the browser, and the six exports produced zero non-GET requests. On ImgIng my own work is on-device codecs and model loading. I did not build the matting model or its edge options, so everything below about them is inferred from exported files, not from reading their code. The refine panel states the default plainly: it uses the AI output pixel by pixel and does not change transparency or edge color.
Straight and premultiplied store different numbers
Straight alpha keeps a pixel's color and its opacity as separate values. Premultiplied alpha stores the color already multiplied by opacity, so the same half-transparent pixel is darker on disk. PNG requires straight alpha. Canvas and GPU pipelines often work premultiplied internally. Paired with its own blend formula, each representation gives the same composite, because premultiplying just moves the multiply earlier. Mixing them is the real bug.
def over(bg, treat_as_premultiplied=False):
src = color if treat_as_premultiplied else color * alpha
out = src + np.asarray(bg, float) * (1 - alpha)
return (out * [0.2126, 0.7152, 0.0722]).sum(-1)[fringe].mean() * 255
Here color and alpha are the exported PNG scaled to 0..1, and fringe selects the 113,438 pixels with alpha between 1 and 254. The correct straight composite gives 75.4. Treating the straight file as premultiplied gives 160.1, because the alpha multiply is skipped and the stored color shows through raw. On the ficus image that mistake gives 200.8 against a correct 52.5. This is the version a hand-rolled pixel loop or a WebGL texture with the wrong premultiply flag produces.
Where the white comes from
The fringe pixels store an average luminance of 160.1, while the opaque fur just inside the edge sits at 108.6. I diffed the export against the source JPEG pixel by pixel. For the 67,624 fringe pixels with alpha from 128 to 254, every one is within 2 of the source color, as are all opaque pixels. So the export attaches an alpha and keeps the photo's color untouched. At a hair-thin edge that color was already fur mixed with white backdrop by the camera. Deviations cluster below alpha 32, where only 56.9% stay within 2. I have not pinned down why; rounding through a premultiplied step is my guess, not something I verified.
Puppy photo from Wikimedia Commons, CC0. Left to right: source crop, default export over black, and export with "remove background color fringe" over black, all at 3x. In the middle panel the fur below the belly carries a soft grey rim, which is the stored white. On the right the rim is gone, but a jagged hard edge appears where the front leg meets the belly.
Fixing it means changing the data
Removing the fringe requires separating the subject color from the mix, and ImgIng's decontamination toggle, off by default, does that by rewriting alpha. On the puppy 76,236 pixels changed alpha, and not one opaque pixel shifted color by more than 30. The fringe over black landed at 76.0 against an estimated 75.6. On the ficus the pale variegated leaf was close enough to white that it got pushed toward transparent and shows holes over black. That is a trade, not a fix.
My order now: first check that the blend formula matches how the file is stored. If the math is right and the rim remains, look at the subject before enabling decontamination. Anything near the old background color, like white fur, pale leaves or glass, gets handled separately.
Top comments (0)