Files
Live-streaming/scripts/build_kirara_sprite_catalog.py

95 lines
3.8 KiB
Python
Raw Permalink 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.
"""Update the Kirara sprite rectangles in web/index.html from transparent PNGs.
Usage: .venv/Scripts/python.exe scripts/build_kirara_sprite_catalog.py
"""
from pathlib import Path
from PIL import Image
ROOT = Path(__file__).resolve().parents[1]
ASSETS = ROOT / "web" / "uploads"
PAGE = ROOT / "web" / "index.html"
START = " // Source rectangles come from the four generated transparent 2×2 walk sheets."
END = " let kiraraDirection = \"front\";"
def frame_boxes(path: Path) -> list[list[int]]:
image = Image.open(path).convert("RGBA")
width, height = image.size
if width < 100 or height < 100:
raise ValueError(f"Sprite is unexpectedly small: {path}")
boxes = []
for frame in range(4):
start = round(frame * width / 4)
stop = round((frame + 1) * width / 4)
alpha = image.crop((start, 0, stop, height)).getchannel("A")
content = alpha.point(lambda value: 255 if value >= 16 else 0).getbbox()
if content is None:
raise ValueError(f"Empty frame {frame}: {path}")
left = max(0, content[0] - 3)
top = max(0, content[1] - 3)
right = min(stop - start, content[2] + 3)
bottom = min(height, content[3] + 3)
boxes.append([start + left, top, right - left, bottom - top])
return boxes
names = [
*(f"kirara_walk_{direction}" for direction in ("front", "back", "left", "right")),
"kirara_eat_rice", "kirara_eat_dango", "kirara_bath_sponge", "kirara_bath_shower",
*(f"kirara_idle_{name}" for name in (
"delivery", "reading", "phone", "sleep", "stretch", "groom",
"butterfly", "tea", "yarn", "humming")),
"kirara_rest_breathe",
*(f"kirara_emotion_{name}_{variant}" for name in (
"plus3", "plus1", "minus1", "minus3") for variant in ("a", "b")),
]
lines = [
" // Crops follow each generated sprite's transparent silhouette.",
" const kiraraSpriteBoxes = {",
]
for name in names:
boxes = frame_boxes(ASSETS / f"{name}.png")
formatted = ", ".join(str(box).replace(" ", "") for box in boxes)
lines.append(f' "{name}": [{formatted}],')
lines += [
" };",
" const kiraraSpriteAsset = name => ({",
" src: `/uploads/${name}.png`, boxes: kiraraSpriteBoxes[name]",
" });",
" const kiraraWalkAssets = Object.fromEntries(",
" [\"front\", \"back\", \"left\", \"right\"].map(direction =>",
" [direction, kiraraSpriteAsset(`kirara_walk_${direction}`)])",
" );",
" const kiraraActionAssets = {",
" feed: [kiraraSpriteAsset(\"kirara_eat_rice\"), kiraraSpriteAsset(\"kirara_eat_dango\")],",
" bath: [kiraraSpriteAsset(\"kirara_bath_sponge\"), kiraraSpriteAsset(\"kirara_bath_shower\")]",
" };",
" const kiraraIdleAssets = Object.fromEntries(",
" [\"delivery\", \"reading\", \"phone\", \"sleep\", \"stretch\",",
" \"groom\", \"butterfly\", \"tea\", \"yarn\", \"humming\"].map(type =>",
" [type, kiraraSpriteAsset(`kirara_idle_${type}`)])",
" );",
" const kiraraRestAsset = kiraraSpriteAsset(\"kirara_rest_breathe\");",
" const kiraraEmotionAssets = {",
]
for delta, word in ((3, "plus3"), (1, "plus1"), (-1, "minus1"), (-3, "minus3")):
lines.append(
f' "{delta}": [kiraraSpriteAsset("kirara_emotion_{word}_a"), '
f'kiraraSpriteAsset("kirara_emotion_{word}_b")],'
)
lines += [" };", ""]
text = PAGE.read_text(encoding="utf-8")
start = text.find(START)
if start < 0:
start = text.find(" // Crops follow each generated sprite's transparent silhouette.")
end = text.find(END, start)
if start < 0 or end < 0:
raise ValueError("Kirara asset block was not found")
PAGE.write_text(text[:start] + "\n".join(lines) + text[end:], encoding="utf-8")
print(f"Updated {len(names)} Kirara sprite sheets in {PAGE}")