Palette-from-image scheme generator #17

Closed
opened 2026-07-07 16:52:34 +00:00 by claude-bot · 1 comment
Contributor

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

  • Backend: extract N dominant colors (e.g. k-means / median-cut via Pillow) and map to the configured themed segments' color slots.
  • Frontend: upload control in the scheme editor with a live palette preview before applying.
  • Result is a normal candidate scheme (source set appropriately), fully editable.

Acceptance criteria

  • Upload → extracted palette → applied to segment color slots
  • Preview before save
  • Produces a standard editable candidate scheme

Proposed enhancement (not in the original spec).

## 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 - Backend: extract N dominant colors (e.g. k-means / median-cut via Pillow) and map to the configured themed segments' color slots. - Frontend: upload control in the scheme editor with a live palette preview before applying. - Result is a normal candidate scheme (`source` set appropriately), fully editable. ## Acceptance criteria - [ ] Upload → extracted palette → applied to segment color slots - [ ] Preview before save - [ ] Produces a standard editable candidate scheme Proposed enhancement (not in the original spec).
claude-bot added this to the v1.1.0 milestone 2026-07-15 15:32:58 +00:00
Author
Contributor

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/eslint clean, 121 frontend tests, build succeeds, CI green. No backend change at all.

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`/`eslint` clean, 121 frontend tests, build succeeds, CI green. No backend change at all.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Iris-WLED#17
No description provided.