Skip to content

feat: Spectra 6 colour rendering for plugins and designed screens - #34

Open
docloulou wants to merge 3 commits into
usetrmnl:mainfrom
docloulou:feat/spectra6-color
Open

docloulou wants to merge 3 commits into
usetrmnl:mainfrom
docloulou:feat/spectra6-color

Conversation

@docloulou

Copy link
Copy Markdown

Spectra 6 colour e-ink support

Adds colour output for 6-colour Spectra 6 panels (black, white, yellow, red, blue, green), e.g. the Seeed EE04 with the 7.3" 800x480 Spectra 6 panel on TRMNL firmware 1.8.x. Today every device image is forced through grayscale + 1-bit dithering, so these panels only show black and white even though the firmware's png_draw_6clr decoder handles colour PNGs.

How it works

  • New seeded display model Spectra 6 7.3" (800x480, colour) (spectra6_800x480, PNG, colors: 6, bitDepth: 1). Existing installs get it on the next container start, because the idempotent seed runs on every boot.
  • For devices whose model has colors === 6, /api/display adds palette=spectra6 to plugin and designed-screen render URLs. Both render endpoints accept palette; only spectra6 is recognised and other values are ignored.
  • With palette=spectra6, applyEinkProcessing keeps the image in colour. It flattens alpha on white, applies a mild saturation/contrast boost, and runs serpentine Floyd–Steinberg error diffusion in RGB against a perceptual palette that approximates real Spectra 6 inks. Each pixel is then written as a pure canonical colour (#000/#fff/#ff0/#f00/#00f/#0f0), so it maps unambiguously through the firmware's RGB333 nearest-colour table. The firmware does no dithering or inversion on Spectra panels, so there is no negate here.
  • Output is an exact 4-bit indexed PNG whose palette has exactly the 6 colours, at full device resolution: about 80 KB for a photo, well under the 750 KB PSRAM limit. It is hand-encoded because sharp/libimagequant with colours: 6 re-quantizes an already 6-colour image into shifted colours.

Unchanged

  • 1-bit, BMP and TRMNL X grayscale paths; mode=preview.
  • Default and sleep screens stay 1-bit.
  • Uploaded screens stay black & white: only their dithered 1-bit file is stored, and the colour original is discarded on upload.
  • BMP models never get a palette.

Tests
cd backend && bun test: 456 pass. Unit tests for the dither and encoder: pure colours stay pure with no negation, every output pixel is one of the 6 canonical colours, and the PLTE holds exactly 6 entries (decoded round-trip, odd width). Also tests for the display URL descriptor and for the palette whitelist on both endpoints.

Add a palette=spectra6 render descriptor for 6-colour e-ink panels
(black, white, yellow, red, blue, green):

- applyEinkProcessing(..., palette) gains a colour branch (PNG only, checked
  before the bitDepth/grayscale paths): flatten alpha on white, mild
  saturation/contrast boost, serpentine Floyd-Steinberg in RGB against a
  perceptual ink palette, then write the canonical colours as an exact
  4-bit indexed PNG (6-entry PLTE). No grayscale, no negate, no downscale.
- The PNG is hand-encoded: sharp/libimagequant with colours: 6 re-quantizes
  an already 6-colour image into fewer, shifted colours.
- display.service appends palette=spectra6 to plugin and designed-screen
  render URLs when the device model has colors === 6 (PNG models only).
- The plugin render route and /api/device-images/design/:id accept palette
  (whitelist: spectra6; anything else is ignored) and thread it through
  PluginsService/PluginRendererService/ScreenRendererService.
- Uploaded screens are unchanged (only the 1-bit dithered file is stored).
New spectra6_800x480 model (image/png, colors 6, bitDepth 1 so default and
sleep screens keep the 1-bit path). The container runs the idempotent seed
on every boot, so existing databases pick the model up on upgrade without
touching existing rows.
- Device page "Current Screen": plugin playlist items were never rendered (the
  default screen was returned); render them as einkPreview with the device's
  palette (paletteForModel), so a Spectra 6 device shows its colour image.
- Plugin einkPreview renders (instance form, Screens/Playlists thumbnails) carry
  no palette: derive it from the devices showing the instance. An explicit
  palette param still wins; device/preview modes are never inferred.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant