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",
|
|
mappath,
|
|
"-o",
|
|
tilepath,
|
|
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_
|
|
|
|
#include "map.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");
|
|
|
|
extern const map_t map_{section} = {{
|
|
(uint16_t)&{section}_tiles[0],
|
|
{tiles_size},
|
|
(uint16_t)&{section}_map[0],
|
|
(uint16_t)&{section}_collision[0],
|
|
{width},
|
|
{height},
|
|
{spawn[0]},
|
|
{spawn[1]},
|
|
{camera[0]},
|
|
{camera[1]}
|
|
}};
|
|
""".strip()
|
|
)
|
|
|
|
|
|
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()
|