Files
ctbjrj/.agents/skills/winforms-designer-package/references/icon-generation.md
T

170 lines
7.0 KiB
Markdown
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.
# 图标生成(Pillow 纯 Python 绘制 → 多尺寸 ICO)
用 Pillow 直接绘制图标 → 多尺寸 `.ico`,嵌入 exe。**纯 Python,零原生依赖**(不依赖 cairo/svglib,那些需要 `libcairo-2.dll`,Windows 上通常没有)。
> 本模板在 GCS 铜排壳体计算项目中验证过,产出 `app.ico`(16/32/48/64/128/256 六尺寸),通过 `/win32icon:app.ico` 嵌入 exe,并在所有窗体构造函数里 `ExtractAssociatedIcon` 设置窗口/任务栏图标。
## 为什么不用 SVG→ICO
常规路径 cairosvg / svglib+reportlab 都依赖原生 `libcairo-2.dll`,Windows 默认没有,装起来麻烦。**Pillow 直接画** 是零依赖、最稳的方案,缺点是只能画几何图形(渐变、滤镜要自己用代码模拟)。
## 核心思路
1. **高分辨率绘制**:在 512×512 画布上画完整图标(细节清晰)
2. **Pillow 自动缩放**:保存 ICO 时传 `sizes=[(16,16),(32,32),...]`,Pillow 自动从大图缩出各尺寸
3. **模拟渐变**:逐行/逐列插值颜色(`lerp_color`),Pillow 没有原生渐变填充
4. **圆角矩形**:用临时 mask 层 `rounded_rectangle` 做遮罩,再 paste
## 完整模板(铜排+闪电图标,可直接改色改形)
```python
# -*- coding: utf-8 -*-
"""用 Pillow 绘制图标 -> 多尺寸 app.ico。纯 Python,零原生依赖。
设计:深蓝→青蓝渐变圆角背景 + 三根铜排 + 接线端子 + 闪电(电流)。"""
import os
from PIL import Image, ImageDraw
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ICO_PATH = os.path.join(SCRIPT_DIR, "app.ico")
SIZES = [16, 32, 48, 64, 128, 256]
RENDER_SIZE = 512 # 高分辨率绘制
def lerp_color(c1, c2, t):
"""线性插值两个 (r,g,b) 颜色,t∈[0,1]。"""
return (
int(c1[0] + (c2[0] - c1[0]) * t),
int(c1[1] + (c2[1] - c1[1]) * t),
int(c1[2] + (c2[2] - c1[2]) * t),
)
def draw_gradient_rect(img, box, c_top, c_mid, c_bot, radius):
"""带垂直渐变的圆角矩形。用逐行填充近似渐变 + 圆角 mask。"""
x0, y0, x1, y1 = box
w, h = x1 - x0, y1 - y0
grad = Image.new("RGBA", (w, h), (0, 0, 0, 0))
gp = grad.load()
for y in range(h):
t = y / max(h - 1, 1)
if t < 0.55:
c = lerp_color(c_top, c_mid, t / 0.55)
else:
c = lerp_color(c_mid, c_bot, (t - 0.55) / 0.45)
for x in range(w):
gp[x, y] = (c[0], c[1], c[2], 255)
mask = Image.new("L", (w, h), 0)
ImageDraw.Draw(mask).rounded_rectangle([0, 0, w - 1, h - 1], radius=radius, fill=255)
img.paste(grad, (x0, y0), mask)
def draw_copper_bar(img, box, radius):
"""画一根铜排:铜橙→金黄渐变 + 左侧高光条。box=(x,y,w,h)。"""
bx, by, bw, bh = box
c_top, c_mid, c_bot = (255, 210, 127), (232, 163, 61), (181, 101, 29)
grad = Image.new("RGBA", (bw, bh), (0, 0, 0, 0))
gp = grad.load()
for y in range(bh):
t = y / max(bh - 1, 1)
c = lerp_color(c_top, c_mid, t / 0.45) if t < 0.45 else lerp_color(c_mid, c_bot, (t - 0.45) / 0.55)
for x in range(bw):
gp[x, y] = (c[0], c[1], c[2], 255)
mask = Image.new("L", (bw, bh), 0)
ImageDraw.Draw(mask).rounded_rectangle([0, 0, bw - 1, bh - 1], radius=radius, fill=255)
img.paste(grad, (bx, by), mask)
# 高光条
shine_w = max(bw // 4, 6)
shine = Image.new("RGBA", (shine_w, bh - 10), (0, 0, 0, 0))
sp = shine.load()
for y in range(shine.height):
a = int(180 * (1 - y / shine.height))
for x in range(shine_w):
sp[x, y] = (255, 255, 255, a)
smask = Image.new("L", (shine_w, shine.height), 0)
ImageDraw.Draw(smask).rounded_rectangle([0, 0, shine_w - 1, shine.height - 1], radius=max(radius - 2, 2), fill=255)
img.paste(shine, (bx + bw // 6, by + 5), smask)
def draw_lightning(img, cx, cy, scale):
"""画一道闪电。金黄色,带白色描边。"""
pts = [(4, -34), (-10, 4), (2, 4), (-8, 30), (16, -4), (4, -4)]
pts = [(cx + p[0] * scale, cy + p[1] * scale) for p in pts]
d = ImageDraw.Draw(img)
for dx, dy in [(-2, 0), (2, 0), (0, -2), (0, 2), (-2, -2), (2, 2), (-2, 2), (2, -2)]:
d.polygon([(p[0] + dx, p[1] + dy) for p in pts], fill=(255, 255, 255, 255))
d.polygon(pts, fill=(255, 224, 102, 255))
def draw_terminal(img, cx, cy, r):
"""铜排顶部接线端子。"""
d = ImageDraw.Draw(img)
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=(244, 228, 188, 255),
outline=(139, 90, 43, 255), width=2)
d.ellipse([cx - r // 3, cy - r // 3, cx + r // 3, cy + r // 3], fill=(58, 36, 16, 255))
def draw_icon(size):
s = size
img = Image.new("RGBA", (s, s), (0, 0, 0, 0))
margin = int(s * 0.03)
radius = int(s * 0.17)
# 背景渐变
draw_gradient_rect(img, (margin, margin, s - margin, s - margin),
c_top=(15, 61, 94), c_mid=(27, 108, 168), c_bot=(42, 157, 143),
radius=radius)
# 三根铜排(长短递减)
bar_w = int(s * 0.133)
bar_radius = max(int(s * 0.023), 3)
bars = [
(int(s * 0.227), int(s * 0.203), int(s * 0.594)), # 最长
(int(s * 0.434), int(s * 0.281), int(s * 0.516)),
(int(s * 0.641), int(s * 0.375), int(s * 0.422)), # 最短
]
term_r = int(s * 0.027)
for bx, by, bh in bars:
draw_terminal(img, bx + bar_w // 2, by - term_r + 2, term_r)
draw_copper_bar(img, (bx, by, bar_w, bh), bar_radius)
# 闪电
draw_lightning(img, int(s * 0.512), int(s * 0.613), s / 512.0)
return img
def main():
hi = draw_icon(RENDER_SIZE)
hi.save(ICO_PATH, format="ICO", sizes=[(sz, sz) for sz in SIZES])
print("已生成:", ICO_PATH, "尺寸:", SIZES)
if __name__ == "__main__":
main()
```
## 改造成其他图标
要换图标主题时,改 3 处:
1. **背景色**:`draw_gradient_rect` 的 `c_top/c_mid/c_bot`(RGB 三元组)
2. **主体元素**:替换 `draw_copper_bar` / `draw_lightning` 为你要画的元素(用 `ImageDraw` 的 ellipse/rectangle/polygon/line)
3. **布局坐标**:`draw_icon` 里各元素的 x/y/w/h(按 `s * 比例` 计算,这样任意尺寸都自适应)
## 嵌入 exe 的两步(配合 SKILL.md 主文档)
**1) 编译加参数**:`build.rsp` 或 csc 命令加 `/win32icon:app.ico`
**2) 窗体加图标加载代码**(exe 嵌入图标后,Form 默认不自动用它):
```csharp
private void LoadAppIcon()
{
try { this.Icon = System.Drawing.Icon.ExtractAssociatedIcon(
System.Windows.Forms.Application.ExecutablePath); }
catch { }
}
// 在每个窗体构造函数 InitializeComponent() 之后调用 LoadAppIcon();
```
这样 exe 文件图标、窗口标题栏、任务栏、Alt+Tab 都显示这个图标。
## 小尺寸清晰度技巧
- 16×16 这种小尺寸,复杂图案会糊成一片。设计时**主体元素要大、要少**(1~3 个),避免细节。
- Pillow 从 512 缩到 16 用的是抗锯齿,比手画 16 清晰。如果还是糊,可以 `draw_icon(16)` 单独画一版简化图标(只保留最核心的轮廓)再合并到 ICO。