tools: switch tileset storage format to 2 bits per pixel
This commit is contained in:
parent
c55300ea75
commit
210e1bd22a
10
Makefile
10
Makefile
@ -9,7 +9,7 @@ DEP = $(patsubst %.o,%.d,$(SRC))
|
|||||||
res = $(subst pokered/,res/,$(patsubst %.$(1),%.$(1).o,$(wildcard $(2)*.$(1))))
|
res = $(subst pokered/,res/,$(patsubst %.$(1),%.$(1).o,$(wildcard $(2)*.$(1))))
|
||||||
res_png = $(subst pokered/,res/,$(patsubst %.png,%.$(1).o,$(wildcard $(2)*.png)))
|
res_png = $(subst pokered/,res/,$(patsubst %.png,%.$(1).o,$(wildcard $(2)*.png)))
|
||||||
|
|
||||||
GFX_TILESETS = $(call res_png,4bpp,pokered/gfx/tilesets/)
|
GFX_TILESETS = $(call res_png,2bpp,pokered/gfx/tilesets/)
|
||||||
GFX_BLOCKSETS = $(call res,bst,pokered/gfx/blocksets/)
|
GFX_BLOCKSETS = $(call res,bst,pokered/gfx/blocksets/)
|
||||||
MAPS_BLOCKS = $(call res,blk,pokered/maps/)
|
MAPS_BLOCKS = $(call res,blk,pokered/maps/)
|
||||||
|
|
||||||
@ -28,14 +28,14 @@ endef
|
|||||||
|
|
||||||
generated: $(GENERATED)
|
generated: $(GENERATED)
|
||||||
|
|
||||||
res/%.4bpp: pokered/%.png
|
res/%.2bpp: pokered/%.png
|
||||||
@mkdir -p $(dir $@)
|
@mkdir -p $(dir $@)
|
||||||
python tools/png_to_4bpp.py $< $@
|
python tools/png_to_nbpp.py $< 2 $@
|
||||||
|
|
||||||
%.4bpp.h:
|
%.2bpp.h:
|
||||||
$(BUILD_BINARY_H)
|
$(BUILD_BINARY_H)
|
||||||
|
|
||||||
%.4bpp.o: %.4bpp %.4bpp.h
|
%.2bpp.o: %.2bpp %.2bpp.h
|
||||||
$(BUILD_BINARY_O)
|
$(BUILD_BINARY_O)
|
||||||
|
|
||||||
res/%.blk: pokered/%.blk
|
res/%.blk: pokered/%.blk
|
||||||
|
26
main.cpp
26
main.cpp
@ -23,12 +23,28 @@ void palette_data()
|
|||||||
uint32_t cell_data(const start_size_t& buf, const uint32_t top)
|
uint32_t cell_data(const start_size_t& buf, const uint32_t top)
|
||||||
{
|
{
|
||||||
// round to nearest multiple of 32
|
// round to nearest multiple of 32
|
||||||
const uint32_t table_size = (buf.size + 0x20 - 1) & (-0x20);
|
const uint32_t table_size = ((buf.size * 2) + 0x20 - 1) & (-0x20);
|
||||||
const uint32_t base_address = top - table_size; // in bytes
|
const uint32_t base_address = top - table_size; // in bytes
|
||||||
|
|
||||||
copy<uint32_t>(&vdp2.vram.u32[(base_address / 4)],
|
uint32_t * vram = &vdp2.vram.u32[(base_address / 4)];
|
||||||
reinterpret_cast<uint32_t const * const>(buf.start),
|
for (uint32_t ix = 0; ix < buf.size / 4; ix += 1) {
|
||||||
buf.size);
|
const uint32_t pixels = reinterpret_cast<uint32_t const * const>(buf.start)[ix];
|
||||||
|
const uint32_t px0 = pixels >> 16 & 0xffff;
|
||||||
|
const uint32_t px1 = pixels >> 0 & 0xffff;
|
||||||
|
|
||||||
|
#define lshift(n) ((7 - n) * 2)
|
||||||
|
#define rshift(n) ((7 - n) * 4)
|
||||||
|
#define px(p, n) (((p >> lshift(n)) & 0b11) << rshift(n))
|
||||||
|
#define p0(n) (px(px0, n))
|
||||||
|
#define p1(n) (px(px1, n))
|
||||||
|
vram[ix * 2 + 0] = p0(7) | p0(6) | p0(5) | p0(4) | p0(3) | p0(2) | p0(1) | p0(0);
|
||||||
|
vram[ix * 2 + 1] = p1(7) | p1(6) | p1(5) | p1(4) | p1(3) | p1(2) | p1(1) | p1(0);
|
||||||
|
#undef p1
|
||||||
|
#undef p0
|
||||||
|
#undef px
|
||||||
|
#undef lshift
|
||||||
|
#undef rshift
|
||||||
|
}
|
||||||
|
|
||||||
return base_address;
|
return base_address;
|
||||||
}
|
}
|
||||||
@ -115,7 +131,7 @@ void main()
|
|||||||
vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a); // bits 5~0
|
vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a); // bits 5~0
|
||||||
vdp2.reg.MPCDN0 = MPABN0__N0MPD(0) | MPABN0__N0MPC(0); // bits 5~0
|
vdp2.reg.MPCDN0 = MPABN0__N0MPD(0) | MPABN0__N0MPC(0); // bits 5~0
|
||||||
|
|
||||||
uint32_t top = (sizeof (union vdp2_vram));// - ((sizeof (union vdp2_vram)) / 8);
|
uint32_t top = (sizeof (union vdp2_vram));
|
||||||
palette_data();
|
palette_data();
|
||||||
uint32_t base_address = top = cell_data(tilesets[tileset_t::overworld].tileset, top);
|
uint32_t base_address = top = cell_data(tilesets[tileset_t::overworld].tileset, top);
|
||||||
uint32_t base_pattern = base_address / 32;
|
uint32_t base_pattern = base_address / 32;
|
||||||
|
@ -1,22 +1,178 @@
|
|||||||
|
from pathlib import Path
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
import io
|
||||||
|
|
||||||
from parse import parse
|
from parse import parse
|
||||||
|
|
||||||
|
def sorted_map_headers():
|
||||||
|
# hack to remove unused/duplicate underground_path_route_7
|
||||||
|
map_headers = sorted(parse.map_headers, key=lambda m: m.name2)
|
||||||
|
return filter(lambda m: m.name1 != "UndergroundPathRoute7Copy", map_headers)
|
||||||
|
|
||||||
|
def includes():
|
||||||
|
for map_header in sorted_map_headers():
|
||||||
|
block_path = parse.maps_blocks_list[map_header.blocks()]
|
||||||
|
yield f'#include "res/{block_path}.h"'
|
||||||
|
for tileset_name in sorted(parse.tileset_constants_list):
|
||||||
|
tileset_index = parse.tileset_constants_list[tileset_name]
|
||||||
|
tileset_header = parse.tileset_headers_list[tileset_index]
|
||||||
|
|
||||||
|
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
|
||||||
|
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
|
||||||
|
|
||||||
|
yield f'#include "res/{blockset_path}.h"'
|
||||||
|
yield f'#include "res/{gfx_path}.h"'
|
||||||
|
yield ""
|
||||||
|
|
||||||
|
def struct_start_size_t():
|
||||||
|
return [
|
||||||
|
"struct start_size_t {",
|
||||||
|
"uint8_t const * const start;",
|
||||||
|
"uint32_t size;",
|
||||||
|
"};",
|
||||||
|
]
|
||||||
|
|
||||||
|
def struct_tileset_t():
|
||||||
|
tileset_names = (
|
||||||
|
f"{name.lower()},"
|
||||||
|
for name in sorted(parse.tileset_constants_list)
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
"struct tileset_t {",
|
||||||
|
"start_size_t blockset;",
|
||||||
|
"start_size_t tileset;",
|
||||||
|
"",
|
||||||
|
"enum tileset {",
|
||||||
|
*tileset_names,
|
||||||
|
"};",
|
||||||
|
"};",
|
||||||
|
]
|
||||||
|
|
||||||
|
def struct_map_t():
|
||||||
|
map_names = (
|
||||||
|
f"{map_header.name2.lower()},"
|
||||||
|
for map_header in sorted_map_headers()
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
"struct map_t {",
|
||||||
|
"start_size_t blocks;",
|
||||||
|
"enum tileset_t::tileset tileset;",
|
||||||
|
"uint32_t width;",
|
||||||
|
"uint32_t height;",
|
||||||
|
"",
|
||||||
|
"enum map {",
|
||||||
|
*map_names,
|
||||||
|
"};",
|
||||||
|
"};",
|
||||||
|
]
|
||||||
|
|
||||||
|
def binary_res(path, suffix):
|
||||||
|
# _binary_res_gfx_blocksets_overworld_bst_start
|
||||||
|
name = path.replace('/', '_').replace('.', '_')
|
||||||
|
return f"_binary_res_{name}_{suffix}"
|
||||||
|
|
||||||
|
def start_size_value(path):
|
||||||
|
start = binary_res(path, "start")
|
||||||
|
size = binary_res(path, "size")
|
||||||
|
|
||||||
|
return [
|
||||||
|
f"reinterpret_cast<uint8_t*>(&{start}),",
|
||||||
|
f"reinterpret_cast<uint32_t>(&{size}),",
|
||||||
|
]
|
||||||
|
|
||||||
|
def blockset_tileset(name: str):
|
||||||
|
tileset_index = parse.tileset_constants_list[name]
|
||||||
|
tileset_header = parse.tileset_headers_list[tileset_index]
|
||||||
|
|
||||||
|
blockset_path = parse.gfx_tilesets_list[tileset_header.blockset()]
|
||||||
|
gfx_path = parse.gfx_tilesets_list[tileset_header.gfx()]
|
||||||
|
|
||||||
|
return [
|
||||||
|
f"[tileset_t::{name.lower()}] = {{",
|
||||||
|
".blockset = {",
|
||||||
|
*start_size_value(blockset_path),
|
||||||
|
"},",
|
||||||
|
".tileset = {",
|
||||||
|
*start_size_value(gfx_path),
|
||||||
|
"}",
|
||||||
|
"},"
|
||||||
|
]
|
||||||
|
|
||||||
|
def tilesets():
|
||||||
|
yield "const tileset_t tilesets[] = {"
|
||||||
|
for tileset in sorted(parse.tileset_constants_list):
|
||||||
|
yield from blockset_tileset(tileset)
|
||||||
|
yield "};"
|
||||||
|
|
||||||
|
def map(map_header):
|
||||||
|
block_path = parse.maps_blocks_list[map_header.blocks()]
|
||||||
|
map_constant = parse.map_constants_list[map_header.name2]
|
||||||
|
return [
|
||||||
|
f"[map_t::{map_header.name2.lower()}] = {{",
|
||||||
|
".blocks = {",
|
||||||
|
*start_size_value(block_path),
|
||||||
|
"},",
|
||||||
|
f".tileset = tileset_t::{map_header.tileset.lower()},",
|
||||||
|
f".width = {map_constant.width},",
|
||||||
|
f".height = {map_constant.height},",
|
||||||
|
"},",
|
||||||
|
]
|
||||||
|
|
||||||
|
def maps():
|
||||||
|
yield "const map_t maps[] = {"
|
||||||
|
for map_header in sorted_map_headers():
|
||||||
|
yield from map(map_header)
|
||||||
|
yield "};"
|
||||||
|
|
||||||
|
def _render(out, lines):
|
||||||
|
indent = " "
|
||||||
|
level = 0
|
||||||
|
for l in lines:
|
||||||
|
if l and l[0] == "}":
|
||||||
|
level -= 2
|
||||||
|
assert level >= 0, out.getvalue()
|
||||||
|
|
||||||
|
out.write(indent * level + l + "\n")
|
||||||
|
|
||||||
|
if l and l[-1] == "{":
|
||||||
|
level += 2
|
||||||
|
|
||||||
|
if level == 0 and l and l[-1] == ";":
|
||||||
|
out.write("\n")
|
||||||
|
return out
|
||||||
|
|
||||||
|
def renderer():
|
||||||
|
out = io.StringIO()
|
||||||
|
def render(lines):
|
||||||
|
return _render(out, lines)
|
||||||
|
return render, out
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
pprint(parse.maps_blocks_list)
|
#pprint(parse.maps_blocks_list)
|
||||||
#pprint(parse.map_headers)
|
#pprint(parse.map_headers)
|
||||||
#pprint(parse.tileset_constants_list)
|
#pprint(parse.tileset_constants_list)
|
||||||
#pprint(parse.tileset_headers_list)
|
#pprint(parse.tileset_headers_list)
|
||||||
#pprint(parse.gfx_tilesets_list)
|
#pprint(parse.gfx_tilesets_list)
|
||||||
|
render, out = renderer()
|
||||||
|
|
||||||
|
render(includes())
|
||||||
|
render(struct_start_size_t())
|
||||||
|
render(struct_tileset_t())
|
||||||
|
render(struct_map_t())
|
||||||
|
render(tilesets())
|
||||||
|
render(maps())
|
||||||
|
|
||||||
|
print(out.getvalue(), end='')
|
||||||
|
|
||||||
generate()
|
generate()
|
||||||
|
|
||||||
"""
|
"""
|
||||||
map_headers[0].tileset == 'OVERWORLD'
|
map_headers[0].tileset == 'OVERWORLD'
|
||||||
map_headers[0].width() == 'PALLET_TOWN_WIDTH'
|
map_headers[0].name1 = 'PalletTown'
|
||||||
map_headers[0].height() == 'PALLET_TOWN_HEIGHT'
|
map_headers[0].name2 = 'PALLET_TOWN'
|
||||||
map_headers[0].blocks() == 'PalletTown_Blocks'
|
map_headers[0].blocks() == 'PalletTown_Blocks'
|
||||||
maps_blocks['PalletTown_Blocks'] == 'maps/PalletTown.blk'
|
map_constants_list['PALLET_TOWN'] == MapConstant(10, 19)
|
||||||
|
maps_blocks_list['PalletTown_Blocks'] == 'maps/PalletTown.blk'
|
||||||
tileset_constants_list['OVERWORLD'] == 0
|
tileset_constants_list['OVERWORLD'] == 0
|
||||||
tileset_headers_list[0].name == 'Overworld'
|
tileset_headers_list[0].name == 'Overworld'
|
||||||
tileset_headers_list[0].blockset() == 'Overworld_Block'
|
tileset_headers_list[0].blockset() == 'Overworld_Block'
|
||||||
|
@ -12,7 +12,6 @@ def tokenize_lines(lines):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MapConstant:
|
class MapConstant:
|
||||||
name: str
|
|
||||||
width: int
|
width: int
|
||||||
height: int
|
height: int
|
||||||
|
|
||||||
@ -20,8 +19,7 @@ def flatten(tokens):
|
|||||||
for macro, args in tokens:
|
for macro, args in tokens:
|
||||||
if macro == 'map_const':
|
if macro == 'map_const':
|
||||||
name, width, height = args
|
name, width, height = args
|
||||||
yield MapConstant(
|
yield name, MapConstant(
|
||||||
name,
|
|
||||||
int(width),
|
int(width),
|
||||||
int(height)
|
int(height)
|
||||||
)
|
)
|
||||||
@ -30,4 +28,7 @@ def parse(prefix):
|
|||||||
path = prefix / "constants/map_constants.asm"
|
path = prefix / "constants/map_constants.asm"
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
tokens = tokenize_lines(f.read().split("\n"))
|
tokens = tokenize_lines(f.read().split("\n"))
|
||||||
return list(flatten(tokens))
|
l = list(flatten(tokens))
|
||||||
|
d = dict(l)
|
||||||
|
assert len(l) == len(d)
|
||||||
|
return d
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
def two_bpp_index(px):
|
|
||||||
indices = {0x00: 0, 0x55: 1, 0xaa: 2, 0xff: 3}
|
|
||||||
assert px in indices, px
|
|
||||||
return indices[px]
|
|
||||||
|
|
||||||
def convert(image):
|
|
||||||
assert image.mode == 'L', image.mode
|
|
||||||
width, height = image.size
|
|
||||||
|
|
||||||
buf = bytearray(width * height // 2)
|
|
||||||
|
|
||||||
for cell_y in range(height//8):
|
|
||||||
for cell_x in range(width//8):
|
|
||||||
for y in range(8):
|
|
||||||
for x in range(8):
|
|
||||||
px = im.getpixel((cell_x * 8 + x, cell_y * 8 + y))
|
|
||||||
index = two_bpp_index(px)
|
|
||||||
buf_ix = x//2 + (4 * (cell_x * 8 + (cell_y * width) + y))
|
|
||||||
buf[buf_ix] |= (index << 4 * (1 - (x % 2)))
|
|
||||||
return buf
|
|
||||||
|
|
||||||
def debug(buf):
|
|
||||||
for row in range(len(buf) // 4):
|
|
||||||
for x in range(4):
|
|
||||||
px = buf[row * 4 + x]
|
|
||||||
print((px >> 4) & 0xf, end='')
|
|
||||||
print((px >> 0) & 0xf, end='')
|
|
||||||
print()
|
|
||||||
if (row % 8 == 7):
|
|
||||||
print()
|
|
||||||
|
|
||||||
im = Image.open(sys.argv[1])
|
|
||||||
buf = convert(im)
|
|
||||||
with open(sys.argv[2], 'wb') as f:
|
|
||||||
f.write(buf)
|
|
59
tools/png_to_nbpp.py
Normal file
59
tools/png_to_nbpp.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
def intensity_to_index(px):
|
||||||
|
indices = {0x00: 0, 0x55: 1, 0xaa: 2, 0xff: 3}
|
||||||
|
assert px in indices, px
|
||||||
|
return indices[px]
|
||||||
|
|
||||||
|
def convert(image, bpp):
|
||||||
|
assert bpp in {8, 4, 2}, bpp
|
||||||
|
px_per_byte = 8 // bpp
|
||||||
|
px_per_row = 8
|
||||||
|
bits_per_byte = 8
|
||||||
|
bytes_per_row = (px_per_row // (bits_per_byte // bpp))
|
||||||
|
|
||||||
|
assert image.mode == 'L', image.mode
|
||||||
|
width, height = image.size
|
||||||
|
|
||||||
|
buf = bytearray(width * height // px_per_byte)
|
||||||
|
|
||||||
|
for cell_y in range(height//8):
|
||||||
|
for cell_x in range(width//8):
|
||||||
|
for y in range(8):
|
||||||
|
for x in range(8):
|
||||||
|
px = im.getpixel((cell_x * 8 + x, cell_y * 8 + y))
|
||||||
|
index = intensity_to_index(px)
|
||||||
|
buf_ix = x//px_per_byte + (bytes_per_row * (cell_x * 8 + (cell_y * width) + y))
|
||||||
|
buf[buf_ix] |= (index << bpp * ((px_per_byte - 1) - (x % px_per_byte)))
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def debug(buf, bpp):
|
||||||
|
assert bpp in {8, 4, 2}, bpp
|
||||||
|
px_per_byte = 8 // bpp
|
||||||
|
px_per_row = 8
|
||||||
|
bits_per_byte = 8
|
||||||
|
bytes_per_row = (px_per_row // (bits_per_byte // bpp))
|
||||||
|
bit_mask = (2 ** bpp) - 1
|
||||||
|
|
||||||
|
for row in range(len(buf) // bytes_per_row):
|
||||||
|
for x in range(bytes_per_row):
|
||||||
|
px = buf[row * bytes_per_row + x]
|
||||||
|
for shift in reversed(range(0, px_per_row, bpp)):
|
||||||
|
print((px >> shift) & bit_mask, end='')
|
||||||
|
print()
|
||||||
|
if (row % 8 == 7):
|
||||||
|
print()
|
||||||
|
|
||||||
|
in_path = sys.argv[1]
|
||||||
|
bpp = int(sys.argv[2])
|
||||||
|
out_path = sys.argv[3]
|
||||||
|
|
||||||
|
im = Image.open(in_path)
|
||||||
|
buf = convert(im, bpp)
|
||||||
|
if 'NBPP_DEBUG' in os.environ:
|
||||||
|
debug(buf, bpp)
|
||||||
|
with open(out_path, 'wb') as f:
|
||||||
|
f.write(buf)
|
Loading…
x
Reference in New Issue
Block a user