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) def abort(msg: str) -> NoReturn: print(msg, file=sys.stderr) sys.exit(1) SpawnPoint = Tuple[int, int] def generate_coll_map( in_path: pathlib.Path, width: int, height: int, compress: bool = False ) -> Tuple[str, SpawnPoint]: 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 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) 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 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 = 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_WIDTH: DB {width} {section}_MAP_HEIGHT: DB {height} {section}_SPAWN_X: DB {spawn[0]} {section}_SPAWN_Y: DB {spawn[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()