"""Generate simple expressive faces for the mask (16 rows tall). The display is ~48x16 on the units we measured. Faces are drawn into a column list (``width`` columns, each 16 ints, row 0 = top) compatible with ``bitmap.encode_bitmap``. Color is per-column, so a face is most reliable in a single color (eyes and mouth can share columns). ``mouth`` 0..4 = closed..wide-open; ``eyes`` True/False = open/blink; ``look`` shifts the eyes left(-)/right(+). """ from __future__ import annotations from typing import List from constants import MASK_HEIGHT DISPLAY_WIDTH = 48 # measured on a MASK-05xxxx unit def _blank(width: int) -> List[List[int]]: return [[0] * width for _ in range(MASK_HEIGHT)] def _fill(rows, r0, r1, c0, c1, width): for r in range(max(0, r0), min(MASK_HEIGHT, r1 + 1)): for c in range(max(0, c0), min(width, c1 + 1)): rows[r][c] = 1 def face(mouth: int = 0, eyes: bool = True, look: int = 0, width: int = DISPLAY_WIDTH) -> List[List[int]]: """Build a face and return it as columns (width x 16).""" rows = _blank(width) cx = width // 2 quarter = width // 4 # --- eyes --- eye_hw = 3 # half-width lx, rx = cx - quarter, cx + quarter if eyes: _fill(rows, 3, 7, lx - eye_hw + look, lx + eye_hw + look, width) _fill(rows, 3, 7, rx - eye_hw + look, rx + eye_hw + look, width) else: # blink: a thin closed line _fill(rows, 6, 6, lx - eye_hw, lx + eye_hw, width) _fill(rows, 6, 6, rx - eye_hw, rx + eye_hw, width) # --- mouth --- m = max(0, min(4, mouth)) if m == 0: # closed: a small smile line _fill(rows, 12, 12, cx - 7, cx + 7, width) else: top, bot = 12 - m, 11 + m hw = 8 - (m - 1) # narrows as it opens, for an "O" shape _fill(rows, top, bot, cx - hw, cx + hw, width) return [[rows[r][c] for r in range(MASK_HEIGHT)] for c in range(width)] # Convenience named expressions -> (mouth, eyes, look) EXPRESSIONS = { "neutral": dict(mouth=0, eyes=True), "blink": dict(mouth=0, eyes=False), "talk_small": dict(mouth=1, eyes=True), "talk_mid": dict(mouth=2, eyes=True), "talk_open": dict(mouth=3, eyes=True), "talk_wide": dict(mouth=4, eyes=True), "look_left": dict(mouth=0, eyes=True, look=-3), "look_right": dict(mouth=0, eyes=True, look=3), } def expression(name: str, width: int = DISPLAY_WIDTH) -> List[List[int]]: return face(width=width, **EXPRESSIONS[name]) def ascii_preview(columns) -> str: """Render columns as ASCII art (for offline checks).""" width = len(columns) return "\n".join( "".join("#" if columns[c][r] else " " for c in range(width)) for r in range(MASK_HEIGHT) )