fix(brand): logo composited at SVG-native res, then downsampled

Previous render composed directly at the target size (110×110 / 44×44),
which produced thin text and lost the SVG's typographic intent. Now
the composition runs at SVG-native 320×320 — same coordinates as
webApp/frontend/public/logo.svg — and downsamples to the panel logo
size with LANCZOS. Adds stroke_width=2 around the wordmark to fake
font-weight 900 (DejaVuSans is only weight 700; no Black face is on
the build host, so this is the best approximation without bundling a
font binary into the firmware repo).

The yellow V comes through, the wordmark is heavier, and the harbour
background still palette-quantizes recognisably to Spectra-6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 22:49:23 -04:00
parent eff34717c9
commit 3420ec56f5
14 changed files with 40 additions and 32 deletions
+15 -13
View File
@@ -122,33 +122,35 @@ LOGO_SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"IMG_2524-square900.jpg")
def compose_logo(size):
"""Returns a size×size RGB PIL image — harbour photo + black-fade + wordmark."""
bg = Image.open(LOGO_SRC).convert("RGB").resize((size, size), Image.LANCZOS)
overlay = Image.new("RGBA", (size, size), (0, 0, 0, 0))
"""Build at SVG-native 320×320, downsample to `size`. See
gen_screens_13e6.py for the full rationale."""
SRC = 320
bg = Image.open(LOGO_SRC).convert("RGB").resize((SRC, SRC), Image.LANCZOS)
overlay = Image.new("RGBA", (SRC, SRC), (0, 0, 0, 0))
od = ImageDraw.Draw(overlay)
for y in range(size):
t = y / max(1, size - 1)
for y in range(SRC):
t = y / (SRC - 1)
if t < 0.42:
a = 0.45 * (t / 0.42)
elif t < 0.58:
a = 0.45
else:
a = 0.45 * ((1 - t) / 0.42)
od.line([(0, y), (size, y)], fill=(0, 0, 0, int(a * 255)))
od.line([(0, y), (SRC, y)], fill=(0, 0, 0, int(a * 255)))
bg = Image.alpha_composite(bg.convert("RGBA"), overlay).convert("RGB")
draw = ImageDraw.Draw(bg)
font_px = max(8, int(62 * size / 320))
font = ttf("DejaVuSans-Bold.ttf", font_px)
font = ttf("DejaVuSans-Bold.ttf", 62)
parts = [("We", WH), ("V", YL), ("isto", WH)]
widths = [draw.textbbox((0, 0), t, font=font)[2] for t, _ in parts]
widths = [draw.textbbox((0, 0), t, font=font, stroke_width=2)[2] for t, _ in parts]
total_w = sum(widths)
x = (size - total_w) // 2
y = int(0.547 * size) - font_px // 2 - 2
x = (SRC - total_w) // 2
y = 175 - 62 // 2 - 16
for (t, fill), w in zip(parts, widths):
draw.text((x, y), t, font=font, fill=fill)
draw.text((x, y), t, font=font, fill=fill,
stroke_width=2, stroke_fill=fill)
x += w
return bg
return bg.resize((size, size), Image.LANCZOS)
def draw_logo_placeholder(img):
img.paste(compose_logo(LOGO_SIDE), (LOGO_X, LOGO_Y))