from __future__ import annotations import argparse from pathlib import Path from PIL import Image ROOT = Path(__file__).resolve().parents[1] DEFAULT_SOURCE_DIR = ROOT / "tmp" / "imagegen" OUTPUT_ROOT = ROOT / "src" / "assets" / "images" / "ui" / "equipment-icons" def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Build centered 128px and 32px equipment icons from transparent source art." ) parser.add_argument("ids", nargs="+", help="Equipment ids whose -alpha.png source should be built.") parser.add_argument("--source-dir", type=Path, default=DEFAULT_SOURCE_DIR) return parser.parse_args() def fit_icon(source: Image.Image, size: int) -> Image.Image: rgba = source.convert("RGBA") bounds = rgba.getchannel("A").getbbox() if bounds is None: raise ValueError("source image has no visible pixels") cropped = rgba.crop(bounds) inner_size = max(1, round(size * 0.88)) scale = min(inner_size / cropped.width, inner_size / cropped.height) resized_size = ( max(1, round(cropped.width * scale)), max(1, round(cropped.height * scale)), ) resized = cropped.resize(resized_size, Image.Resampling.LANCZOS) canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0)) offset = ((size - resized.width) // 2, (size - resized.height) // 2) canvas.alpha_composite(resized, offset) return canvas def main() -> None: args = parse_args() for size in (128, 32): (OUTPUT_ROOT / str(size)).mkdir(parents=True, exist_ok=True) for item_id in args.ids: source_path = args.source_dir / f"{item_id}-alpha.png" if not source_path.exists(): raise FileNotFoundError(f"missing transparent source: {source_path}") with Image.open(source_path) as source: for size in (128, 32): output_path = OUTPUT_ROOT / str(size) / f"{item_id}.png" fit_icon(source, size).save(output_path, optimize=True) print(f"Wrote {output_path.relative_to(ROOT)}") if __name__ == "__main__": main()