tarinai/scripts/generate_item_icons.py
2026-06-21 16:26:12 +09:00

81 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Generate UI icons for item designs that must match js/items.js.
The game is buildless, but these icons are generated snapshots of the field
appearance. Do not invent separate tool-UI-only designs.
"""
from __future__ import annotations
from pathlib import Path
import math
from PIL import Image, ImageDraw
ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / "assets" / "ui"
def save_icon(name: str, draw_fn) -> None:
im = Image.new("RGBA", (96, 96), (0, 0, 0, 0))
d = ImageDraw.Draw(im, "RGBA")
draw_fn(d)
im.save(OUT / name, "WEBP", lossless=True, quality=95)
def draw_drop(fill, stroke, highlight):
def fn(d: ImageDraw.ImageDraw) -> None:
d.ellipse([18, 60, 78, 74], fill=(60, 45, 35, 32))
# Same horizontal oval as fallen water/zunda/mercury item sprites; rain overlay is separate diagonal lines.
d.ellipse([12, 33, 84, 63], fill=fill, outline=stroke, width=4)
d.ellipse([24, 38, 45, 48], fill=highlight)
return fn
def draw_laxative(d: ImageDraw.ImageDraw) -> None:
d.rounded_rectangle([14, 28, 82, 68], radius=15, fill=(244, 212, 154, 255), outline=(98, 61, 29, 235), width=4)
# Zunchi mound motif inside the tablet.
for box in ([26, 47, 44, 60], [38, 36, 60, 53], [54, 47, 74, 61], [46, 29, 61, 41]):
d.ellipse(box, fill=(96, 70, 38, 230), outline=(56, 39, 22, 130), width=2)
d.arc([22, 27, 72, 50], 205, 330, fill=(255, 255, 255, 135), width=3)
def star_points(cx: float, cy: float, r1: float, r2: float, n: int = 5, rot: float = -math.pi / 2):
return [(cx + math.cos(rot + i * math.pi / n) * (r1 if i % 2 == 0 else r2), cy + math.sin(rot + i * math.pi / n) * (r1 if i % 2 == 0 else r2)) for i in range(n * 2)]
def draw_mystery(d: ImageDraw.ImageDraw) -> None:
d.ellipse([18, 23, 78, 73], fill=(243, 236, 255, 255), outline=(91, 55, 150, 240), width=5)
try:
from PIL import ImageFont
font = ImageFont.truetype("DejaVuSans-Bold.ttf", 44)
except Exception:
font = None
d.text((48, 48), "?", anchor="mm", fill=(91, 55, 150, 245), font=font)
d.arc([25, 24, 74, 54], 205, 330, fill=(255, 255, 255, 135), width=3)
def draw_bullet(d: ImageDraw.ImageDraw) -> None:
d.ellipse([20, 62, 76, 77], fill=(60, 45, 35, 38))
pts = [(76, 48), (60, 28), (27, 28), (16, 35), (16, 61), (27, 68), (60, 68)]
d.polygon(pts, fill=(244, 196, 106, 255), outline=(87, 51, 22, 240))
d.line(pts + [pts[0]], fill=(87, 51, 22, 240), width=4)
d.rectangle([16, 35, 29, 61], fill=(176, 111, 35, 255), outline=(87, 51, 22, 220), width=3)
d.line([(34, 36), (64, 42)], fill=(255, 237, 176, 170), width=4)
d.line([(34, 60), (58, 56)], fill=(142, 82, 30, 110), width=3)
def draw_sleep(d: ImageDraw.ImageDraw) -> None:
d.ellipse([17, 28, 79, 68], fill=(235, 228, 255, 255), outline=(74, 60, 144, 230), width=4)
d.ellipse([41, 31, 65, 56], fill=(112, 91, 188, 220))
d.ellipse([52, 27, 74, 52], fill=(235, 228, 255, 255))
d.arc([24, 27, 74, 57], 200, 330, fill=(255, 255, 255, 190), width=3)
def main() -> None:
save_icon("tool_laxative.webp", draw_laxative)
save_icon("tool_mystery_drug.webp", draw_mystery)
save_icon("tool_ammo.webp", draw_bullet)
save_icon("tool_sleep_drug.webp", draw_sleep)
save_icon("tool_water.webp", draw_drop((86, 157, 224, 220), (45, 101, 168, 150), (255, 255, 255, 150)))
save_icon("tool_zunda_juice.webp", draw_drop((125, 218, 82, 235), (63, 150, 50, 160), (238, 255, 226, 160)))
save_icon("tool_mercury.webp", draw_drop((210, 216, 222, 240), (112, 122, 132, 165), (255, 255, 255, 170)))
if __name__ == "__main__":
main()