Palette-from-image scheme generator #17
Labels
No labels
area/ai
area/backend
area/frontend
area/infra
area/scheduler
area/wled
good-first-issue
priority/high
priority/low
priority/medium
type/bug
type/chore
type/ci-cd
type/docs
type/feature
type/qa
v1.0.0
v1.1.0
v1.2.0
v2.0.0
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Iris-WLED#17
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Let users upload an image (a photo, artwork, a brand palette) and seed a scheme's segment colors from the extracted dominant palette.
Why it's valuable
A fast, delightful non-AI way to author schemes — "make the lights match this sunset photo." Complements the AI suggestion pipeline for users who want deterministic, offline color sourcing.
Sketch
sourceset appropriately), fully editable.Acceptance criteria
Proposed enhancement (not in the original spec).
Landed in #131 (squash-merged to
main). All three acceptance criteria met — image → palette, preview before save, an ordinary editable scheme.Two deviations from the sketch, both deliberate — say the word if you'd rather have it the other way.
Extraction is client-side, where the sketch proposed a Pillow endpoint. Server-side means a self-hosted box parsing arbitrary uploaded images with several megabytes of native code — a meaningful addition to the attack surface #60 spent a release shrinking, on the one path that consumes attacker-chosen bytes. The browser already decodes JPEG, PNG, WebP and HEIC in a sandbox hardened far beyond anything we'd add. So: no new backend dependency, no upload endpoint, no size caps or temp files to get wrong, and the photo never leaves the machine it's on.
Median cut rather than k-means, because the issue asks for deterministic colour sourcing and k-means isn't — it depends on centroid seeding, so the same photo can give two different palettes. A palette you can't reproduce isn't worth saving.
The split point was found by a failing test, not reasoned in advance. Textbook median cut splits at the median index. My first test — equal parts red, green and blue — failed with a bucket 120 units from any colour in the image: sorting by red puts 200 non-red pixels before 100 red ones, so the median lands inside the non-red run and the first bucket averages green with half the blues. A flag or a logo came back as mud. Cutting at the widest gap finds the boundary between clusters instead, and the tie-break (nearest the middle) restores median cut's balance for photos, where every gap is equal.
Smaller decisions, each with a test: pixels are sampled with a stride across the whole image rather than from the front (a photo is laid out row by row, so the first N describe the sky); transparent pixels are skipped; and fewer colours are returned than asked for when the image genuinely has fewer, rather than padding a two-tone logo out to five with colours it doesn't contain.
Five colours are extracted but only three fit WLED's slots — the extras stay on screen so someone who wants the fourth can set it by hand rather than re-uploading.
Verified: 14 new vitest cases asserting properties rather than fixture values,
tsc/eslintclean, 121 frontend tests, build succeeds, CI green. No backend change at all.