"""Pack the approved piano/pianist and guitarist cutouts for the MASM stage.""" from pathlib import Path import struct from PIL import Image ROOT = Path(__file__).resolve().parents[2] def pack(sheet, left, right, width, name): cells = [sheet.crop((left, row * 384, right, (row + 1) * 384)) for row in range(4)] bounds = [cell.getchannel("A").point(lambda a: 255 if a >= 128 else 0).getbbox() for cell in cells] assert all(bounds), "each playing pose must contain opaque pixels" box = (min(b[0] for b in bounds), min(b[1] for b in bounds), max(b[2] for b in bounds), max(b[3] for b in bounds)) scale = min((width - 12) / (box[2] - box[0]), 244 / (box[3] - box[1])) size = (round((box[2] - box[0]) * scale), round((box[3] - box[1]) * scale)) frames = [] for cell in cells: sprite = cell.crop(box).resize(size, Image.Resampling.LANCZOS) # The native sprite rasterizer uses a binary alpha test. sprite.putalpha(sprite.getchannel("A").point(lambda a: 255 if a >= 128 else 0)) canvas = Image.new("RGBA", (width, 256)) canvas.alpha_composite(sprite, ((width - size[0]) // 2, 250 - size[1])) frames.append(canvas) raw = b"".join(frame.tobytes("raw", "BGRA") for frame in frames) (ROOT / f"assets/animations/hall/{name}-v1.bin").write_bytes( struct.pack("<4s4I", b"ANM1", width, 256, 4, len(raw)) + raw) return frames def main(): sheet = Image.open(ROOT / "assets/sprites/hall/cabaret-musicians-v1.png").convert("RGBA") assert sheet.size == (1024, 1536) piano = pack(sheet, 0, 640, 384, "cabaret-piano") guitar = pack(sheet, 640, 1024, 160, "cabaret-guitar") preview = Image.new("RGBA", (544, 1024), (24, 28, 29, 255)) for row in range(4): preview.alpha_composite(piano[row], (0, row * 256)) preview.alpha_composite(guitar[row], (384, row * 256)) preview.save(ROOT / "build/cabaret-musicians-preview.png") print("Packed coherent grand piano/pianist and guitarist: four anchored poses each") if __name__ == "__main__": main()