Files
mechanic-pwa/driver-pwa/frontend/scripts/gen-icons.py
T
tremble7681andClaude Opus 5 388c8e7343 feat(pwa): иконка приложения — фирменный знак парка
Была нарисованная «карта» на кремовом фоне из первых набросков. Теперь знак
«Премиум Парк» из фирменного набора, на белой подложке сайта.

Собирается из вектора скриптом scripts/gen-icons.py (три пути, команды M/L/H/V/C,
сглаживание сверхдискретизацией ×4) — прежний gen-icons.mjs рисовал плитку
вручную и к логотипу отношения не имел. Готовый PNG из набора не подошёл: знак
внутри него занимает 264×195 из 512, и на иконке он бы размылился.

Отдельная maskable-версия с полями: Android режет иконку по кругу, и знак без
запаса теряет края. Ссылка apple-touch-icon в index.html — iOS манифест не
читает и берёт иконку «на экран Домой» только оттуда.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 18:08:00 +10:00

93 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Иконки приложения из фирменного знака «Премиум Парк».
Знак — вектор (assets/logos/premium_park_favicon_pack/favicon.svg, поле 64×64,
чёрные штрихи + янтарная дуга). Растеризуем его сами: путей всего три, команды —
только M/L/H/V/C/Z, а тянуть в проект зависимость ради этого не хочется.
Сглаживание — сверхдискретизацией ×4: рисуем крупнее и уменьшаем. Так края
получаются чище, чем у любой заливки «в лоб».
Запуск (нужен Pillow):
python scripts/gen-icons.py
"""
from pathlib import Path
import re
from PIL import Image, ImageDraw
SVG = Path(r"C:\VibeCoding\assets\logos\premium_park_favicon_pack\favicon.svg")
OUT = Path(__file__).resolve().parent.parent / "public" / "icons"
BG = (255, 255, 255, 255) # --s-paper сайта: знак нарисован для светлой подложки
SS = 4 # кратность сверхдискретизации
# Полезная часть знака внутри поля 64×64 (замерено по путям).
MARK = (11.0, 8.0, 59.4, 43.7)
def _curve(p0, p1, p2, p3, steps=24):
for i in range(1, steps + 1):
t = i / steps
u = 1 - t
yield (u*u*u*p0[0] + 3*u*u*t*p1[0] + 3*u*t*t*p2[0] + t*t*t*p3[0],
u*u*u*p0[1] + 3*u*u*t*p1[1] + 3*u*t*t*p2[1] + t*t*t*p3[1])
def flatten(d: str) -> list[tuple[float, float]]:
"""Путь SVG → многоугольник. Поддержаны M, L, H, V, C, Z — больше в знаке нет."""
tokens = re.findall(r"[MLHVCZmlhvcz]|-?\d*\.?\d+", d)
pts: list[tuple[float, float]] = []
cur = (0.0, 0.0)
cmd = ""
i = 0
while i < len(tokens):
t = tokens[i]
if t.isalpha():
cmd = t
i += 1
continue
n = lambda k: float(tokens[i + k])
if cmd in "ML":
cur = (n(0), n(1)); pts.append(cur); i += 2
elif cmd == "H":
cur = (n(0), cur[1]); pts.append(cur); i += 1
elif cmd == "V":
cur = (cur[0], n(0)); pts.append(cur); i += 1
elif cmd == "C":
p1, p2, p3 = (n(0), n(1)), (n(2), n(3)), (n(4), n(5))
pts.extend(_curve(cur, p1, p2, p3)); cur = p3; i += 6
else:
i += 1
return pts
def paths() -> list[tuple[list[tuple[float, float]], str]]:
svg = SVG.read_text(encoding="utf-8")
out = []
for d, fill in re.findall(r'<path d="([^"]+)"\s+fill="([^"]+)"', svg):
out.append((flatten(d), fill))
return out
def compose(size: int, inset: float) -> Image.Image:
"""inset — доля стороны под знак. Для maskable нужна безопасная зона:
Android режет иконку по кругу, и знак без запаса теряет края."""
big = size * SS
img = Image.new("RGBA", (big, big), BG)
draw = ImageDraw.Draw(img)
x0, y0, x1, y1 = MARK
scale = min(big * inset / (x1 - x0), big * inset / (y1 - y0))
dx = (big - (x1 - x0) * scale) / 2 - x0 * scale
dy = (big - (y1 - y0) * scale) / 2 - y0 * scale
for pts, fill in paths():
draw.polygon([(x * scale + dx, y * scale + dy) for x, y in pts], fill=fill)
return img.resize((size, size), Image.LANCZOS)
if __name__ == "__main__":
OUT.mkdir(parents=True, exist_ok=True)
for name, size, inset in (("icon-192.png", 192, 0.82),
("icon-512.png", 512, 0.82),
("icon-maskable-512.png", 512, 0.60),
("apple-touch-icon.png", 180, 0.82)):
compose(size, inset).save(OUT / name)
print("собрано", name)