Files
tick-stock-panel/packaging/generate_icon.py
T
shy3130 5721f1fe93 feat: Windows 打包改为 Inno Setup 安装包 (Setup.exe)
- 新增 packaging/tickflow.iss: Inno Setup 脚本
  · 用户目录安装 (不弹 UAC), 桌面+开始菜单快捷方式
  · 卸载时询问是否删除用户数据
  · 内置简中语言包 (ChineseSimplified.isl, 官方仓库获取)
- 新增 packaging/generate_icon.py + icon.ico: 应用图标
  · 与 favicon.svg/Logo.tsx 一致 (紫色方括号 + K线)
  · 用 Pillow 绘制, 多尺寸 (16/32/48/64/128/256)
- spec 加 icon: exe 图标 = 安装包图标 = 快捷方式图标
- release.yml: Windows 步骤改为 choco install innosetup + ISCC 编译
- 本地验证通过: 安装→快捷方式→启动→health 0.1.35 全 OK
2026-06-23 22:26:31 +08:00

95 lines
2.9 KiB
Python

"""生成应用图标 .ico — 与前端 favicon.svg / Logo.tsx 保持一致。
几何 (32x32 基准, 按比例缩放到各尺寸):
- 紫色方括号 [ ] (brand color #5B21B6)
- 中间一根带 wick 的 K 线 body (实心矩形)
用途:
- exe 图标 (PyInstaller icon=)
- 安装包图标 (Inno Setup SetupIconFile=)
- 快捷方式图标
运行: python packaging/generate_icon.py
产物: packaging/icon.ico (含 16/32/48/64/128/256 多尺寸)
"""
from __future__ import annotations
from pathlib import Path
from PIL import Image, ImageDraw
# 品牌色 (与 frontend/public/favicon.svg 一致)
BRAND = (91, 33, 182, 255) # #5B21B6
BRAND_DIM = (91, 33, 182, 166) # stroke-opacity 0.65 (wick)
OUTPUT = Path(__file__).parent / "icon.ico"
def draw_logo(size: int) -> Image.Image:
"""按 favicon.svg 的几何绘制指定尺寸的图标。
坐标基于 32x32 viewBox, 按比例缩放到 size。
"""
# 用 4x 超采样再缩放, 抗锯齿
scale = max(4, 256 // size)
s = size * scale
img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
# 把 32-viewBox 坐标缩放到 s
def x(v: float) -> float:
return v * s / 32
def y(v: float) -> float:
return v * s / 32
sw = 2.5 # 括号线宽
bw = 2.0 # wick 线宽
w = max(1, int(round(sw * scale)))
ww = max(1, int(round(bw * scale)))
# 左方括号 [ : M10,4 L4,4 L4,28 L10,28
d.line([(x(10), y(4)), (x(4), y(4))], fill=BRAND, width=w)
d.line([(x(4), y(4)), (x(4), y(28))], fill=BRAND, width=w)
d.line([(x(4), y(28)), (x(10), y(28))], fill=BRAND, width=w)
# 括号转角处用圆点补强 (line 转角有缝隙)
r = w // 2 + 1
for cx, cy in [(x(4), y(4)), (x(4), y(28))]:
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=BRAND)
# 右方括号 ] : M22,4 L28,4 L28,28 L22,28
d.line([(x(22), y(4)), (x(28), y(4))], fill=BRAND, width=w)
d.line([(x(28), y(4)), (x(28), y(28))], fill=BRAND, width=w)
d.line([(x(28), y(28)), (x(22), y(28))], fill=BRAND, width=w)
for cx, cy in [(x(28), y(4)), (x(28), y(28))]:
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=BRAND)
# K 线 wick: x=16, y=7..25
d.line([(x(16), y(7)), (x(16), y(25))], fill=BRAND_DIM, width=ww)
# K 线 body: rect (12.5, 11.5) 7x11, rx=0.8
bx0, by0 = x(12.5), y(11.5)
bx1, by1 = x(12.5) + x(7), y(11.5) + y(11)
radius = x(0.8)
d.rounded_rectangle([bx0, by0, bx1, by1], radius=radius, fill=BRAND)
# 缩放到目标尺寸 (高质量抗锯齿)
return img.resize((size, size), Image.LANCZOS)
def main() -> None:
sizes = [16, 32, 48, 64, 128, 256]
images = [draw_logo(s) for s in sizes]
# 第一个作为主图, 其余作为变体
images[0].save(
OUTPUT,
format="ICO",
sizes=[(s, s) for s in sizes],
append_images=images[1:],
)
print(f"生成图标: {OUTPUT} (尺寸 {sizes})")
if __name__ == "__main__":
main()