Files
crypto_okx/scripts/generate_pwa_icons.py
2026-08-16 16:38:37 +08:00

120 lines
3.8 KiB
Python

"""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 = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" rx="112" fill="#0b0d14"/>
<rect x="36" y="36" width="440" height="440" rx="88" fill="#121828" stroke="#ffffff" stroke-width="10"/>
<circle cx="256" cy="236" r="108" fill="none" stroke="#4285f4" stroke-width="22"/>
<path d="M196 176 L316 296 M316 176 L196 296" stroke="#ffffff" stroke-width="28" stroke-linecap="round"/>
<rect x="186" y="392" width="140" height="18" rx="9" fill="#7b42ff"/>
</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()