"""按 Web Logo(icons.tsx 的 )重绘 PWA 图标:靛蓝底 + 三条白线 + 一点淡紫。 用法:python tools/make-icons.py 输出覆盖 web/public/icons/*.png(与 SVG 同一套比例:64 画布,rx=18,线宽 4,点半径 6)。 """ from __future__ import annotations from pathlib import Path from PIL import Image, ImageDraw ROOT = Path(__file__).resolve().parents[1] OUT = ROOT / "web" / "public" / "icons" BG = "#4F46E5" LINE = "#FFFFFF" DOT = "#C7D2FE" SS = 8 # 超采样倍数,边缘才不毛 def draw(size: int, *, rounded: bool, inset: float = 0.0) -> Image.Image: """在 size×size 上画 logo。inset 是安全区留白比例(maskable 图标要留)。""" big = size * SS img = Image.new("RGBA", (big, big), (0, 0, 0, 0)) d = ImageDraw.Draw(img) if rounded: d.rounded_rectangle([0, 0, big - 1, big - 1], radius=big * 18 / 64, fill=BG) else: d.rectangle([0, 0, big, big], fill=BG) # 64 画布坐标 → 像素;inset 让图形整体缩小居中(给 maskable 留安全区) scale = big / 64 * (1 - 2 * inset) off = big * inset def box(x: float, y: float) -> tuple[float, float]: return off + x * scale, off + y * scale stroke = max(1, round(4 * scale)) for y, x2 in ((23, 46), (32, 37), (41, 30)): x1, yy = box(18, y) d.line([x1, yy, *box(x2, y)], fill=LINE, width=stroke) # 圆头:PIL 的 line 没有 cap,用小圆补两端 r = stroke / 2 for cx, cy in ((x1, yy), box(x2, y)): d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=LINE) cx, cy = box(46, 41) r = 6 * scale d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=DOT) return img.resize((size, size), Image.LANCZOS) def main() -> None: OUT.mkdir(parents=True, exist_ok=True) # 圆角版:favicon 之外的常规 PWA 图标 draw(192, rounded=True).save(OUT / "icon-192.png") draw(512, rounded=True).save(OUT / "icon-512.png") # iOS 会自己裁圆角,底图必须是方的 draw(180, rounded=False).save(OUT / "apple-touch-icon.png") # maskable:图形缩进 12% 安全区 draw(512, rounded=False, inset=0.12).save(OUT / "maskable-512.png") # 文档里引用的安卓图标预览 preview = ROOT / "2.Design" / "screens" / "android-icon-512.png" preview.parent.mkdir(parents=True, exist_ok=True) draw(512, rounded=True).save(preview) for p in sorted(OUT.glob("*.png")): print(p.relative_to(ROOT), Image.open(p).size) print(preview.relative_to(ROOT), Image.open(preview).size) if __name__ == "__main__": main()