fix(display): draw text 1-bit, so glyphs stay crisp on the LED grid (#521)

An LED panel has no partial brightness. PIL defaults ImageDraw's fontmode to
"L", which anti-aliases TrueType glyphs into a grey fringe the panel can only
round off -- a 4px glyph arrives smeared into 3px.

DisplayManager creates its shared `draw` in six places and set fontmode at
none of them, while _load_fonts loads extra_small_font as 4x6-font.ttf at
size 6. Measured at draw time, that face at that size puts 74% of its lit
pixels at partial coverage. Every plugin drawing small text through the
shared draw inherited the blur; geochron was the case that surfaced it.

The harness's VisualDisplayManager had the same gap, which mattered more than
it looks: goldens were recording anti-aliased text that production would not
produce, so the harness could not have caught this. Fixing only production
left geochron still blurry under the harness -- that is how the second site
was found.

Both are set to "1" so the harness renders what the panel renders.


Claude-Session: https://claude.ai/code/session_014RRtqXDCnvnY6EQwhT5CV9

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-04 16:02:04 -04:00
committed by GitHub
co-authored by Claude Opus 5
parent 6bea1a7c21
commit 91d15a8943
2 changed files with 11 additions and 0 deletions
@@ -70,6 +70,7 @@ class VisualTestDisplayManager:
# Canvas
self.image = Image.new('RGB', (width, height), (0, 0, 0))
self.draw = ImageDraw.Draw(self.image)
self.draw.fontmode = "1" # Match production: 1-bit text, so goldens show what the panel shows.
# Matrix proxy (plugins access display_manager.matrix.width/height)
self.matrix = _MatrixProxy(width, height)
@@ -184,6 +185,7 @@ class VisualTestDisplayManager:
self.clear_called = True
self.image = Image.new('RGB', (self._width, self._height), (0, 0, 0))
self.draw = ImageDraw.Draw(self.image)
self.draw.fontmode = "1" # Match production: 1-bit text, so goldens show what the panel shows.
def update_display(self):
"""No-op for hardware; marks that display was updated."""
@@ -211,6 +213,7 @@ class VisualTestDisplayManager:
self.matrix = _MatrixProxy(target_w, target_h)
self.image = Image.new('RGB', (target_w, target_h), (0, 0, 0))
self.draw = ImageDraw.Draw(self.image)
self.draw.fontmode = "1" # Match production: 1-bit text, so goldens show what the panel shows.
yield
finally:
self._width, self._height = prev_w, prev_h
@@ -569,6 +572,7 @@ class VisualTestDisplayManager:
self.draw_calls = []
self.image = Image.new('RGB', (self._width, self._height), (0, 0, 0))
self.draw = ImageDraw.Draw(self.image)
self.draw.fontmode = "1" # Match production: 1-bit text, so goldens show what the panel shows.
self._scrolling_state = {
'is_scrolling': False,
'last_scroll_activity': 0,
@@ -582,3 +586,4 @@ class VisualTestDisplayManager:
"""Clean up resources."""
self.image = Image.new('RGB', (self._width, self._height), (0, 0, 0))
self.draw = ImageDraw.Draw(self.image)
self.draw.fontmode = "1" # Match production: 1-bit text, so goldens show what the panel shows.