78 lines
3.2 KiB
Python
78 lines
3.2 KiB
Python
"""Pack project-local source art into the MASM renderer's 128px BGRA slots.
|
|
|
|
Python is an offline build tool; the shipped game has no Python dependency.
|
|
Only transparent alpha is discarded. Black, white and gold detail is retained.
|
|
"""
|
|
from pathlib import Path
|
|
from PIL import Image
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SIZE = 128
|
|
SLOTS = 48
|
|
CHROMA = (255, 0, 255, 0)
|
|
|
|
|
|
def cells(path, columns, rows):
|
|
image = Image.open(path).convert("RGBA")
|
|
return [image.crop((c * image.width // columns, r * image.height // rows,
|
|
(c + 1) * image.width // columns, (r + 1) * image.height // rows))
|
|
for r in range(rows) for c in range(columns)]
|
|
|
|
|
|
def sprite(cell):
|
|
# Preserve each sheet's common anchor and scale, including internal margins.
|
|
scale = min((SIZE - 8) / cell.width, (SIZE - 8) / cell.height)
|
|
resized = cell.resize((round(cell.width * scale), round(cell.height * scale)),
|
|
Image.Resampling.LANCZOS)
|
|
out = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
|
|
out.alpha_composite(resized, ((SIZE - resized.width) // 2, SIZE - 4 - resized.height))
|
|
return out
|
|
|
|
|
|
def pack(image, is_sprite=False):
|
|
result = bytearray()
|
|
for r, g, b, a in image.getdata():
|
|
if is_sprite and a < 128:
|
|
r, g, b, a = CHROMA
|
|
result.extend((b, g, r, 0))
|
|
return result
|
|
|
|
|
|
def main():
|
|
raw = (ROOT / "assets/textures/casino3d.bin").read_bytes()
|
|
assert len(raw) == SLOTS * 64 * 64 * 4
|
|
textures = []
|
|
for n in range(SLOTS):
|
|
tile = Image.frombytes("RGBA", (64, 64), raw[n * 16384:(n + 1) * 16384],
|
|
"raw", "BGRA")
|
|
tile.putalpha(255)
|
|
textures.append(tile.resize((SIZE, SIZE), Image.Resampling.NEAREST))
|
|
atlas = cells(ROOT / "assets/sprites/casino-atlas-v1.png", 4, 4)
|
|
dancer = cells(ROOT / "assets/sprites/cabaret-loop-v1.png", 4, 3)
|
|
sprites = {}
|
|
for n, frame in enumerate(dancer):
|
|
sprites[10 + n] = sprite(frame)
|
|
for slot, cell in {24: 4, 25: 5, 26: 6, 30: 7, 31: 8, 32: 10,
|
|
34: 9, 35: 11, 36: 12, 37: 13, 38: 14, 39: 15}.items():
|
|
sprites[slot] = sprite(atlas[cell])
|
|
# The original ATM uses an explicit magenta chroma background.
|
|
textures[33].putalpha(255)
|
|
for slot, image in sprites.items():
|
|
textures[slot] = image
|
|
data = b"".join(pack(image, n in sprites) for n, image in enumerate(textures))
|
|
assert len(data) == SLOTS * SIZE * SIZE * 4
|
|
target = ROOT / "assets/textures/casino3d-hd.bin"
|
|
target.write_bytes(data)
|
|
preview = Image.new("RGBA", (SIZE * 8, SIZE * 6), (24, 29, 32, 255))
|
|
for n, image in enumerate(textures):
|
|
preview.alpha_composite(image, ((n % 8) * SIZE, (n // 8) * SIZE))
|
|
preview.convert("RGB").save(ROOT / "build/graphics/texture-atlas-preview.png")
|
|
dancer_preview = [sprites[10 + n].resize((256, 256), Image.Resampling.NEAREST)
|
|
for n in range(12)]
|
|
dancer_preview[0].save(ROOT / "build/graphics/cabaret-loop-preview.gif", save_all=True,
|
|
append_images=dancer_preview[1:], duration=100, loop=0, disposal=2)
|
|
print(f"Packed {SLOTS} x {SIZE}x{SIZE}: {target}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|