perf(web): stream the preview PNG raw instead of PIL decode + re-encode

display_preview_generator() opened each changed snapshot with PIL and
re-encoded it to PNG just to base64 it — but /tmp/led_matrix_preview.png
already IS a PNG, written atomically by the display service (tmp file +
os.replace in display_manager.py), so a partially-written file can never be
observed. Read the bytes and base64 them directly: identical payload
(front-end consumes data:image/png;base64 — verified in base.html), one
full image decode+encode per frame less on the same Pi that's driving the
matrix. The existing mtime skip and viewer-marker throttling are unchanged
(they already covered the "skip unchanged frames" concern).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
This commit is contained in:
ChuckBuilds
2026-07-16 10:45:28 -04:00
co-authored by Claude Sonnet 5
parent c548cfeabe
commit 4552e823a1
+16 -18
View File
@@ -615,8 +615,6 @@ def system_status_generator():
def display_preview_generator(): def display_preview_generator():
"""Generate display preview updates from snapshot file""" """Generate display preview updates from snapshot file"""
import base64 import base64
from PIL import Image
import io
snapshot_path = "/tmp/led_matrix_preview.png" # nosec B108 - fixed path matches display_manager; only read here snapshot_path = "/tmp/led_matrix_preview.png" # nosec B108 - fixed path matches display_manager; only read here
# Viewer marker: this generator only runs while the broadcaster has # Viewer marker: this generator only runs while the broadcaster has
@@ -658,23 +656,23 @@ def display_preview_generator():
# Only read if file is new or has been updated # Only read if file is new or has been updated
if last_modified is None or current_modified > last_modified: if last_modified is None or current_modified > last_modified:
try: try:
# Read and encode the image # The snapshot is already a PNG, written atomically by
with Image.open(snapshot_path) as img: # the display service (tmp + os.replace in
# Convert to PNG and encode as base64 # display_manager), so pass the raw bytes straight
buffer = io.BytesIO() # through instead of PIL-decoding and re-encoding —
img.save(buffer, format='PNG') # identical payload, much less CPU on the Pi.
img_str = base64.b64encode(buffer.getvalue()).decode('utf-8') with open(snapshot_path, 'rb') as f:
img_str = base64.b64encode(f.read()).decode('utf-8')
preview_data = { preview_data = {
'timestamp': time.time(), 'timestamp': time.time(),
'width': width, 'width': width,
'height': height, 'height': height,
'image': img_str 'image': img_str
} }
last_modified = current_modified last_modified = current_modified
yield preview_data yield preview_data
except Exception: # nosec B110 - SSE preview file may be mid-write; transient error, skip this update except Exception: # nosec B110 - transient read error (e.g. file rotated away); skip this update
# File might be being written, skip this update
pass pass
else: else:
# No snapshot available # No snapshot available