@ -1,33 +1,35 @@ | |||
# Graphics | |||
SPRITE_PNG := $(shell find png/sprite -type f -name '*.png') | |||
MAP_PNG := $(shell find png/map -type f -name '*.png' -not -name '*_coll.png') | |||
MAP_COLL := $(shell find png/map -type f -name '*_coll.png') | |||
MAP_ASM := $(MAP_PNG:%.png:%.s) | |||
ALL_2BPP := $(SPRITE_PNG:%.png=%.2bpp) $(MAP_PNG:%.png=%.map.2bpp) $(MAP_PNG:%.png=%.tiles.2bpp) | |||
# Graphics (generated) | |||
MAP_S := $(MAP_PNG:%.png=%.s) | |||
MAP_INC := $(MAP_PNG:%.png=%.inc) | |||
SPRITE_2BPP := $(SPRITE_PNG:%.png=%.2bpp) | |||
# Code | |||
SFILES := $(shell find src -type f -name '*.s') | |||
OFILES := $(SFILES:%.s=%.o) $(MAP_ASM) | |||
OFILES := $(SFILES:%.s=%.o) $(MAP_S:%.s=%.o) | |||
.PHONY: clean | |||
is.gb is.gb.sym:$(OFILES) $(ALL_2BPP) | |||
is.gb is.gb.sym: $(OFILES) | |||
rgblink -o $@ -n $@.sym $(OFILES) | |||
rgbfix -v $@ | |||
$(OFILES): $(ALL_2BPP) | |||
$(OFILES): $(MAP_INC) | |||
$(OFILES): $(SPRITE_2BPP) | |||
%.o: %.s | |||
rgbasm -i inc -o $@ $< | |||
png/map/%.tiles.2bpp png/map/%.map.2bpp: png/map/%.png | |||
rgbgfx -u -t $(<:%.png=%.map.2bpp) -o $(<:%.png=%.tiles.2bpp) $< | |||
png/map/%.s: png/map/%_coll.png | |||
python scripts/generate_coll_map.py -o $@ $< | |||
png/map/%.s png/map/%.inc: png/map/%.png png/map/%_coll.png | |||
python scripts/generate_map.py $< | |||
png/sprite/%.2bpp: png/sprite/%.png | |||
rgbgfx -o $@ $< | |||
clean: | |||
rm -f is.gb is.gb.sym $(ALL_2BPP) $(OFILES) $(MAP_ASM) | |||
rm -f is.gb is.gb.sym $(OFILES) $(MAP_S) $(MAP_INC) $(SPRITE_2BPP) |
@ -1,71 +0,0 @@ | |||
import argparse | |||
import pathlib | |||
import sys | |||
from PIL import Image | |||
GREEN = (0, 255, 0) | |||
RED = (255, 0, 0) | |||
def generate_coll_map(in_path: str, out_path: str) -> None: | |||
png = Image.open(in_path).convert("RGB") | |||
assert png.height % 8 == 0 and png.width % 8 == 0 | |||
out_bytes = [] | |||
bits = [] | |||
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 = 1 | |||
elif pixel == GREEN: | |||
bit = 0 | |||
else: | |||
print(f"unsupported pixel in collision map {pixel}", file=sys.stderr) | |||
return | |||
bits.append(bit) | |||
if len(bits) == 8: | |||
byte = sum([bit << i for i, bit in enumerate(bits)]) | |||
out_bytes.append(byte) | |||
bits = [] | |||
lines = [] | |||
for line_no in range(0, len(out_bytes), 16): | |||
line = out_bytes[line_no : line_no + 16] | |||
lines.append(" DB " + ", ".join(["$%02X" % b for b in line])) | |||
section = pathlib.Path(in_path).name.replace("_coll.png", "") | |||
all_lines = "\n".join(lines) | |||
with open(out_path, "wb") as outf: | |||
outf.write( | |||
f"""SECTION "{section.upper()} MAP", ROM0 | |||
; Values are provided in tiles | |||
DEF {section}_width EQU {png.width // 8} | |||
DEF {section}_height EQU {png.height // 8} | |||
{section}_collision_map:: | |||
{all_lines} | |||
""".encode( | |||
"utf-8" | |||
) | |||
) | |||
def main() -> None: | |||
parser = argparse.ArgumentParser("generate_coll_map") | |||
parser.add_argument("-o", "--output", required=True) | |||
parser.add_argument("png") | |||
args = parser.parse_args() | |||
generate_coll_map(args.png, args.output) | |||
if __name__ == "__main__": | |||
main() |
@ -0,0 +1,140 @@ | |||
import argparse | |||
import pathlib | |||
import subprocess | |||
import sys | |||
import tempfile | |||
from typing import NoReturn | |||
from PIL import Image | |||
GREEN = (0, 255, 0) | |||
RED = (255, 0, 0) | |||
def abort(msg: str) -> NoReturn: | |||
print(msg, file=sys.stderr) | |||
sys.exit(1) | |||
def generate_coll_map(in_path: pathlib.Path, width: int, height: int) -> str: | |||
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 = [] | |||
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 = 1 | |||
elif pixel == GREEN: | |||
bit = 0 | |||
else: | |||
abort(f"unsupported pixel in collision map: {pixel}") | |||
bits.append(bit) | |||
if len(bits) == 8: | |||
byte = sum([bit << i for i, bit in enumerate(bits)]) | |||
out_bytes.append(byte) | |||
bits = [] | |||
png.close() | |||
lines = [] | |||
for line_no in range(0, len(out_bytes), 16): | |||
line = out_bytes[line_no : line_no + 16] | |||
lines.append(" DB " + ", ".join(["$%02X" % b for b in line])) | |||
return "\n".join(lines) | |||
def format_bytes(data: bytes) -> str: | |||
lines = [] | |||
for line_no in range(0, len(data), 16): | |||
line = data[line_no : line_no + 16] | |||
lines.append(" DB " + ", ".join(["$%02X" % b for b in line])) | |||
return "\n".join(lines) | |||
def generate_map(pngfile: str) -> 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 | |||
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()) | |||
tile_data = format_bytes(mapf.read()) | |||
section = pngpath.name.replace(".png", "") | |||
collpath = pngpath.parent / pngpath.name.replace(".png", "_coll.png") | |||
coll_map = generate_coll_map(collpath, width, height) | |||
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}", ROMX | |||
{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("png") | |||
args = parser.parse_args() | |||
generate_map(args.png) | |||
if __name__ == "__main__": | |||
main() |