diff --git a/plugins.json b/plugins.json index 610c1736..aa5599de 100644 --- a/plugins.json +++ b/plugins.json @@ -50,7 +50,7 @@ "last_updated": "2026-09-02", "verified": true, "screenshot": "", - "latest_version": "1.0.4" + "latest_version": "1.0.5" }, { "id": "baseball-scoreboard", diff --git a/plugins/7-segment-clock/manager.py b/plugins/7-segment-clock/manager.py index 359b8143..83a77113 100644 --- a/plugins/7-segment-clock/manager.py +++ b/plugins/7-segment-clock/manager.py @@ -186,19 +186,26 @@ def _colorize( black pixel is unlit. Unlit pixels come back fully transparent so the caller can paste the result straight over whatever is already on the panel. + + Brightness decides which pixels are lit, and alpha is deliberately + ignored. Every asset on disk is mode "1" carrying a tRNS chunk, and both + loaders open them with .convert("RGBA"), which turns the WHITE (lit) + pixels into alpha 0 and the black ones into alpha 255 -- exactly + inverted. The previous rule, `a > 0 and (r or g or b)`, was therefore + false for every pixel: lit ones failed the alpha half, unlit ones failed + the colour half. The result was a fully transparent tile, so the clock + drew nothing at all. + + Do not "restore" an alpha check here. By the time this runs the alpha is + already inverted, so honouring it blanks the display again. """ - rgba = base_image.convert("RGBA") - colored_image = Image.new("RGBA", rgba.size, (0, 0, 0, 0)) - lit = (*color, 255) - clear = (0, 0, 0, 0) - - source = rgba.load() - target = colored_image.load() - for x in range(rgba.width): - for y in range(rgba.height): - r, g, b, a = source[x, y] - # Visible and not black => a lit segment. - target[x, y] = lit if (a > 0 and (r or g or b)) else clear + # Lit == bright. `point` flattens to a hard 0/255 mask; the sources are + # 1-bit, so there are no midtones to lose. + mask = base_image.convert("L").point(lambda v: 255 if v > 0 else 0) + + colored_image = Image.new("RGBA", base_image.size, (0, 0, 0, 0)) + colored_image.paste(Image.new("RGBA", base_image.size, (*color, 255)), + (0, 0), mask) if scale != 1.0: new_width = max(1, int(colored_image.width * scale)) diff --git a/plugins/7-segment-clock/manifest.json b/plugins/7-segment-clock/manifest.json index b07f938d..6eeb7c08 100644 --- a/plugins/7-segment-clock/manifest.json +++ b/plugins/7-segment-clock/manifest.json @@ -1,7 +1,7 @@ { "id": "7-segment-clock", "name": "7-Segment Clock", - "version": "1.0.4", + "version": "1.0.5", "description": "Display a retro-style 7-segment clock with customizable colors", "author": "LEDMatrix", "entry_point": "manager.py", @@ -24,6 +24,12 @@ "default_duration": 15, "config_schema": "config_schema.json", "versions": [ + { + "released": "2026-09-02", + "version": "1.0.5", + "notes": "Fix the clock rendering nothing at all. The digit and separator assets are 1-bit bitmaps with a transparency chunk, so loading them with .convert(\"RGBA\") hands back alpha 0 for the lit (white) pixels and alpha 255 for the unlit black ones -- exactly inverted. The recolour step asked for \"visible AND not black\", which was false for every pixel: lit ones failed the visibility half and unlit ones failed the colour half, so each frame came out fully transparent and the panel stayed dark. Lit-ness is now read from brightness, which the inverted alpha cannot affect. All eight safety-harness renders match the goldens captured before the regression, so the display returns to exactly what it drew before rather than merely drawing something.", + "ledmatrix_min_version": "2.0.0" + }, { "released": "2026-09-02", "version": "1.0.4",