"""生成 PWA 图标。 设计:天蓝色底 + 三根白色半透明圆角竖条(看板的三列), 每根竖条内从上往下堆叠若干不透明白色卡片,数量各不相同。 注意:Pillow 的 ImageDraw 不做 alpha 混合(半透明填充会直接覆盖像素), 因此所有半透明元素必须画在独立图层上,再用 alpha_composite 合成。 Maskable 版本把内容缩到安全区(中心 80%)内,避免被系统裁成圆形时切掉。 """ from __future__ import annotations import os from PIL import Image, ImageDraw OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "public") BACKGROUND = (2, 132, 199, 255) # sky-600 TRACK = (255, 255, 255, 70) # 半透明的列轨道 CARD = (255, 255, 255, 255) # 每列的卡片高度(相对内容区高度的比例),从上往下堆叠 COLUMNS = [ [0.22, 0.15], [0.16, 0.22, 0.13], [0.14], ] SUPERSAMPLE = 4 def draw_icon(size: int, content_ratio: float) -> Image.Image: """content_ratio: 内容占据画布的比例(1.0 表示铺满)""" canvas = size * SUPERSAMPLE background = Image.new("RGBA", (canvas, canvas), BACKGROUND) layer = Image.new("RGBA", (canvas, canvas), (0, 0, 0, 0)) draw = ImageDraw.Draw(layer) content = canvas * content_ratio offset = (canvas - content) / 2 gap = content * 0.07 column_width = (content - gap * (len(COLUMNS) - 1)) / len(COLUMNS) track_radius = max(2, int(column_width * 0.24)) for index, cards in enumerate(COLUMNS): left = offset + index * (column_width + gap) top = offset bottom = offset + content draw.rounded_rectangle( [left, top, left + column_width, bottom], radius=track_radius, fill=TRACK, ) pad = column_width * 0.17 card_width = column_width - pad * 2 card_gap = content * 0.035 cursor = top + pad for ratio in cards: card_height = content * ratio draw.rounded_rectangle( [left + pad, cursor, left + pad + card_width, cursor + card_height], radius=max(2, int(track_radius * 0.5)), fill=CARD, ) cursor += card_height + card_gap image = Image.alpha_composite(background, layer) return image.resize((size, size), Image.LANCZOS) def main() -> None: os.makedirs(OUT_DIR, exist_ok=True) targets = [ ("pwa-192x192.png", 192, 0.88), ("pwa-512x512.png", 512, 0.88), ("maskable-512x512.png", 512, 0.60), # 留出安全区 ("apple-touch-icon.png", 180, 0.88), ] for name, size, ratio in targets: path = os.path.join(OUT_DIR, name) draw_icon(size, ratio).convert("RGB").save(path, "PNG", optimize=True) print(f"generated {name} ({size}x{size})") if __name__ == "__main__": main()