feat(importer): phase 6 — skip-white + paint-transparent toggles

Two quick quality-of-life options for logo and background-heavy imports:

- Skip-white: near-white pixels (r,g,b >= threshold, default 230) are
  marked transparent in the output so they don't consume credits. Useful
  when the source has a white background you don't want to paint.
- Paint-transparent: fully-transparent source pixels are treated as opaque
  white before quantization. Useful if the canvas background is white and
  you want empty regions of a logo to blend in.

Both toggles compose with every existing dither method; paint-transparent
runs first, so combining the two leaves originally-transparent pixels as
skip (since they're synthesized white, then the skip-white pass drops
them).

UI adds the two checkboxes and a threshold slider shown only when
skip-white is on. CLI gains --skip-white, --white-threshold,
--paint-transparent. 4 new unit tests pin the behavior including the
paint+skip interaction.
This commit is contained in:
tiennm99 committed 2026-04-17 11:58:55 +07:00
1 parent 6c35771445
commit d924a66c50
6 files changed
+129 -15

No files matched your search

+10 -1
View File
@@ -35,6 +35,9 @@ const { values, positionals } = parseArgs({
contrast: { type: 'string', default: '0' },
saturation: { type: 'string', default: '0' },
gamma: { type: 'string', default: '1' },
'skip-white': { type: 'boolean', default: false },
'white-threshold': { type: 'string', default: '230' },
'paint-transparent': { type: 'boolean', default: false },
},
allowPositionals: true,
});
@@ -100,7 +103,13 @@ const working = hasCorrection
// sharp raw buffer has channels=4 after ensureAlpha; same layout as Canvas ImageData.
const ditherMethod = values['dither-method'] ?? (values.dither ? 'floyd' : 'none');
const indices = rgbaToPalette(working, outW, outH, { alphaThreshold, method: ditherMethod });
const indices = rgbaToPalette(working, outW, outH, {
alphaThreshold,
method: ditherMethod,
skipWhite: values['skip-white'],
whiteThreshold: parseInt(values['white-threshold'], 10),
paintTransparent: values['paint-transparent'],
});
const pixels = Array.from(indices);
const opaque = pixels.reduce((n, p) => n + (p >= 0 ? 1 : 0), 0);