"""Offline normalization of generated door components and distance-driven gait art.""" from pathlib import Path import struct from PIL import Image from pack_guest_cast import isolate ROOT = Path(__file__).resolve().parents[2] ART = ROOT / "assets/sprites/hall" OUT = ROOT / "assets/animations/hall" PREVIEWS = ROOT / "build/hall-expansion" def pack(name, frames): w, h = frames[0].size assert all(frame.size == (w, h) for frame in frames) payload = b"".join(frame.tobytes("raw", "BGRA") for frame in frames) (OUT / name).write_bytes(struct.pack("<4s4I", b"ANM1", w, h, len(frames), len(payload)) + payload) def guests(): sheet = Image.open(ART / "guest-walk-v2.png").convert("RGBA") # The generated strip has variable row heights; these gutters separate feet/heads. rows = (0, 190, 376, 560, 746, 916, sheet.height) frames = [] for appearance in range(6): cells = [isolate(sheet.crop((col*sheet.width//8, rows[appearance], (col+1)*sheet.width//8, rows[appearance+1]))) for col in range(8)] scale = min(126/max(cell.width for cell in cells), 204/max(cell.height for cell in cells)) for cell in cells: cell = cell.resize((round(cell.width*scale), round(cell.height*scale)), Image.Resampling.LANCZOS) frame = Image.new("RGBA", (128,224)) frame.alpha_composite(cell, ((128-cell.width)//2, 224-cell.height)) frames.append(frame) pack("guest-walk-v2.bin", frames) pack("guest-walk-left-v2.bin", [frame.transpose(Image.Transpose.FLIP_LEFT_RIGHT) for frame in frames]) preview = Image.new("RGBA", (8*128,6*224), (27,30,29,255)) for i, frame in enumerate(frames[:48]): preview.alpha_composite(frame, ((i%8)*128, (i//8)*224)) preview.save(PREVIEWS / "guest-walk-v2-preview.png") def door(): sheet = Image.open(ART / "basement-door-v2.png").convert("RGBA") leaf = isolate(sheet.crop((0,0,640,sheet.height))).resize((256,512), Image.Resampling.LANCZOS) source_frame = isolate(sheet.crop((640,0,1420,sheet.height))) wheel = isolate(sheet.crop((1420,0,sheet.width,sheet.height))).resize((92,92), Image.Resampling.LANCZOS) frames = [] for phase in range(17): texture = leaf.copy() rotated = wheel.rotate(-270*phase/16, Image.Resampling.BICUBIC, expand=False) # UV texels are not square in world space on the 1.5 by 1.9 metre leaf. rotated = rotated.resize((58,92), Image.Resampling.LANCZOS) texture.alpha_composite(rotated, (99,244)) frames.append(texture) # Map the rigid jambs around a known rectangular aperture, without a baked leaf. w,h = source_frame.size left, right, lintel = round(w*.24), round(w*.72), round(h*.14) frame = Image.new("RGBA", (256,512)) frame.alpha_composite(source_frame.crop((0,0,left,h)).resize((64,512)), (0,0)) frame.alpha_composite(source_frame.crop((right,0,w,h)).resize((64,512)), (192,0)) frame.alpha_composite(source_frame.crop((left,0,right,lintel)).resize((128,32)), (64,0)) frames.append(frame) pack("basement-door-v2.bin", frames) preview = Image.new("RGBA", (3*256,512), (27,30,29,255)) for i, texture in enumerate((frames[0],frames[8],frame)): preview.alpha_composite(texture,(i*256,0)) preview.save(PREVIEWS / "basement-door-v2-preview.png") if __name__ == "__main__": PREVIEWS.mkdir(parents=True, exist_ok=True) guests() door() print("Packed 96 gait frames and 18 door textures; runtime remains native assembly.")