53 lines
1.7 KiB
Python
53 lines
1.7 KiB
Python
"""Status image functions — loading, alpha compositing, and WS transmission."""
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
IMG_DIR = Path(__file__).parent / "assets" / "img"
|
|
STATUS_IMG_SIZE = 120
|
|
MONOCHROME_THRESHOLD = 140
|
|
|
|
|
|
def load_status_image(path: Path) -> bytes:
|
|
"""Load a PNG, convert to 1-bit 120x120 monochrome bitmap (MSB-first, black=1).
|
|
|
|
Transparent pixels are composited onto white so they don't render as black.
|
|
"""
|
|
img = Image.open(path)
|
|
|
|
# Composite transparent pixels onto white background
|
|
if img.mode in ("RGBA", "LA", "PA"):
|
|
bg = Image.new("RGBA", img.size, (255, 255, 255, 255))
|
|
bg.paste(img, mask=img.split()[-1])
|
|
img = bg
|
|
|
|
img = img.convert("L")
|
|
|
|
# Resize to fit within 120x120, preserving aspect ratio
|
|
img.thumbnail((STATUS_IMG_SIZE, STATUS_IMG_SIZE), Image.LANCZOS)
|
|
|
|
# Paste centered onto white canvas
|
|
canvas = Image.new("L", (STATUS_IMG_SIZE, STATUS_IMG_SIZE), 255)
|
|
x_off = (STATUS_IMG_SIZE - img.width) // 2
|
|
y_off = (STATUS_IMG_SIZE - img.height) // 2
|
|
canvas.paste(img, (x_off, y_off))
|
|
|
|
# Threshold to 1-bit: black (< MONOCHROME_THRESHOLD) -> 1, white -> 0
|
|
bw = canvas.point(lambda p: 1 if p < MONOCHROME_THRESHOLD else 0, "1")
|
|
raw = bw.tobytes()
|
|
log.info("Status image loaded: %s -> %d bytes", path.name, len(raw))
|
|
return raw
|
|
|
|
|
|
async def send_status_image(ws, img_bytes: bytes):
|
|
"""Send a status image over the WebSocket (text header + binary payload)."""
|
|
header = json.dumps({"type": "status_image", "width": STATUS_IMG_SIZE, "height": STATUS_IMG_SIZE})
|
|
await ws.send(header)
|
|
await ws.send(img_bytes)
|
|
log.info("Sent status image (%d bytes)", len(img_bytes))
|