|
|
@ -0,0 +1,174 @@ |
|
|
|
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[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(" " + ", ".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") |
|
|
|
coll_map, spawn, camera = generate_coll_map( |
|
|
|
collpath, width, height, compress=compress |
|
|
|
) |
|
|
|
|
|
|
|
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 uint8_t {section}_collision[] = {{ |
|
|
|
{coll_map} |
|
|
|
}}; |
|
|
|
|
|
|
|
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() |