import argparse import os import pathlib import re import subprocess import sys 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[bytes, 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 bytes(out_bytes), 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(" " + ", ".join(["0x%02X" % b for b in line])) return ",\n".join(lines) def generate_map(pngfile: str, compress: bool = False) -> None: pngpath = pathlib.Path(pngfile).resolve() section = re.sub(r"[^a-z]", "_", pngpath.name.replace(".png", "").lower()) tilepath = pngpath.parent / f"{section}.tiles" mappath = pngpath.parent / f"{section}.tilemap" incpath = pngpath.parent / pngpath.name.replace(".png", ".h") spath = pngpath.parent / pngpath.name.replace(".png", ".c") 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() subprocess.run( [ "rgbgfx", "-u", "-t", tilepath, "-o", mappath, pngfile, ] ) collpath = pngpath.parent / pngpath.name.replace(".png", "_coll.png") collpath_out = pngpath.parent / f"{section}.collision" coll_map, spawn, camera = generate_coll_map( collpath, width, height, compress=compress ) with open(collpath_out, "wb") as outf: outf.write(coll_map) tiles_size = os.path.getsize(tilepath) with open(incpath, "w") as outf: outf.write( f""" #ifndef IS_MAP_{section}_H_ #define IS_MAP_{section}_H_ extern map_t map_{section}; #endif """ ) with open(spath, "w") as outf: outf.write( f""" #include "map.h" MAP_ASSET({section}_tiles, "{section}.tiles"); MAP_ASSET({section}_map, "{section}.tilemap"); MAP_ASSET({section}_collision, "{section}.collision"); const map_t map_{section} = {{ (uint16_t)&{section}_tiles[0], {tiles_size} / 16, (uint16_t)&{section}_map[0], (uint16_t)&{section}_collision[0], {width}, {height}, {spawn[0]}, {spawn[1]}, {camera[0]}, {camera[1]} }}; """ ) 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()