chore: share game-studio hermes plugin

This commit is contained in:
2026-05-11 12:00:45 +08:00
parent d23cf3807d
commit 81f57ea5ce
43 changed files with 2230 additions and 1 deletions

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Render a simple contact sheet from a directory of normalized sprite frames."""
from __future__ import annotations
import argparse
import math
import re
from pathlib import Path
try:
from PIL import Image, ImageDraw
except ImportError as exc: # pragma: no cover
raise SystemExit(
"Pillow is required. Install it with `python3 -m pip install pillow`."
) from exc
NUMBER_RE = re.compile(r"(\d+)")
def natural_key(path: Path) -> list[int | str]:
parts: list[int | str] = []
for chunk in NUMBER_RE.split(path.stem):
if not chunk:
continue
if chunk.isdigit():
parts.append(int(chunk))
else:
parts.append(chunk)
parts.append(path.suffix)
return parts
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Render a preview contact sheet from a directory of sprite frames."
)
parser.add_argument("--frames-dir", required=True, help="Directory containing PNG frames.")
parser.add_argument("--out", required=True, help="Output PNG path.")
parser.add_argument(
"--columns",
type=int,
default=4,
help="Number of columns in the preview sheet. Default: 4.",
)
parser.add_argument(
"--gap",
type=int,
default=8,
help="Gap between frames in pixels. Default: 8.",
)
return parser.parse_args()
def paint_checkerboard(image: Image.Image, tile: int = 16) -> None:
draw = ImageDraw.Draw(image)
colors = ((240, 243, 246, 255), (225, 230, 235, 255))
for top in range(0, image.height, tile):
for left in range(0, image.width, tile):
color = colors[((left // tile) + (top // tile)) % 2]
draw.rectangle((left, top, left + tile, top + tile), fill=color)
def main() -> None:
args = parse_args()
if args.columns < 1:
raise SystemExit("--columns must be at least 1.")
if args.gap < 0:
raise SystemExit("--gap cannot be negative.")
frame_dir = Path(args.frames_dir)
frames = sorted(frame_dir.glob("*.png"), key=natural_key)
if not frames:
raise SystemExit("No PNG frames were found in --frames-dir.")
images = [Image.open(path).convert("RGBA") for path in frames]
frame_width = max(image.width for image in images)
frame_height = max(image.height for image in images)
rows = math.ceil(len(images) / args.columns)
sheet_width = args.columns * frame_width + max(0, args.columns - 1) * args.gap
sheet_height = rows * frame_height + max(0, rows - 1) * args.gap
sheet = Image.new("RGBA", (sheet_width, sheet_height), (255, 255, 255, 255))
paint_checkerboard(sheet)
for index, image in enumerate(images):
row = index // args.columns
column = index % args.columns
left = column * (frame_width + args.gap) + (frame_width - image.width) // 2
top = row * (frame_height + args.gap) + (frame_height - image.height) // 2
sheet.alpha_composite(image, (left, top))
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
sheet.save(out_path)
if __name__ == "__main__":
main()