"""Generate PWA / favicon assets under static/icons.""" from __future__ import annotations import math import os from PIL import Image, ImageDraw BASE = os.path.join(os.path.dirname(__file__), "..", "static", "icons") BASE = os.path.abspath(BASE) BG = (11, 13, 20, 255) PANEL = (18, 24, 40, 255) ACCENT = (66, 133, 244, 255) ACCENT2 = (123, 66, 255, 255) WHITE = (255, 255, 255, 255) def thick_line(draw: ImageDraw.ImageDraw, p1, p2, t: float, color) -> None: x1, y1 = p1 x2, y2 = p2 dx, dy = x2 - x1, y2 - y1 length = math.hypot(dx, dy) or 1.0 ux, uy = -dy / length, dx / length hx, hy = ux * t / 2, uy * t / 2 draw.polygon( [ (x1 + hx, y1 + hy), (x2 + hx, y2 + hy), (x2 - hx, y2 - hy), (x1 - hx, y1 - hy), ], fill=color, ) def make_icon(size: int, *, maskable: bool = False) -> Image.Image: im = Image.new("RGBA", (size, size), (0, 0, 0, 0)) d = ImageDraw.Draw(im) pad = int(size * (0.08 if maskable else 0.0)) outer = [pad, pad, size - 1 - pad, size - 1 - pad] r = int((size - 2 * pad) * 0.22) d.rounded_rectangle(outer, radius=r, fill=BG) inset = int(size * 0.07) inner = [pad + inset, pad + inset, size - 1 - pad - inset, size - 1 - pad - inset] r2 = max(8, int((size - 2 * pad - 2 * inset) * 0.18)) d.rounded_rectangle(inner, radius=r2, fill=PANEL, outline=WHITE, width=max(2, size // 64)) cx = cy = size // 2 ring_r = int(size * 0.22) stroke = max(3, size // 28) bbox = [ cx - ring_r, cy - ring_r - int(size * 0.02), cx + ring_r, cy + ring_r - int(size * 0.02), ] d.ellipse(bbox, outline=ACCENT, width=stroke) arm = int(size * 0.11) thick = max(3, size // 26) ox = cx oy = cy - int(size * 0.02) thick_line(d, (ox - arm, oy - arm), (ox + arm, oy + arm), thick, WHITE) thick_line(d, (ox + arm, oy - arm), (ox - arm, oy + arm), thick, WHITE) bar_h = max(3, size // 28) bar_w = int(size * 0.28) by = int(size * (0.74 if maskable else 0.78)) d.rounded_rectangle( [cx - bar_w // 2, by, cx + bar_w // 2, by + bar_h], radius=max(1, bar_h // 2), fill=ACCENT2, ) return im def main() -> None: os.makedirs(BASE, exist_ok=True) mapping = { "icon-16.png": 16, "icon-32.png": 32, "apple-touch-icon.png": 180, "icon-192.png": 192, "icon-512.png": 512, } for name, sz in mapping.items(): path = os.path.join(BASE, name) make_icon(sz).save(path, format="PNG", optimize=True) print("wrote", name, os.path.getsize(path)) for name, sz in (("icon-192-maskable.png", 192), ("icon-512-maskable.png", 512)): path = os.path.join(BASE, name) make_icon(sz, maskable=True).save(path, format="PNG", optimize=True) print("wrote", name, os.path.getsize(path)) ico_sizes = [16, 32, 48] frames = [make_icon(s) for s in ico_sizes] ico_path = os.path.join(BASE, "favicon.ico") frames[0].save( ico_path, format="ICO", sizes=[(s, s) for s in ico_sizes], append_images=frames[1:], ) print("wrote favicon.ico", os.path.getsize(ico_path)) svg = """ """ with open(os.path.join(BASE, "icon.svg"), "w", encoding="utf-8") as f: f.write(svg) print("wrote icon.svg") if __name__ == "__main__": main()