papermario/tools/build/sprites/sprite.py
alex 7cf8bf0b88
Split charsets, matching load_font and load_font_data (#312)
Also use snake_case for splat_ext types
2021-07-16 10:28:37 +01:00

133 lines
4.3 KiB
Python
Executable File

#! /usr/bin/python3
from math import floor, ceil
from sys import argv, path
from pathlib import Path
path.append(str(Path(__file__).parent.parent.parent / "splat"))
path.append(str(Path(__file__).parent.parent.parent / "splat_ext"))
from pm_npc_sprites import Sprite
def pack_color(r, g, b, a):
r = floor(31 * (r / 255))
g = floor(31 * (g / 255))
b = floor(31 * (b / 255))
s = round(a / 0xFF)
s |= (r & 0x1F) << 11
s |= (g & 0x1F) << 6
s |= (b & 0x1F) << 1
return s
def iter_in_groups(iterable, n, fillvalue=None):
from itertools import zip_longest
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
if __name__ == "__main__":
if len(argv) != 3:
print("usage: compile_npc_sprite.py [OUTBIN] [DIR]")
exit(1)
_, outfile, sprite_dir = argv
try:
sprite = Sprite.from_dir(Path(sprite_dir))
except AssertionError as e:
print("error:", e)
exit(1)
with open(outfile, "wb") as f:
f.seek(0x10) # leave space for header
# leave space for animation offset list
f.seek((len(sprite.animations) + 1) * 4, 1)
animation_offsets = []
# write animations
for i, components in enumerate(sprite.animations):
animation_offsets.append(f.tell())
# leave space for component offset list
f.seek((len(components) + 1) * 4, 1)
component_offsets = []
for comp in components:
offset = f.tell()
for command in comp.commands:
f.write(command.to_bytes(2, byteorder="big"))
f.seek(f.tell() % 4, 1)
component_offsets.append(f.tell())
f.write(offset.to_bytes(4, byteorder="big"))
f.write((len(comp.commands) * 2).to_bytes(2, byteorder="big"))
f.write(comp.x.to_bytes(2, byteorder="big", signed=True))
f.write(comp.y.to_bytes(2, byteorder="big", signed=True))
f.write(comp.z.to_bytes(2, byteorder="big", signed=True))
next_anim = f.tell()
# write component offset list
f.seek(animation_offsets[i])
component_offsets.append(-1)
for offset in component_offsets:
f.write(offset.to_bytes(4, byteorder="big", signed=True))
f.seek(next_anim)
# palettes start 8-byte aligned
if (f.tell() & 7) == 4:
f.seek(4, 1)
# write palettes
palette_offsets = []
for i, palette in enumerate(sprite.palettes):
palette_offsets.append(f.tell())
for rgba in palette:
if rgba[3] not in (0, 0xFF):
print("error: translucent pixels not allowed in palette {sprite.palette_names[i]}")
exit(1)
color = pack_color(*rgba)
f.write(color.to_bytes(2, byteorder="big"))
# write images/rasters
image_offsets = []
for image in sprite.images:
offset = f.tell()
for a, b in iter_in_groups(image.raster, 2):
byte = (a << 4) | b
f.write(byte.to_bytes(1, byteorder="big"))
image_offsets.append(f.tell())
f.write(offset.to_bytes(4, byteorder="big"))
f.write(bytes([image.width, image.height, image.palette_index, 0xFF]))
# write image offset list
image_offset_list_offset = f.tell()
image_offsets.append(-1)
for offset in image_offsets:
f.write(offset.to_bytes(4, byteorder="big", signed=True))
# write palette offset list
palette_offset_list_offset = f.tell()
palette_offsets.append(-1)
for offset in palette_offsets:
f.write(offset.to_bytes(4, byteorder="big", signed=True))
# write header
f.seek(0)
f.write(image_offset_list_offset.to_bytes(4, byteorder="big"))
f.write(palette_offset_list_offset.to_bytes(4, byteorder="big"))
f.write(sprite.max_components.to_bytes(4, byteorder="big"))
f.write(sprite.num_variations.to_bytes(4, byteorder="big"))
# write animation offset list
animation_offsets.append(-1)
for offset in animation_offsets:
f.write(offset.to_bytes(4, byteorder="big", signed=True))