diff --git a/plans/260417-1125-image-importer-enhancements/phase-06-skip-white.md b/plans/260417-1125-image-importer-enhancements/phase-06-skip-white.md index 5ed2f12..f02bfd0 100644 --- a/plans/260417-1125-image-importer-enhancements/phase-06-skip-white.md +++ b/plans/260417-1125-image-importer-enhancements/phase-06-skip-white.md @@ -2,7 +2,7 @@ ## Overview - **Priority:** P3 (low, easy win) -- **Status:** Todo +- **Status:** Done - Two cheap toggles: treat near-white pixels as transparent (useful for logos on white backgrounds), and optionally paint pixels with alpha < threshold as white instead of skipping. ## Requirements @@ -40,10 +40,10 @@ Logic: 5. Tests: near-white pixel → -1 iff skipWhite on; low-alpha pixel → -1 iff paintTransparent off. ## Todo -- [ ] Extend `rgbaToPalette` options -- [ ] UI toggles + threshold -- [ ] CLI flags -- [ ] Tests +- [x] Extend `rgbaToPalette` options (`skipWhite`, `whiteThreshold`, `paintTransparent`) +- [x] UI toggles + threshold slider +- [x] CLI `--skip-white`, `--white-threshold`, `--paint-transparent` +- [x] Tests (4 new cases in image-to-palette.test.js) ## Success Criteria - With defaults, output matches pre-change output. diff --git a/plans/260417-1125-image-importer-enhancements/plan.md b/plans/260417-1125-image-importer-enhancements/plan.md index a3fc357..655f9ab 100644 --- a/plans/260417-1125-image-importer-enhancements/plan.md +++ b/plans/260417-1125-image-importer-enhancements/plan.md @@ -20,7 +20,7 @@ Grow the in-app Image Import panel into a full pre-placement pipeline: resize, t | 3 | [Transforms (flip / rotate)](phase-03-transforms.md) | Done | Medium — quick fixes without re-editing source | | 4 | [More dithering algorithms](phase-04-dithering-algorithms.md) | Done | Medium — different looks per source | | 5 | [Color correction sliders](phase-05-color-correction.md) | Done | Medium — photos benefit most | -| 6 | [Skip-white + paint-transparent toggles](phase-06-skip-white.md) | Todo | Low — easy win, often useful for logos | +| 6 | [Skip-white + paint-transparent toggles](phase-06-skip-white.md) | Done | Low — easy win, often useful for logos | Later / parked: multi-algorithm color distance (Lab/Oklab), Kuwahara smoothing, template save/load, repair mode. Revisit after Phase 6. diff --git a/scripts/image-to-colors.js b/scripts/image-to-colors.js index 48bca1f..483c892 100644 --- a/scripts/image-to-colors.js +++ b/scripts/image-to-colors.js @@ -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); diff --git a/src/client/components/ImageImporter.svelte b/src/client/components/ImageImporter.svelte index c96658c..dda5bcf 100644 --- a/src/client/components/ImageImporter.svelte +++ b/src/client/components/ImageImporter.svelte @@ -26,6 +26,9 @@ let originY = $state(0); let skipMatching = $state(true); let ditherMethod = $state('none'); // see DITHER_METHODS + let skipWhite = $state(false); + let whiteThreshold = $state(230); + let paintTransparent = $state(false); // Resize controls — init to source dims on file load let resizeW = $state(0); @@ -108,7 +111,11 @@ const corrected = (brightness !== 0 || contrast !== 0 || saturation !== 0 || gamma !== 1) ? applyColorCorrection(resized, resizeW, resizeH, { brightness, contrast, saturation, gamma }) : resized; - const idx = rgbaToPalette(corrected, resizeW, resizeH, { method: ditherMethod }); + const idx = rgbaToPalette(corrected, resizeW, resizeH, { + method: ditherMethod, + skipWhite, whiteThreshold, + paintTransparent, + }); paletteIndices = idx; let count = 0; for (let i = 0; i < idx.length; i++) if (idx[i] >= 0) count++; @@ -362,6 +369,25 @@ Skip pixels that already match + +
+ + {#if skipWhite} + + {/if} +
+ +