Skip to content

Commit 16386ea

Browse files
MatteoCnda1nedseb
andauthored
fix(steami_screen): Improve text scaling using pixel-by-pixel framebuf. (#398)
* fix(steami_screen): Add true pixel-scale zoom for scaled text. * 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. --------- Co-authored-by: Sébastien NEDJAR <sebastien@nedjar.com>
1 parent 369ea85 commit 16386ea

2 files changed

Lines changed: 36 additions & 9 deletions

File tree

lib/steami_screen/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ screen.text("Big", at="CENTER", scale=2)
7474

7575
Cardinal positions: `"N"`, `"NE"`, `"E"`, `"SE"`, `"S"`, `"SW"`, `"W"`, `"NW"`, `"CENTER"`.
7676

77-
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.
77+
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.
7878

7979
---
8080

lib/steami_screen/steami_screen/ssd1327.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ class SSD1327Display:
1818

1919
def __init__(self, raw):
2020
self._raw = raw
21-
self.width = getattr(raw, 'width', 128)
22-
self.height = getattr(raw, 'height', 128)
21+
self.width = getattr(raw, "width", 128)
22+
self.height = getattr(raw, "height", 128)
23+
# Resolve fill_rect dispatch once at init (avoids repeated hasattr)
24+
if hasattr(raw, "fill_rect"):
25+
self._fill_rect_raw = raw.fill_rect
26+
else:
27+
self._fill_rect_raw = raw.framebuf.fill_rect
2328

2429
def fill(self, color):
2530
self._raw.fill(rgb_to_gray4(color))
@@ -34,18 +39,40 @@ def line(self, x1, y1, x2, y2, color):
3439
self._raw.line(x1, y1, x2, y2, rgb_to_gray4(color))
3540

3641
def fill_rect(self, x, y, w, h, color):
37-
gray = rgb_to_gray4(color)
38-
if hasattr(self._raw, 'fill_rect'):
39-
self._raw.fill_rect(x, y, w, h, gray)
40-
else:
41-
self._raw.framebuf.fill_rect(x, y, w, h, gray)
42+
self._fill_rect_raw(x, y, w, h, rgb_to_gray4(color))
4243

4344
def rect(self, x, y, w, h, color):
4445
gray = rgb_to_gray4(color)
45-
if hasattr(self._raw, 'rect'):
46+
if hasattr(self._raw, "rect"):
4647
self._raw.rect(x, y, w, h, gray)
4748
else:
4849
self._raw.framebuf.rect(x, y, w, h, gray)
4950

5051
def show(self):
5152
self._raw.show()
53+
54+
def draw_scaled_text(self, text, x, y, color, scale):
55+
"""Draw text with true pixel-scale zoom.
56+
57+
Each character is rendered into a temporary 8x8 MONO_HLSB framebuf,
58+
then each lit pixel is expanded into a scale x scale filled rectangle
59+
on the display. The framebuf import is deferred to avoid breaking
60+
imports in CPython environments where framebuf is not available.
61+
"""
62+
import framebuf
63+
64+
gray = rgb_to_gray4(color)
65+
blit = self._fill_rect_raw
66+
67+
char_buf = bytearray(8)
68+
fb = framebuf.FrameBuffer(char_buf, 8, 8, framebuf.MONO_HLSB)
69+
70+
cx = x
71+
for char in text:
72+
fb.fill(0)
73+
fb.text(char, 0, 0, 1)
74+
for py in range(8):
75+
for px in range(8):
76+
if fb.pixel(px, py):
77+
blit(cx + px * scale, y + py * scale, scale, scale, gray)
78+
cx += 8 * scale

0 commit comments

Comments
 (0)