Repository navigation
feat(board): LED bitmap layers for pixel canvases - #343
Merged
Merged
Conversation
jeffredodd
force-pushed
the
feat/led-bitmap-layers
branch
from
October 7, 2026 04:10
a9b5312 to
a8a6251
Compare
Contributor
Visual regression report0 changed · 12 added · 0 removed · 3140 unchanged
📦 Download the full visual report (artifact Baseline from this run · commit |
jeffredodd
force-pushed
the
feat/led-bitmap-layers
branch
from
October 7, 2026 04:35
a8a6251 to
d020185
Compare
FiestaBoard pages can hold pixel canvases; core rasterises them and hands
the preview finished RGBA bitmaps. FiestaUI only draws them.
- LedBitmapLayer {x, y, width, height, rgba: Uint8ClampedArray | base64}
with decodeLedBitmapLayer / decodeLedBitmapRgba and ledMonochromeLit.
- Layout: `layers` on LedLayoutOptions (message-in and cells-in); one
"bitmap" op per layer after every cell op. Raster: alpha > 0 overwrites,
alpha 0 leaves, clipped. Monochrome: 299r+587g+114b >= 127500 lights in
the panel colour, otherwise unlit. No layers = byte-identical layouts.
- Transitions: per-pixel kinds include layers via frames; flip/cascade use
the old layers for the first half of frames and the new for the second,
and repaint layers over half-flaps.
- LedMatrixDisplay / DisplayPreview take `layers` (LED only). Frames with
more than 64 colours paint lit dots through one image (source-in) instead
of a Path2D per colour.
- Golden fixtures gain layer cases; every existing case is unchanged.
- Stories: Pixoo64Canvas, FullBleedArt, MonochromeLayer.
Co-Authored-By: Claude <noreply@anthropic.com>
jeffredodd
force-pushed
the
feat/led-bitmap-layers
branch
from
October 7, 2026 07:24
d020185 to
ea5aa63
Compare
This was referenced Oct 7, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What / why
FiestaBoard pages are getting pixel canvases (gradients, shapes, plugin art) on LED pages. FiestaBoard core does all canvas work (evaluation, rasterisation, placement in panel pixels) and hands the preview finished RGBA bitmaps. This PR adds the part FiestaUI owns: drawing those bitmaps (design §3 step 5, §4).
Stacks on #342 (
feat/led-text-size). Until #342 merges, this diff includes its commit. Review only the top commit (a9b53128).API
LedBitmapLayer = { x, y, width, height, rgba: Uint8ClampedArray | string /* base64 RGBA, row-major */ },DecodedLedBitmapLayer,decodeLedBitmapLayer(normalises the layer: floors the geometry, pads or cutsrgbato w×h×4, and returnsnullfor bad base64 or a layer with no area),decodeLedBitmapRgba,ledMonochromeLit. All are exported from the index.LedLayoutOptions.layersis used bylayoutLedMessage,layoutLedCellGridandrenderLedFrame. Each layer becomes one newLedDrawOp{ kind: "bitmap", x, y, width, height, rgba }, appended after every cell op.LedLayout.layersis set only when layers exist.rasterizeLedOpspaints a layer pixel: alpha > 0 overwrites with the RGB (no blending), alpha 0 leaves the pixel underneath, and the layer is clipped to the matrix.299·r + 587·g + 114·b ≥ 127500(PILLweights, so the Python port can match it) and is unlit otherwise. The thresholded bytes are stored in the op.ledBackgroundMask: opaque layer pixels over a block field are unmasked, so they glow like lit LEDs.fofFswitches when2f ≥ F−1, cascade slotnofNwhen2n ≥ N. Layers are repainted over every half-flap. A change of layers alone snaps under per-cell kinds.LedMatrixDisplaygets alayersprop (it comes fromLedLayoutOptions) and reportsdata-layers. A board with layers but no text is not "empty". The previous layers come along through the shown layout, so a transition runs from what was on screen.DisplayPreviewgets alayersprop that it passes only to the LED renderer. Split-flap renderers ignore it.Paint performance
paintLedFramekeeps onePath2Dper colour up to 64 colours, so every existing frame paints exactly as before and VRT is unchanged. Past 64 colours (for example a photo-like 64×64 layer with 4,096 colours), the lit dots are traced into one path and filled once on a scratch canvas. The frame is then drawn over it at 1 px per LED, nearest-neighbour, withsource-in, so each dot keeps its shape and takes its LED's colour. That is 2 fills plus a fewdrawImagecalls, regardless of the colour count. I checked the result by eye in Storybook (Chromium): the Pixoo canvas, full-bleed plasma and MAX7219 stories all render correctly.Stories
Pixoo64Canvas: a sky gradient, sun and hills canvas, bled to the top edge, above two text rows.FullBleedArt: a 64×64 plasma.MonochromeLayer: a MAX7219 with a grey ramp and a sun.VRT should change for these new stories only.
Test evidence
All run in
node:24Docker:vitest: 68 files, 1236 tests passedprettier --check .: cleaneslint src --quiet: cleantsc --noEmit: cleannode --test scripts/ci/tests/*.test.mjs: 335 passedThe tests were written first. The new lib tests failed 18 of 26 before the implementation, and the component tests failed 5. A mutation pass then broke each behaviour and confirmed a test fails:
127_500→127_0002f ≥ F−1→f ≥ F−12n ≥ N→n ≥ NMAX_COLOR_PATHS→ 100 000DisplayPreviewstops passinglayersGoldens:
led-golden.jsongains 2 layout and 2 transition layer cases (options.layers,fromLayers/toLayers). A JSON comparison confirms all 28 existing layout cases and 11 existing transition cases are byte-identical.🤖 Generated with Claude Code