mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
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:
@@ -364,6 +364,7 @@ class DisplayManager:
|
||||
# Create image with the (logical) display dimensions
|
||||
self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
|
||||
self.draw = ImageDraw.Draw(self.image)
|
||||
self.draw.fontmode = "1" # 1-bit text: the panel has no partial brightness, so AA only smears glyphs.
|
||||
logger.info(f"Image canvas created with dimensions: {self.matrix.width}x{self.matrix.height}")
|
||||
|
||||
# Initialize font with Press Start 2P
|
||||
@@ -403,6 +404,7 @@ class DisplayManager:
|
||||
|
||||
self.image = Image.new('RGB', (fallback_width, fallback_height))
|
||||
self.draw = ImageDraw.Draw(self.image)
|
||||
self.draw.fontmode = "1" # 1-bit text: the panel has no partial brightness, so AA only smears glyphs.
|
||||
# Simple fallback visualization so web UI shows a realistic canvas
|
||||
try:
|
||||
self.draw.rectangle([0, 0, fallback_width - 1, fallback_height - 1], outline=(255, 0, 0))
|
||||
@@ -705,6 +707,7 @@ class DisplayManager:
|
||||
# self.image, so swapping the buffer below is enough on its own.
|
||||
self.image = Image.new('RGB', (target_w, target_h))
|
||||
self.draw = ImageDraw.Draw(self.image)
|
||||
self.draw.fontmode = "1" # 1-bit text: the panel has no partial brightness, so AA only smears glyphs.
|
||||
yield
|
||||
finally:
|
||||
self.matrix = real_matrix
|
||||
@@ -814,6 +817,7 @@ class DisplayManager:
|
||||
|
||||
self.image = Image.new('RGB', (width, height))
|
||||
self.draw = ImageDraw.Draw(self.image)
|
||||
self.draw.fontmode = "1" # 1-bit text: the panel has no partial brightness, so AA only smears glyphs.
|
||||
logger.debug("Cleared display in fallback mode")
|
||||
return
|
||||
|
||||
@@ -825,6 +829,7 @@ class DisplayManager:
|
||||
# Create a new black image
|
||||
self.image = Image.new('RGB', (self.matrix.width, self.matrix.height))
|
||||
self.draw = ImageDraw.Draw(self.image)
|
||||
self.draw.fontmode = "1" # 1-bit text: the panel has no partial brightness, so AA only smears glyphs.
|
||||
|
||||
if not self._capture_mode_active:
|
||||
# Clear both canvases and the underlying matrix to ensure no artifacts.
|
||||
@@ -1258,6 +1263,7 @@ class DisplayManager:
|
||||
try:
|
||||
self.image = Image.new('RGB', (self.width, self.height))
|
||||
self.draw = ImageDraw.Draw(self.image)
|
||||
self.draw.fontmode = "1" # 1-bit text: the panel has no partial brightness, so AA only smears glyphs.
|
||||
except (OSError, RuntimeError, ValueError, MemoryError):
|
||||
logger.debug("Canvas reset during cleanup failed", exc_info=True)
|
||||
# Reset the singleton state when cleaning up
|
||||
|
||||
Reference in New Issue
Block a user