diff --git a/lib/steami_screen/README.md b/lib/steami_screen/README.md index da61d15..28280a5 100644 --- a/lib/steami_screen/README.md +++ b/lib/steami_screen/README.md @@ -74,7 +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 bold effect (text drawn with 1px offset), not a true pixel-scale zoom. Backends can provide `draw_scaled_text()` for true scaling. +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 c650a71..64b2e19 100644 --- a/lib/steami_screen/steami_screen/ssd1327.py +++ b/lib/steami_screen/steami_screen/ssd1327.py @@ -18,8 +18,13 @@ 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) + # 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)) @@ -34,18 +39,40 @@ 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) - 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 with true pixel-scale zoom. + + 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) + + 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): + blit(cx + px * scale, y + py * scale, scale, scale, gray) + cx += 8 * scale