feat(brand): real logo composited onto setup screens
Replaces the bordered text placeholder with the composed WeVisto logo (harbour photo + dark gradient + 'WeVisto' wordmark with the yellow V) in the top-right of every setup screen. Pure-PIL composition mirroring webApp/frontend/public/logo.svg — no cairosvg/rsvg dependency needed. Source asset: webApp/brand/IMG_2524-square900.jpg. Logo box went from a 300×92 wide placeholder to an 110×110 square on 13.3 and a 44×44 square on 7.3 — matches Matt's request for a 'nice square, readable rendition' and keeps a comfortable margin within each panel's header band. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
-22
@@ -111,30 +111,47 @@ def leave_qr_white(draw, qr_x, qr_y, qr_px):
|
||||
"""Blank the QR overlay region so firmware can write the real QR."""
|
||||
draw.rectangle([qr_x, qr_y, qr_x+qr_px-1, qr_y+qr_px-1], fill=WH)
|
||||
|
||||
# ── Logo placeholder ─────────────────────────────────────────────────────────
|
||||
# Top-right brand placeholder for both setup screens. When the real logo
|
||||
# lands, replace draw_logo_placeholder() with a paste of assets/logo.png
|
||||
# (or similar) at the same coordinates so the layout stays stable.
|
||||
LOGO_W = 200
|
||||
LOGO_H = 40
|
||||
LOGO_X = 800 - LOGO_W - 16
|
||||
LOGO_Y = (52 - LOGO_H) // 2 # BAR_H=52
|
||||
# ── Logo ─────────────────────────────────────────────────────────────────────
|
||||
# Composed mirror of webApp/frontend/public/logo.svg, rendered in pure PIL.
|
||||
# See gen_screens_13e6.py for the full rationale.
|
||||
LOGO_SIDE = 44 # square; fits within BAR_H=52 with 4-px margin
|
||||
LOGO_X = 800 - LOGO_SIDE - 16
|
||||
LOGO_Y = (52 - LOGO_SIDE) // 2
|
||||
LOGO_SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"..", "..", "webApp", "brand",
|
||||
"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))
|
||||
od = ImageDraw.Draw(overlay)
|
||||
for y in range(size):
|
||||
t = y / max(1, size - 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)))
|
||||
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)
|
||||
parts = [("We", WH), ("V", YL), ("isto", WH)]
|
||||
widths = [draw.textbbox((0, 0), t, font=font)[2] for t, _ in parts]
|
||||
total_w = sum(widths)
|
||||
x = (size - total_w) // 2
|
||||
y = int(0.547 * size) - font_px // 2 - 2
|
||||
for (t, fill), w in zip(parts, widths):
|
||||
draw.text((x, y), t, font=font, fill=fill)
|
||||
x += w
|
||||
return bg
|
||||
|
||||
def draw_logo_placeholder(img):
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.rectangle([LOGO_X, LOGO_Y, LOGO_X + LOGO_W - 1, LOGO_Y + LOGO_H - 1], fill=WH)
|
||||
draw.rectangle([LOGO_X, LOGO_Y, LOGO_X + LOGO_W - 1, LOGO_Y + LOGO_H - 1],
|
||||
outline=BK, width=2)
|
||||
f_logo = ttf("DejaVuSans-Bold.ttf", 18)
|
||||
bb = draw.textbbox((0, 0), "WeVisto", font=f_logo)
|
||||
tw = bb[2] - bb[0]
|
||||
draw.text((LOGO_X + (LOGO_W - tw) // 2, LOGO_Y + 4), "WeVisto",
|
||||
font=f_logo, fill=BK)
|
||||
f_hint = ttf("DejaVuSans-Bold.ttf", 8)
|
||||
bb = draw.textbbox((0, 0), "PLACEHOLDER", font=f_hint)
|
||||
pw = bb[2] - bb[0]
|
||||
draw.text((LOGO_X + (LOGO_W - pw) // 2, LOGO_Y + LOGO_H - 12),
|
||||
"PLACEHOLDER", font=f_hint, fill=BK)
|
||||
img.paste(compose_logo(LOGO_SIDE), (LOGO_X, LOGO_Y))
|
||||
|
||||
|
||||
def wrap_to_width(draw, text, font, max_w):
|
||||
|
||||
+48
-26
@@ -147,34 +147,56 @@ def draw_header(draw, accent, header_text):
|
||||
draw.text((LEFT_PAD, 40), header_text, font=F_BAR, fill=BK)
|
||||
|
||||
|
||||
# ── Logo placeholder ─────────────────────────────────────────────────────────
|
||||
# Box dimensions for the top-right placeholder. When the real brand mark
|
||||
# lands, replace draw_logo_placeholder() with a paste of assets/logo.png
|
||||
# (or similar) at these same coordinates so the layout stays stable.
|
||||
LOGO_W = 300
|
||||
LOGO_H = 92
|
||||
LOGO_X = W - LOGO_W - LEFT_PAD # 864
|
||||
LOGO_Y = (HEADER_H - LOGO_H) // 2 # 19
|
||||
# ── Logo ─────────────────────────────────────────────────────────────────────
|
||||
# Composed mirror of webApp/frontend/public/logo.svg: harbour photo +
|
||||
# black-fade gradient + "WeVisto" wordmark (V in yellow). Rendered in
|
||||
# pure PIL because cairosvg / rsvg / imagemagick aren't installed on the
|
||||
# build host. Source photo lives in webApp/brand/.
|
||||
LOGO_SIDE = 110 # square; fits within HEADER_H=130 with padding
|
||||
LOGO_X = W - LOGO_SIDE - LEFT_PAD # 1054
|
||||
LOGO_Y = (HEADER_H - LOGO_SIDE) // 2 # 10
|
||||
LOGO_SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"..", "..", "webApp", "brand",
|
||||
"IMG_2524-square900.jpg")
|
||||
|
||||
def compose_logo(size):
|
||||
"""Returns a size×size RGB PIL image of the WeVisto wordmark over the
|
||||
Camogli harbour photo. Matches the SVG layout in
|
||||
webApp/frontend/public/logo.svg (320×320 viewBox)."""
|
||||
bg = Image.open(LOGO_SRC).convert("RGB").resize((size, size), Image.LANCZOS)
|
||||
|
||||
# Black overlay band: 45% opacity peak at 42-58% height, fading at edges.
|
||||
# Matches the linearGradient stops in the SVG.
|
||||
overlay = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
od = ImageDraw.Draw(overlay)
|
||||
for y in range(size):
|
||||
t = y / max(1, size - 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)))
|
||||
bg = Image.alpha_composite(bg.convert("RGBA"), overlay).convert("RGB")
|
||||
|
||||
# Wordmark: "We" white + "V" yellow + "isto" white, centred at y≈0.547.
|
||||
draw = ImageDraw.Draw(bg)
|
||||
font_px = max(10, int(62 * size / 320))
|
||||
font = ttf("DejaVuSans-Bold.ttf", font_px)
|
||||
parts = [("We", WH), ("V", YL), ("isto", WH)]
|
||||
widths = [draw.textbbox((0, 0), t, font=font)[2] for t, _ in parts]
|
||||
total_w = sum(widths)
|
||||
x = (size - total_w) // 2
|
||||
y = int(0.547 * size) - font_px // 2 - 4 # nudged up to match SVG
|
||||
for (t, fill), w in zip(parts, widths):
|
||||
draw.text((x, y), t, font=font, fill=fill)
|
||||
x += w
|
||||
return bg
|
||||
|
||||
def draw_logo_placeholder(img):
|
||||
"""White rounded-look box with 'WeVisto' brand text in the top right of
|
||||
the header. Stand-in for the real logo — same position will be reused."""
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.rectangle([LOGO_X, LOGO_Y, LOGO_X + LOGO_W - 1, LOGO_Y + LOGO_H - 1], fill=WH)
|
||||
draw.rectangle([LOGO_X, LOGO_Y, LOGO_X + LOGO_W - 1, LOGO_Y + LOGO_H - 1],
|
||||
outline=BK, width=4)
|
||||
|
||||
f_logo = ttf("DejaVuSans-Bold.ttf", 44)
|
||||
bb = draw.textbbox((0, 0), "WeVisto", font=f_logo)
|
||||
tw = bb[2] - bb[0]
|
||||
draw.text((LOGO_X + (LOGO_W - tw) // 2, LOGO_Y + 12), "WeVisto",
|
||||
font=f_logo, fill=BK)
|
||||
|
||||
f_hint = ttf("DejaVuSans-Bold.ttf", 14)
|
||||
bb = draw.textbbox((0, 0), "PLACEHOLDER", font=f_hint)
|
||||
pw = bb[2] - bb[0]
|
||||
draw.text((LOGO_X + (LOGO_W - pw) // 2, LOGO_Y + LOGO_H - 22),
|
||||
"PLACEHOLDER", font=f_hint, fill=BK)
|
||||
"""Paste the composed WeVisto logo in the top-right of the header."""
|
||||
img.paste(compose_logo(LOGO_SIDE), (LOGO_X, LOGO_Y))
|
||||
|
||||
|
||||
def draw_divider(draw):
|
||||
|
||||
Reference in New Issue
Block a user