156 lines
5.7 KiB
Python
156 lines
5.7 KiB
Python
# imx500_gui/ui/renderer.py
|
|
|
|
from typing import Tuple, List, Optional
|
|
import pygame
|
|
import pygame.surfarray as surfarray
|
|
|
|
from .theme import Theme
|
|
|
|
|
|
def clamp01(x: float) -> float:
|
|
return max(0.0, min(1.0, float(x)))
|
|
|
|
|
|
class Renderer:
|
|
"""Zeichnet alle Panels, Texte, Balken und Video-Overlays."""
|
|
|
|
def __init__(self, theme: Theme):
|
|
self.t = theme
|
|
|
|
def conf_color(self, conf: float) -> Tuple[int, int, int]:
|
|
return self.t.GOOD if conf >= 0.60 else self.t.WARN
|
|
|
|
def draw_card(self, surface, rect, fill=None, outline=None, radius=None):
|
|
fill = self.t.PANEL if fill is None else fill
|
|
outline = self.t.LINE if outline is None else outline
|
|
radius = self.t.RADIUS if radius is None else radius
|
|
pygame.draw.rect(surface, fill, rect, border_radius=radius)
|
|
pygame.draw.rect(surface, outline, rect, width=1, border_radius=radius)
|
|
|
|
def draw_text(self, surface, font, text, pos, color=None):
|
|
color = self.t.TEXT if color is None else color
|
|
surface.blit(font.render(text, True, color), pos)
|
|
|
|
def draw_button(self, surface, rect, label, font, primary=False, border_width=1):
|
|
"""Einheitliche Button-Darstellung."""
|
|
if primary:
|
|
fill = self.t.BTN_PRIMARY_FILL
|
|
outline = self.t.BTN_PRIMARY_BORDER
|
|
text_col = self.t.BTN_PRIMARY_BORDER
|
|
else:
|
|
fill = self.t.PANEL_2
|
|
outline = self.t.LINE
|
|
text_col = self.t.TEXT
|
|
|
|
pygame.draw.rect(surface, fill, rect, border_radius=self.t.RADIUS)
|
|
pygame.draw.rect(surface, outline, rect, width=border_width, border_radius=self.t.RADIUS)
|
|
|
|
txt_surf = font.render(label, True, text_col)
|
|
txt_rect = txt_surf.get_rect(center=rect.center)
|
|
surface.blit(txt_surf, txt_rect)
|
|
|
|
def draw_pill(self, surface, font, text, pos, bg=(20, 20, 20, 180), fg=None):
|
|
fg = self.t.TEXT if fg is None else fg
|
|
txt = font.render(text, True, fg)
|
|
w, h = txt.get_size()
|
|
pill_rect = pygame.Rect(pos[0], pos[1], w + 16, h + 8)
|
|
|
|
# Draw translucent background
|
|
s = pygame.Surface((pill_rect.w, pill_rect.h), pygame.SRCALPHA)
|
|
s.fill(bg)
|
|
surface.blit(s, pill_rect.topleft)
|
|
|
|
surface.blit(txt, (pos[0] + 8, pos[1] + 4))
|
|
|
|
def rect_in_video_coords(self, box, src_size, video_rect) -> pygame.Rect:
|
|
"""
|
|
Transformiert eine Box (x,y,w,h) von src_size (Kamera-Auflösung)
|
|
in die Koordinaten des Video-Panels auf dem Screen.
|
|
"""
|
|
sx, sy, sw, sh = box
|
|
src_w, src_h = src_size
|
|
|
|
scale_x = video_rect.w / src_w
|
|
scale_y = video_rect.h / src_h
|
|
|
|
rx = video_rect.x + int(sx * scale_x)
|
|
ry = video_rect.y + int(sy * scale_y)
|
|
rw = int(sw * scale_x)
|
|
rh = int(sh * scale_y)
|
|
return pygame.Rect(rx, ry, rw, rh)
|
|
|
|
def draw_step_indicator(self, surface, rect, step, total_steps, font_small):
|
|
# Background rail
|
|
pygame.draw.rect(surface, self.t.PANEL_2, rect, border_radius=rect.height // 2)
|
|
|
|
if total_steps < 1:
|
|
return
|
|
|
|
# Width of one segment
|
|
seg_w = rect.w / total_steps
|
|
|
|
# Fill active steps
|
|
if step > 0:
|
|
fill_w = step * seg_w
|
|
fill_rect = pygame.Rect(rect.x, rect.y, fill_w, rect.h)
|
|
pygame.draw.rect(surface, self.t.ACCENT, fill_rect, border_radius=rect.height // 2)
|
|
|
|
# Draw segment separators
|
|
for i in range(1, total_steps):
|
|
x = rect.x + i * seg_w
|
|
pygame.draw.line(surface, self.t.BG, (x, rect.y), (x, rect.bottom), 2)
|
|
|
|
# Draw Text Indicator above
|
|
label = f"{step} / {total_steps}"
|
|
txt = font_small.render(label, True, self.t.TEXT_MUTED)
|
|
txt_rect = txt.get_rect(centerx=rect.centerx, bottom=rect.y - 12)
|
|
surface.blit(txt, txt_rect)
|
|
|
|
def draw_pixel_grid(self, surface, rect, spacing=20):
|
|
"""Simuliert Pixel-Raster."""
|
|
col = (255, 255, 255, 30) # sehr transparent
|
|
|
|
# Vertikale Linien
|
|
for x in range(rect.x, rect.right, spacing):
|
|
pygame.draw.line(surface, col, (x, rect.y), (x, rect.bottom))
|
|
|
|
# Horizontale Linien
|
|
for y in range(rect.y, rect.bottom, spacing):
|
|
pygame.draw.line(surface, col, (rect.x, y), (rect.right, y))
|
|
|
|
def draw_bar_chart(self, surface, rect, top3: List[Tuple[str, float]], threshold: float, title_font, body_font):
|
|
"""Zeichnet Balkendiagramm für Top-3 Predictions."""
|
|
|
|
pad_top = title_font.get_linesize() + 10
|
|
pad_left = 14
|
|
pad_right = 14
|
|
|
|
row_h = 28
|
|
gap = 12
|
|
|
|
chart_x = rect.x + pad_left
|
|
chart_y = rect.y + pad_top
|
|
chart_w = rect.w - pad_left - pad_right
|
|
chart_h = 3 * row_h + 2 * gap
|
|
|
|
# Threshold Line
|
|
thr_x = chart_x + int(chart_w * clamp01(threshold))
|
|
pygame.draw.line(surface, self.t.ACCENT, (thr_x, chart_y - 8), (thr_x, chart_y + chart_h + 8), 2)
|
|
|
|
if not top3:
|
|
return
|
|
|
|
for i, (lab, conf) in enumerate(top3[:3]):
|
|
y = chart_y + i * (row_h + gap)
|
|
|
|
# Hintergrund
|
|
pygame.draw.rect(surface, (28, 31, 37), pygame.Rect(chart_x, y, chart_w, row_h), border_radius=10)
|
|
|
|
# Balken
|
|
bar_col = self.conf_color(conf)
|
|
bw = int(chart_w * clamp01(conf))
|
|
pygame.draw.rect(surface, bar_col, pygame.Rect(chart_x, y, bw, row_h), border_radius=10)
|
|
|
|
# Label Text - JETZT IN SCHWARZ (0, 0, 0)
|
|
lbl_surf = body_font.render(f"{lab} {int(conf*100)}%", True, (0, 0, 0))
|
|
surface.blit(lbl_surf, (chart_x + 8, y + (row_h - lbl_surf.get_height()) // 2))
|