"""Иконки приложения из фирменного знака «Премиум Парк». Знак — вектор (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' 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)