From c218df52667a9a926643d71377feee54a39f8782 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 15:33:23 -0400 Subject: [PATCH] fix(7-segment-clock): draw the digits, which had gone fully blank The panel showed nothing. Every asset is a 1-bit bitmap carrying a tRNS chunk, and both loaders open them with .convert("RGBA") -- which returns alpha 0 for the WHITE (lit) pixels and alpha 255 for the black ones, exactly inverted. _colorize then asked for `a > 0 and (r or g or b)`: "visible and not black". That is false for every pixel in the file. Lit pixels fail the first half, unlit pixels fail the second. Each tile came back fully transparent, so the clock drew nothing. Lit-ness now comes from brightness, which the inverted alpha cannot reach. The alpha check is not merely relaxed but removed, and the docstring says why: by the time _colorize runs the alpha is already inverted, so "restoring" an alpha test blanks the display again. That is the trap this fix exists to close. Verified against the goldens captured before the regression: unfixed, all eight harness renders drift (up to 7062px, max delta 255, i.e. black where the clock should be); fixed, all eight match byte-for-byte. So this restores exactly what the clock used to draw rather than just making it draw something. test_render_polarity.py now passes, and its three counts match an independent audit of the assets: '8' lights 218 pixels, '1' lights 72, the separator 12. --- plugins.json | 2 +- plugins/7-segment-clock/manager.py | 31 ++++++++++++++++----------- plugins/7-segment-clock/manifest.json | 8 ++++++- 3 files changed, 27 insertions(+), 14 deletions(-) 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",