84 lines
2.7 KiB
Python

"""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)
)