mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-04 21:33:58 +08:00
84 lines
3.6 KiB
Python
84 lines
3.6 KiB
Python
"""Render synthetic overlay states with the repo's font atlas, entirely on CPU.
|
|
|
|
Requires Pillow. No device, network, audio, replay, worker, or window is opened.
|
|
"""
|
|
import argparse
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT / 'roadscore/prototype'))
|
|
from overlay_view import draw_panel
|
|
|
|
|
|
class Canvas:
|
|
WHITE = (255, 255, 255, 255)
|
|
Color = staticmethod(lambda *rgba: rgba)
|
|
Rectangle = staticmethod(lambda x, y, w, h: (x, y, x + w, y + h))
|
|
Vector2 = staticmethod(lambda x, y: SimpleNamespace(x=x, y=y))
|
|
|
|
def __init__(self, image, font_path):
|
|
self.image = image
|
|
self.atlas = Image.open(font_path.with_suffix('.png')).convert('RGBA')
|
|
lines = font_path.read_text().splitlines()
|
|
self.base = abs(int(re.search(r'size=(-?\d+)', lines[0])[1]))
|
|
self.glyphs = {}
|
|
for line in lines:
|
|
if line.startswith('char '):
|
|
glyph = {key: int(value) for key, value in re.findall(r'(\w+)=(-?\d+)', line)}
|
|
self.glyphs[glyph['id']] = glyph
|
|
|
|
def measure_text_ex(self, font, text, size, spacing):
|
|
return SimpleNamespace(x=sum(self.glyphs[ord(c)]['xadvance'] for c in text) * size / self.base)
|
|
|
|
def draw_text_ex(self, font, text, pos, size, spacing, color):
|
|
scale = size / self.base
|
|
for char in text:
|
|
g = self.glyphs[ord(char)]
|
|
if g['width'] and g['height']:
|
|
mask = self.atlas.crop((g['x'], g['y'], g['x'] + g['width'], g['y'] + g['height'])).getchannel('A')
|
|
mask = mask.resize((max(1, round(g['width'] * scale)), max(1, round(g['height'] * scale))))
|
|
ink = Image.new('RGBA', mask.size, color)
|
|
self.image.paste(ink, (round(pos.x + g['xoffset'] * scale), round(pos.y + g['yoffset'] * scale)), mask)
|
|
pos.x += g['xadvance'] * scale
|
|
|
|
def draw_rectangle_rounded(self, rect, roundness, segments, color):
|
|
layer = Image.new('RGBA', self.image.size)
|
|
ImageDraw.Draw(layer).rounded_rectangle(rect, radius=6, fill=color)
|
|
self.image.alpha_composite(layer)
|
|
|
|
def draw_circle(self, x, y, radius, color):
|
|
ImageDraw.Draw(self.image).ellipse((x-radius, y-radius, x+radius, y+radius), fill=color)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--output', type=Path, required=True)
|
|
args = parser.parse_args()
|
|
base = dict(composer='ace', style='Prism', readiness='READY', section='VERSE / CONTINUOUS',
|
|
next_section='PRECHORUS', form_labels_are_intent=True, buffered=64)
|
|
cases = [{}, base, dict(base, job_inflight=True, generation_elapsed_seconds=23.4, turn_signal_music=True),
|
|
dict(base, readiness='DEGRADED', holding_accepted_music=True, job_inflight=True, buffered=18),
|
|
dict(base, worker_failed=True, buffered=0),
|
|
dict(readiness='READY', style='Stored score', section='ARCHIVED SCORE', compute='none', buffered=92)]
|
|
# Match the comma four UI's logical canvas, not a cropped panel.
|
|
width, height = 536, 240
|
|
board = Image.new('RGBA', (width * 2, height * 3), '#080e16')
|
|
for index, state in enumerate(cases):
|
|
panel = Image.new('RGBA', (width, height), '#080e16')
|
|
canvas = Canvas(panel, ROOT / 'selfdrive/assets/fonts/Inter-Medium.fnt')
|
|
canvas.draw_text_ex(None, 'SYNTHETIC UI / 536 x 240', canvas.Vector2(12, 12), 11, 0, (120, 135, 150, 255))
|
|
draw_panel(canvas, None, state, width, height)
|
|
board.paste(panel, ((index % 2) * width, (index // 2) * height))
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
board.convert('RGB').save(args.output)
|
|
print(args.output)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|