DEV Community

tony huang
tony huang

Posted on

Verifying a white background programmatically (and the two export mistakes it catches)

Marketplace image checks are usually automated, and "looks white" is not the same as "is 255,255,255". If you process product photos in a browser, this is a five-line check worth adding to your pipeline.

Why the naive check fails

Monitors and image previews apply gamma and colour management, so two files that look identical on screen can differ by 4-6 levels per channel. A background at 250/251/249 passes your eyes and fails a strict white check.

Compression makes it worse. JPEG at quality 85 puts ringing artifacts around high-contrast edges — precisely at the product boundary — so the pixels nearest the subject drift away from white even when the flat field is fine.

The check: sample the flat field, not one corner

async function whiteFieldReport(file) {
  const bmp = await createImageBitmap(file);
  const c = new OffscreenCanvas(bmp.width, bmp.height);
  const ctx = c.getContext("2d", { willReadFrequently: true });
  ctx.drawImage(bmp, 0, 0);
  const { data } = ctx.getImageData(0, 0, bmp.width, bmp.height);

  // sample a border band, skipping the product area
  const band = Math.max(2, Math.round(Math.min(bmp.width, bmp.height) * 0.02));
  let n = 0, minC = 255, offWhite = 0, alphaZero = 0;
  const visit = (x, y) => {
    const i = (y * bmp.width + x) * 4;
    const a = data[i + 3];
    if (a === 0) { alphaZero++; return; }          // transparent, not white
    const m = Math.min(data[i], data[i + 1], data[i + 2]);
    if (m < minC) minC = m;
    if (m < 250) offWhite++;
    n++;
  };
  for (let y = 0; y < bmp.height; y++)
    for (let x = 0; x < bmp.width; x++)
      if (x < band || y < band || x >= bmp.width - band || y >= bmp.height - band) visit(x, y);

  return { sampled: n, minChannel: minC, offWhitePct: (100 * offWhite / n).toFixed(2), alphaZero };
}
Enter fullscreen mode Exit fullscreen mode

Three numbers come out of it, and each maps to a different failure:

  • minChannel === 255 → the field is a true white; safe for a strict check.
  • alphaZero > 0 → the file has transparency, not white. If the marketplace wants flattened white, you have the wrong export.
  • offWhitePct high but min near 255 → you have a few dirty pixels, usually from JPEG ringing at the product edge or a stray shadow. Re-export as PNG, or clean the matte.

The two export mistakes this catches

1. Saving the cutout as JPEG. No alpha channel survives, so transparent regions get flattened — usually to white, sometimes to black depending on the encoder. You then cannot tell a real white background from a flattened one without checking alpha, which is gone.

2. Compositing onto near-white. People composite onto #fefefe or a "paper" swatch that came from a design system. It looks white and reads 254. If the check is a strict equality or a threshold above 254, that fails.

What I do in practice

Export PNG with alpha for the working file, composite onto true 255 white only at the final step when the target wants a flattened file, then run the check above on the exported bytes — not on the canvas you composited on. The canvas is the thing you built; the file is the thing that gets uploaded.

For batches, treat minChannel as the pass/fail and offWhitePct as a quality score: anything above roughly 1-2% off-white means you have edge contamination worth looking at before upload.


I build aiphotoeditor.best, where the background removal outputs PNG with alpha preserved and the export step tells you which format you are getting before you spend a credit. Three free AI edits a day, no account; the local adjustments run in the browser and are unlimited.

Honest limits: the matte is imperfect on fine hair against a similar-toned background, and this check tells you nothing about whether the marketplace accepts a soft shadow — that is a rules question that varies by category and platform.

Top comments (0)