QUELLCODE / PY
frame.py
src/hardware_box/vision/frame.py
1# OpenAss: nur privater, experimenteller Gebrauch; auf eigene Verantwortung.2# Niemals Unternehmensrechner, Produktivsysteme, Industrieanlagen oder3# sicherheitskritische Systeme anschließen oder bedienen.4# Haftungshinweis und gesetzliche Grenzen: ../../../DISCLAIMER.txt.5import base646import hashlib7import io8import time9from dataclasses import dataclass1011from PIL import Image12from hardware_box.hid.mapping import Geometry131415@dataclass(frozen=True)16class Frame:17 png: bytes18 geometry: Geometry19 captured_at: float20 sequence: int2122 @property23 def sha256(self) -> str:24 return hashlib.sha256(self.png).hexdigest()2526 @property27 def data_url(self) -> str:28 return "data:image/png;base64," + base64.b64encode(self.png).decode("ascii")293031def prepare(image: Image.Image, settings, sequence: int, captured_at=None) -> Frame:32 # Letterbox, never distort an aspect ratio. Mapping rejects the black bars.33 sw, sh = settings.screenshot_width, settings.screenshot_height34 ratio = min(sw / image.width, sh / image.height)35 cw, ch = max(2, round(image.width * ratio)), max(2, round(image.height * ratio))36 left, top = (sw - cw) // 2, (sh - ch) // 237 resized = image.convert("RGB").resize((cw, ch), Image.Resampling.LANCZOS)38 canvas = Image.new("RGB", (sw, sh))39 canvas.paste(resized, (left, top))40 output = io.BytesIO()41 canvas.save(output, format="PNG")42 geometry = Geometry(sw, sh, settings.target_width, settings.target_height,43 content_rect=(left, top, cw, ch))44 return Frame(output.getvalue(), geometry, captured_at or time.monotonic(), sequence)