b733e551a0
Add scripts/normalize_ambiguous_unicode.py; fix corrupted patch_instance_theme_templates.py. Preserves curly quotes in string literals; removes Git homoglyph warnings on .env.example. Co-authored-by: Cursor <cursoragent@cursor.com>
113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""生成品牌 PNG/ICO(Pillow),供 Chrome 快捷方式与 manifest 使用."""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import shutil
|
|
|
|
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
OUT = os.path.join(REPO, "brand", "icons")
|
|
|
|
BG = (12, 16, 25, 255)
|
|
PANEL = (20, 27, 45, 255)
|
|
CYAN = (34, 211, 238, 255)
|
|
GREEN = (52, 211, 153, 255)
|
|
RED = (248, 113, 113, 255)
|
|
|
|
|
|
def _lerp(c1: tuple[int, ...], c2: tuple[int, ...], t: float) -> tuple[int, int, int, int]:
|
|
t = max(0.0, min(1.0, t))
|
|
return tuple(int(c1[i] + (c2[i] - c1[i]) * t) for i in range(4)) # type: ignore
|
|
|
|
|
|
def _rounded_rect(draw, box, radius: int, fill) -> None:
|
|
draw.rounded_rectangle(box, radius=radius, fill=fill)
|
|
|
|
|
|
def render_icon(size: int):
|
|
from PIL import Image, ImageDraw
|
|
|
|
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
|
draw = ImageDraw.Draw(img)
|
|
m = max(6, size // 12)
|
|
r = max(8, size // 6)
|
|
_rounded_rect(draw, (m, m, size - m, size - m), r, BG)
|
|
inner = m + max(2, size // 28)
|
|
_rounded_rect(draw, (inner, inner, size - inner, size - inner), max(6, r - 4), PANEL)
|
|
|
|
# 渐变描边(四角采样)
|
|
border = max(2, size // 42)
|
|
for i in range(border):
|
|
t0 = i / max(1, border - 1)
|
|
for x in range(inner, size - inner):
|
|
t = (x - inner) / max(1, size - 2 * inner)
|
|
col = _lerp(CYAN, GREEN, (t + t0) * 0.5)
|
|
draw.point((x, inner + i), fill=col)
|
|
draw.point((x, size - inner - 1 - i), fill=col)
|
|
for y in range(inner, size - inner):
|
|
t = (y - inner) / max(1, size - 2 * inner)
|
|
col = _lerp(CYAN, GREEN, (t + t0) * 0.5)
|
|
draw.point((inner + i, y), fill=col)
|
|
draw.point((size - inner - 1 - i, y), fill=col)
|
|
|
|
def sx(v: float) -> int:
|
|
return int(v * size / 512)
|
|
|
|
def sy(v: float) -> int:
|
|
return int(v * size / 512)
|
|
|
|
# 趋势线
|
|
pts = [(120, 320), (200, 248), (280, 272), (392, 168)]
|
|
scaled = [(sx(x), sy(y)) for x, y in pts]
|
|
draw.line(scaled, fill=CYAN, width=max(2, size // 26), joint="curve")
|
|
ex, ey = scaled[-1]
|
|
draw.ellipse(
|
|
(ex - size // 28, ey - size // 28, ex + size // 28, ey + size // 28),
|
|
fill=GREEN,
|
|
)
|
|
|
|
# 蜡烛
|
|
def candle(cx, top, bottom, body_top, body_bottom, color):
|
|
w = max(1, size // 64)
|
|
bh = max(2, size // 32)
|
|
draw.line((cx, top, cx, bottom), fill=color, width=w)
|
|
draw.rounded_rectangle(
|
|
(cx - bh, body_top, cx + bh, body_bottom),
|
|
radius=max(1, bh // 3),
|
|
fill=color,
|
|
)
|
|
|
|
candle(sx(182), sy(248), sy(340), sy(268), sy(332), RED)
|
|
candle(sx(282), sy(200), sy(340), sy(220), sy(316), GREEN)
|
|
|
|
return img
|
|
|
|
|
|
def main() -> None:
|
|
from PIL import Image
|
|
|
|
os.makedirs(OUT, exist_ok=True)
|
|
shutil.copy2(os.path.join(REPO, "brand", "icon.svg"), os.path.join(OUT, "icon.svg"))
|
|
|
|
sizes = [16, 32, 48, 180, 192, 512]
|
|
images: dict[int, Image.Image] = {}
|
|
for sz in sizes:
|
|
im = render_icon(sz)
|
|
images[sz] = im
|
|
name = "apple-touch-icon.png" if sz == 180 else f"icon-{sz}.png"
|
|
im.save(os.path.join(OUT, name), format="PNG", optimize=True)
|
|
|
|
ico_sizes = [16, 32, 48]
|
|
ico_imgs = [images[s] for s in ico_sizes]
|
|
ico_imgs[0].save(
|
|
os.path.join(OUT, "favicon.ico"),
|
|
format="ICO",
|
|
sizes=[(s, s) for s in ico_sizes],
|
|
append_images=ico_imgs[1:],
|
|
)
|
|
print(f"DONE {OUT}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|