"""Rebuild A01 button skins with deterministic horizontal three-slice scaling.""" import argparse import json from pathlib import Path from PIL import Image, PngImagePlugin def rebuild_button( source_path: Path, output_path: Path, *, output_size: tuple[int, int], trim: int, padding: int, cap_width: int, ) -> None: """Trim contaminated edges, preserve both caps, and stretch only the center.""" with Image.open(source_path) as opened: source = opened.convert("RGBA") if trim < 0 or trim * 2 >= min(source.size): raise ValueError(f"invalid trim {trim} for source size {source.size}") if trim: source = source.crop((trim, trim, source.width - trim, source.height - trim)) if cap_width <= 0 or cap_width * 2 >= source.width: raise ValueError(f"invalid capWidth {cap_width} for cropped width {source.width}") output_width, output_height = output_size inner_width = output_width - padding * 2 inner_height = output_height - padding * 2 if inner_width <= 0 or inner_height <= 0: raise ValueError(f"padding {padding} leaves no drawable area in {output_size}") target_cap_width = max(1, round(cap_width * inner_height / source.height)) if target_cap_width * 2 >= inner_width: raise ValueError("scaled caps leave no room for the center slice") left = source.crop((0, 0, cap_width, source.height)) center = source.crop((cap_width, 0, source.width - cap_width, source.height)) right = source.crop((source.width - cap_width, 0, source.width, source.height)) resampling = Image.Resampling.LANCZOS left = left.resize((target_cap_width, inner_height), resampling) center = center.resize((inner_width - target_cap_width * 2, inner_height), resampling) right = right.resize((target_cap_width, inner_height), resampling) output = Image.new("RGBA", output_size, (0, 0, 0, 0)) output.alpha_composite(left, (padding, padding)) output.alpha_composite(center, (padding + target_cap_width, padding)) output.alpha_composite(right, (output_width - padding - target_cap_width, padding)) output_path.parent.mkdir(parents=True, exist_ok=True) png_info = PngImagePlugin.PngInfo() png_info.add(b"sRGB", b"\x00") output.save(output_path, format="PNG", optimize=True, pnginfo=png_info) def rebuild_manifest(manifest_path: Path, workspace_root: Path) -> None: manifest = json.loads(manifest_path.read_text(encoding="utf-8")) for asset in manifest["assets"]: processing = asset["processing"] alpha = asset["alpha"] pixels = asset["outputPixels"] source = workspace_root / asset["source"] output = workspace_root / asset["output"] rebuild_button( source, output, output_size=(pixels["width"], pixels["height"]), trim=processing["trim"], padding=alpha["transparentOuterPadding"], cap_width=processing["capWidth"], ) print(f"REBUILT {asset['id']} -> {asset['output']}") def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("manifest", type=Path) parser.add_argument("--workspace", type=Path, required=True) args = parser.parse_args() rebuild_manifest(args.manifest.resolve(), args.workspace.resolve()) if __name__ == "__main__": main()