|
|
- import argparse
- import pathlib
- import subprocess
- import sys
- import tempfile
- from typing import NoReturn, Tuple
-
- from PIL import Image
-
- GREEN = (0, 255, 0)
- RED = (255, 0, 0)
- BLUE = (0, 0, 255)
- YELLOW = (255, 255, 0)
-
-
- def abort(msg: str) -> NoReturn:
- print(msg, file=sys.stderr)
- sys.exit(1)
-
-
- Point = Tuple[int, int]
-
-
- def generate_coll_map(
- in_path: pathlib.Path, width: int, height: int, compress: bool = False
- ) -> Tuple[str, Point, Point]:
- png = Image.open(in_path).convert("RGB")
- if png.width % 8 != 0 or png.height % 8 != 0:
- abort(f"file '{in_path}' has invalid dimensions (should be multiple of 8)")
-
- if png.width // 8 != width or png.height // 8 != height:
- abort(f"file '{in_path}' has different size from map")
-
- out_bytes = []
- bits = []
- spawn = None
- camera = (0, 0)
-
- for y in range(png.height // 8):
- for x in range(png.width // 8):
- pixel = png.getpixel((x * 8, y * 8))
- bit = None
-
- if pixel == RED:
- bit = 0
- elif pixel == GREEN:
- bit = 1
- elif pixel == BLUE:
- bit = 1
- spawn = (x, y)
- elif pixel == YELLOW:
- bit = 1
- camera = (x, y)
- else:
- abort(f"unsupported pixel in collision map: {pixel}")
-
- if compress:
- bits.append(bit)
- if len(bits) == 8:
- byte = sum([bit << i for i, bit in enumerate(bits)])
- out_bytes.append(byte)
- bits = []
- else:
- out_bytes.append(bit)
-
- png.close()
-
- if spawn is None:
- abort(f"no spawn point located")
-
- return format_bytes(out_bytes, width=width), spawn, camera
-
-
- def format_bytes(data: bytes, width: int = 16) -> str:
- lines = []
- for line_no in range(0, len(data), width):
- line = data[line_no : line_no + width]
- lines.append(" DB " + ", ".join(["$%02X" % b for b in line]))
- return "\n".join(lines)
-
-
- def generate_map(pngfile: str, compress: bool = False) -> None:
- pngpath = pathlib.Path(pngfile).resolve()
- incpath = pngpath.parent / pngpath.name.replace(".png", ".inc")
- spath = pngpath.parent / pngpath.name.replace(".png", ".s")
-
- png = Image.open(pngpath)
- if png.width % 8 != 0 or png.height % 8 != 0:
- abort(f"file '{pngfile}' has invalid dimensions (should be multiple of 8)")
-
- width = png.width // 8
- height = png.height // 8
-
- if width > 127 or height > 127:
- abort(
- f"file '{pngfile}' has invalid dimensions (width/height greater than 127 tiles)"
- )
-
- png.close()
-
- with tempfile.NamedTemporaryFile() as tilef, tempfile.NamedTemporaryFile() as mapf:
- subprocess.run(
- [
- "rgbgfx",
- "-u",
- "-t",
- tilef.name,
- "-o",
- mapf.name,
- pngfile,
- ]
- )
-
- map_data = format_bytes(tilef.read(), width=width)
- tile_data = format_bytes(mapf.read())
-
- section = pngpath.name.replace(".png", "")
-
- collpath = pngpath.parent / pngpath.name.replace(".png", "_coll.png")
- coll_map, spawn, camera = generate_coll_map(
- collpath, width, height, compress=compress
- )
-
- with open(incpath, "w") as outf:
- outf.write(
- f"""DEF {section}_WIDTH EQU {width}
- DEF {section}_HEIGHT EQU {height}
-
- DEF {section}_NUM_TILES EQUS "({section}_TILES_end - {section}_TILES)"
- DEF {section}_MAP_SIZE EQUS "({section}_MAP_end - {section}_MAP)"
-
- ASSERT {section}_MAP_SIZE == {section}_WIDTH * {section}_HEIGHT
- """
- )
-
- with open(spath, "w") as outf:
- outf.write(
- f"""SECTION "MAP - {section}", ROM0
-
- {section}_Data::
- {section}_TILE_PTR: DW {section}_TILES
- {section}_TILE_SIZE: DB ({section}_TILES_end - {section}_TILES)
- {section}_MAP_PTR: DW {section}_MAP
- {section}_MAP_COLLISION: DW {section}_COLLISION
- {section}_MAP_WIDTH: DB {width}
- {section}_MAP_HEIGHT: DB {height}
- {section}_SPAWN_X: DB {spawn[0]}
- {section}_SPAWN_Y: DB {spawn[1]}
- {section}_CAMERA_X: DB {camera[0]}
- {section}_CAMERA_Y: DB {camera[1]}
-
- {section}_MAP::
- {map_data}
- {section}_MAP_end::
-
- {section}_TILES::
- {tile_data}
- {section}_TILES_end::
-
- {section}_COLLISION::
- {coll_map}
- {section}_COLLISION_end::
- """
- )
-
-
- def main() -> None:
- parser = argparse.ArgumentParser("generate_map")
- parser.add_argument("-c", "--compress", default=False)
- parser.add_argument("png")
- args = parser.parse_args()
- generate_map(args.png, compress=(not not args.compress))
-
-
- if __name__ == "__main__":
- main()
|