From 93cab98501bb632e78f2e16b56bb43e8ac6346c2 Mon Sep 17 00:00:00 2001 From: MatteoCnda1 Date: Wed, 15 Apr 2026 09:57:15 +0200 Subject: [PATCH 1/2] fix(steami_screen): Add true pixel-scale zoom for scaled text. --- lib/steami_screen/README.md | 3 +- lib/steami_screen/steami_screen/ssd1327.py | 48 ++++++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/lib/steami_screen/README.md b/lib/steami_screen/README.md index da61d15b..3097d628 100644 --- a/lib/steami_screen/README.md +++ b/lib/steami_screen/README.md @@ -74,7 +74,8 @@ screen.text("Big", at="CENTER", scale=2) Cardinal positions: `"N"`, `"NE"`, `"E"`, `"SE"`, `"S"`, `"SW"`, `"W"`, `"NW"`, `"CENTER"`. -Note: `scale=2` produces a bold effect (text drawn with 1px offset), not a true pixel-scale zoom. Backends can provide `draw_scaled_text()` for true scaling. +Note: `scale=2` produces a true pixel-scale zoom on SSD1327 displays via pixel-by-pixel +framebuf rendering. Other backends can implement `draw_scaled_text()` for native scaling support. --- diff --git a/lib/steami_screen/steami_screen/ssd1327.py b/lib/steami_screen/steami_screen/ssd1327.py index c650a716..1c190891 100644 --- a/lib/steami_screen/steami_screen/ssd1327.py +++ b/lib/steami_screen/steami_screen/ssd1327.py @@ -10,6 +10,8 @@ display = SSD1327Display(raw) """ +import framebuf + from steami_screen.colors import rgb_to_gray4 @@ -18,8 +20,8 @@ class SSD1327Display: def __init__(self, raw): self._raw = raw - self.width = getattr(raw, 'width', 128) - self.height = getattr(raw, 'height', 128) + self.width = getattr(raw, "width", 128) + self.height = getattr(raw, "height", 128) def fill(self, color): self._raw.fill(rgb_to_gray4(color)) @@ -35,17 +37,55 @@ def line(self, x1, y1, x2, y2, color): def fill_rect(self, x, y, w, h, color): gray = rgb_to_gray4(color) - if hasattr(self._raw, 'fill_rect'): + if hasattr(self._raw, "fill_rect"): self._raw.fill_rect(x, y, w, h, gray) else: self._raw.framebuf.fill_rect(x, y, w, h, gray) def rect(self, x, y, w, h, color): gray = rgb_to_gray4(color) - if hasattr(self._raw, 'rect'): + if hasattr(self._raw, "rect"): self._raw.rect(x, y, w, h, gray) else: self._raw.framebuf.rect(x, y, w, h, gray) def show(self): self._raw.show() + + def draw_scaled_text(self, text, x, y, color, scale): + """Draw text scaled up using pixel-by-pixel framebuf rendering. + + Renders each character into a temporary 8x8 MONO_HLSB framebuf, + reads each lit pixel, and draws a scale x scale filled rectangle. + This produces a true pixel-scale zoom instead of a bold offset effect. + """ + + gray = rgb_to_gray4(color) + + char_buf = bytearray(8) + fb = framebuf.FrameBuffer(char_buf, 8, 8, framebuf.MONO_HLSB) + + cx = x + for char in text: + fb.fill(0) + fb.text(char, 0, 0, 1) + for py in range(8): + for px in range(8): + if fb.pixel(px, py): + if hasattr(self._raw, "fill_rect"): + self._raw.fill_rect( + cx + px * scale, + y + py * scale, + scale, + scale, + gray, + ) + else: + self._raw.framebuf.fill_rect( + cx + px * scale, + y + py * scale, + scale, + scale, + gray, + ) + cx += 8 * scale From 716acdb04411a2e4163db7a653e5a7cf107bb6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Thu, 16 Apr 2026 21:36:57 +0200 Subject: [PATCH 2/2] fix(steami_screen): Optimize draw_scaled_text and address review. Address Copilot review comments on #398: 1. Defer `import framebuf` into draw_scaled_text() so steami_screen remains importable in CPython environments (tests, stubs, IDE). 2. Resolve fill_rect dispatch once at __init__ (self._fill_rect_raw) instead of calling hasattr() on every lit pixel in the inner loop. Also simplifies the public fill_rect() method. 3. Cache the dispatch as a local `blit` variable inside draw_scaled_text to avoid attribute lookups in the hot pixel loop. 4. Fix README note: scaling works for any scale > 1, not just scale=2. Mention the bold offset fallback for backends without draw_scaled_text. --- lib/steami_screen/README.md | 3 +- lib/steami_screen/steami_screen/ssd1327.py | 41 ++++++++-------------- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/lib/steami_screen/README.md b/lib/steami_screen/README.md index 3097d628..28280a56 100644 --- a/lib/steami_screen/README.md +++ b/lib/steami_screen/README.md @@ -74,8 +74,7 @@ screen.text("Big", at="CENTER", scale=2) Cardinal positions: `"N"`, `"NE"`, `"E"`, `"SE"`, `"S"`, `"SW"`, `"W"`, `"NW"`, `"CENTER"`. -Note: `scale=2` produces a true pixel-scale zoom on SSD1327 displays via pixel-by-pixel -framebuf rendering. Other backends can implement `draw_scaled_text()` for native scaling support. +Note: `scale` > 1 produces a true pixel-scale zoom on SSD1327 displays via pixel-by-pixel framebuf rendering. Other backends can implement `draw_scaled_text()` for native scaling support. Backends without it fall back to a bold offset effect. --- diff --git a/lib/steami_screen/steami_screen/ssd1327.py b/lib/steami_screen/steami_screen/ssd1327.py index 1c190891..64b2e19c 100644 --- a/lib/steami_screen/steami_screen/ssd1327.py +++ b/lib/steami_screen/steami_screen/ssd1327.py @@ -10,8 +10,6 @@ display = SSD1327Display(raw) """ -import framebuf - from steami_screen.colors import rgb_to_gray4 @@ -22,6 +20,11 @@ def __init__(self, raw): self._raw = raw self.width = getattr(raw, "width", 128) self.height = getattr(raw, "height", 128) + # Resolve fill_rect dispatch once at init (avoids repeated hasattr) + if hasattr(raw, "fill_rect"): + self._fill_rect_raw = raw.fill_rect + else: + self._fill_rect_raw = raw.framebuf.fill_rect def fill(self, color): self._raw.fill(rgb_to_gray4(color)) @@ -36,11 +39,7 @@ def line(self, x1, y1, x2, y2, color): self._raw.line(x1, y1, x2, y2, rgb_to_gray4(color)) def fill_rect(self, x, y, w, h, color): - gray = rgb_to_gray4(color) - if hasattr(self._raw, "fill_rect"): - self._raw.fill_rect(x, y, w, h, gray) - else: - self._raw.framebuf.fill_rect(x, y, w, h, gray) + self._fill_rect_raw(x, y, w, h, rgb_to_gray4(color)) def rect(self, x, y, w, h, color): gray = rgb_to_gray4(color) @@ -53,14 +52,17 @@ def show(self): self._raw.show() def draw_scaled_text(self, text, x, y, color, scale): - """Draw text scaled up using pixel-by-pixel framebuf rendering. + """Draw text with true pixel-scale zoom. - Renders each character into a temporary 8x8 MONO_HLSB framebuf, - reads each lit pixel, and draws a scale x scale filled rectangle. - This produces a true pixel-scale zoom instead of a bold offset effect. + Each character is rendered into a temporary 8x8 MONO_HLSB framebuf, + then each lit pixel is expanded into a scale x scale filled rectangle + on the display. The framebuf import is deferred to avoid breaking + imports in CPython environments where framebuf is not available. """ + import framebuf gray = rgb_to_gray4(color) + blit = self._fill_rect_raw char_buf = bytearray(8) fb = framebuf.FrameBuffer(char_buf, 8, 8, framebuf.MONO_HLSB) @@ -72,20 +74,5 @@ def draw_scaled_text(self, text, x, y, color, scale): for py in range(8): for px in range(8): if fb.pixel(px, py): - if hasattr(self._raw, "fill_rect"): - self._raw.fill_rect( - cx + px * scale, - y + py * scale, - scale, - scale, - gray, - ) - else: - self._raw.framebuf.fill_rect( - cx + px * scale, - y + py * scale, - scale, - scale, - gray, - ) + blit(cx + px * scale, y + py * scale, scale, scale, gray) cx += 8 * scale