You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(shaders): add PaletteMap, a dithered palette Effect - #279
Composition 2 maps a gray heat field onto six colors, and scatters single cells across each band edge instead of drawing a line. No existing part could do that. Dither quantizes each RGB channel on its own, and FractalNoise's stops land on chosen colors but cut clean contours.
What changes
PaletteMap
A new Effect that reads the OKLab lightness of the image beneath it and picks one of its colors, spaced evenly from darkest to lightest. A blue-noise threshold per cell decides whether a cell between two colors rounds up or down. spread sets how wide that scatter runs and defaults to 0.5. pixelSize sets the cell, and matching it to LedWall's spacing gives each dot one color.
Color-only resamples
PaletteMap registers as a resample rather than a color transform, so it runs inside LedWall's cell snap once per cell. ADR 0006 records the rule. It also records what follows: CursorTrail (SHA-169) must be a resample too, or its heat never reaches the map. The glossary gains a Color transform entry, and useResample's JSDoc no longer says a resample reads only what Sources drew.
Docs and tests
The component page demos PaletteMap over a gray FractalNoise with contrast={3.5}, because at the default contrast the darkest of the four default colors barely shows. A visual spec pins the page on the WebGL2 fallback. Two pixel checks on /dev/palette-map-probe cover composition 2's setup: every LED dot holds one palette color, and spread scatters cells across band edges. The canvas readback they use moves out of the FlutedGlass spec into helpers.ts.
Known limitations
CI checks only the WebGL2 fallback. WebGPU was checked by eye in the dev server only.
PaletteMap maps the lightness of the image beneath it onto a few chosen
colors. A blue-noise threshold per cell scatters the two colors at each
band edge, and spread sets how wide that scatter runs. It registers as a
resample, so under LedWall it runs once per cell and each dot takes
exactly one palette color. A dev probe at /dev/palette-map-probe shows it
over a gray FractalNoise.
Refs SHA-308
An Effect that changes only color registers as a resample when a UV warp
mounted after it must move it. PaletteMap is the first, and CursorTrail
follows the same rule so its heat reaches the palette map. The glossary
gains a Color transform entry to name the other case, and useResample's
JSDoc now says a resample reads what the resamples beneath it changed.
Refs SHA-308
The page demos PaletteMap over a gray FractalNoise with its contrast at
3.5, so the noise's lightness covers the full range and all four default
colors show. At the default contrast the first color barely appears. The
panel edits pixel size, spread, and the palette.
A visual spec pins the default story on the WebGL2 fallback. Two pixel
checks on the dev probe cover the composition 2 setup under LedWall:
each dot holds one palette color, and spread scatters cells across band
edges. The canvas readback they use moves out of the FlutedGlass spec
into helpers.ts so both specs share it.
Refs SHA-308
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Palette mapping can select incorrect colors for wide-gamut sources, leave static scenes showing stale palettes after color changes, and introduce stray cells at spread 1.
Adds PaletteMap, a lightness-based palette Effect for composition 2’s dithered heat field. It runs as a resample so a later LedWall can display one palette color per dot.
Changes:
Adds the public component with palette, cell-size, and scatter controls.
Integrates the docs demo and records the color-only resample rule.
Adds visual and pixel checks and shares canvas-readback helpers.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
RGB clamping distorts wide-gamut lightness and selects incorrect palette colors, while blue-noise thresholds reaching 1 introduce stray colors in flat areas at spread=1.
- Measure lightness without clamping negative channels first. The clamp
lifted the L of wide-gamut colors and could pick the next palette color.
- Center the blue-noise bytes inside 0..1. A byte of 255 read back as 1,
so at spread 1 some cells in flat black areas took the second color.
- Request a frame after a palette change, so a static scene shows it.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Removing PaletteMap from a static scene leaves the last palette-mapped frame visible because unmount cleanup does not request a render after the output graph is rebuilt.
Removing PaletteMap from an otherwise unchanged static scene (for example, over <LinearGradient speed={0} />) leaves the last palette-mapped frame visible. useResample unregisters and rebuilds the output graph, but no unmount cleanup requests a frame, so the parked scheduler never draws the image without the map. Return a cleanup here that requests a render after the resample has been removed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Composition 2 maps a gray heat field onto six colors, and scatters single cells across each band edge instead of drawing a line. No existing part could do that.
Ditherquantizes each RGB channel on its own, and FractalNoise'sstopsland on chosen colors but cut clean contours.What changes
PaletteMap
A new Effect that reads the OKLab lightness of the image beneath it and picks one of its
colors, spaced evenly from darkest to lightest. A blue-noise threshold per cell decides whether a cell between two colors rounds up or down.spreadsets how wide that scatter runs and defaults to 0.5.pixelSizesets the cell, and matching it toLedWall'sspacinggives each dot one color.Color-only resamples
PaletteMap registers as a resample rather than a color transform, so it runs inside LedWall's cell snap once per cell. ADR 0006 records the rule. It also records what follows: CursorTrail (SHA-169) must be a resample too, or its heat never reaches the map. The glossary gains a Color transform entry, and
useResample's JSDoc no longer says a resample reads only what Sources drew.Docs and tests
The component page demos PaletteMap over a gray FractalNoise with
contrast={3.5}, because at the default contrast the darkest of the four default colors barely shows. A visual spec pins the page on the WebGL2 fallback. Two pixel checks on/dev/palette-map-probecover composition 2's setup: every LED dot holds one palette color, and spread scatters cells across band edges. The canvas readback they use moves out of the FlutedGlass spec intohelpers.ts.Known limitations
CI checks only the WebGL2 fallback. WebGPU was checked by eye in the dev server only.
Closes SHA-308