add twiddle

This commit is contained in:
Zack Buhman 2025-01-31 15:48:40 -06:00
parent 5097e0e5ac
commit 5f4b0070a6
4 changed files with 109 additions and 35 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__/

View File

@ -2,6 +2,7 @@ import struct
import sys
from PIL import Image
from twiddle import texture as twiddle_texture
class color_format:
def gbgr1555(r, g, b, a): # nintendo ds
@ -29,7 +30,7 @@ class color_format:
r5 = (r >> 3) & 31
g6 = (g >> 2) & 63
b5 = (b >> 3) & 31
return (r5 << 11) | (g5 << 5) | (b5 << 0)
return (r5 << 11) | (g6 << 5) | (b5 << 0)
def axxx4444(r, g, b, a):
a4 = (a >> 4) & 15
@ -44,12 +45,12 @@ class color_format:
("axxx4444", color_format.axxx4444),
])[s]
def convert_colors(f, convert, colors):
def convert_colors(convert, colors):
for color in colors:
value = convert(*color)
f.write(struct.pack("<H", value))
yield value
def convert_indices(f, palette, pixels):
def convert_indices(palette, pixels):
if len(palette) <= 4:
for i in range(len(pixels) // 4):
a = pixels[i * 4 + 0]
@ -73,22 +74,47 @@ def convert_indices(f, palette, pixels):
else:
assert False, len(palette)
def pack_colors(f, colors):
for value in colors:
f.write(struct.pack("<H", value))
def pack_indices(f, indices):
for value in indices:
f.write(struct.pack("<B", value))
if __name__ == "__main__":
in_file = sys.argv[1]
format = sys.argv[2]
out_file = sys.argv[3]
assert sys.argv[3] in {"twiddled", "non_twiddled"}
is_twiddled = sys.argv[3] == "twiddled"
out_file = sys.argv[4]
convert = color_format.from_string(format)
with Image.open(in_file) as im:
width, height = im.size
if not im.palette:
pixels = list(im.convert("RGBA").getdata())
colors = list(convert_colors(convert, pixels))
if is_twiddled:
new_colors = [0] * len(colors)
twiddle_texture(new_colors, colors, width, height)
colors = new_colors
with open(out_file, 'wb') as f:
convert_colors(f, convert, pixels)
pack_colors(f, colors)
else:
pixels = list(im.convert("P").getdata())
palette = list(im.palette.colors)
with open(out_file, 'wb') as f:
convert_indices(f, palette, pixels)
indices = list(convert_indices(palette, pixels))
colors = list(convert_colors(convert, [(*c, 255) for c in palette]))
if twiddle:
new_indices = [0] * len(indices)
twiddle_texture(new_indices, indices, width, height)
indices = new_indices
with open(out_file + '.pal', 'wb') as f:
convert_colors(f, convert, [(*c, 255) for c in palette])
pack_colors(f, colors)
with open(out_file, 'wb') as f:
pack_indices(f, indices)

View File

@ -8,17 +8,19 @@ from PIL import Image
from parse_material import parse_mtl_file
material_filenames = sys.argv[1:]
def render_material_enum(newmtl_mapkd):
def render_material_enum(prefix, newmtl_mapkd):
yield f"enum material {{"
for newmtl, mapkd in newmtl_mapkd:
yield f"{newmtl.name},";
yield f"{prefix}_{newmtl.name},";
yield "};"
def render_pixel_descriptor(offset, mapkd, dimensions):
def transform_filename(prefix, name):
name = name.replace("/", "_")
return f"model_{prefix}_{name}"
def render_pixel_descriptor(prefix, offset, mapkd, dimensions):
name, _ext = mapkd.name.rsplit('.', maxsplit=1)
pixel_name = f"{name}_data"
pixel_name = f"{transform_filename(prefix, name)}_data"
width, height = dimensions
yield ".pixel = {"
yield f".start = (uint8_t *)&_binary_{pixel_name}_start,"
@ -54,12 +56,10 @@ def round_up_colors(name, colors):
else:
assert False, (name, colors)
def image_metadata(mapkd):
def image(mapkd):
path = texture_path(mapkd.name)
with Image.open(path) as im:
dimensions = im.size
colors = len(im.palette.colors)
return dimensions, colors
im = Image.open(path)
return im
def round_up_n(x, multiple):
return ((x + multiple - 1) // multiple) * multiple
@ -68,27 +68,30 @@ def bytes_per_pixel(palette_size):
bits_per_pixel = int(log(palette_size)/log(2))
return bits_per_pixel / 8
def render_material(offset, mapkd):
dimensions, colors = image_metadata(mapkd)
palette_size = round_up_colors(mapkd.name, colors)
def render_material(prefix, offset, mapkd):
im = image(mapkd)
dimensions = im.size
#colors = len(im.palette.colors)
#palette_size = round_up_colors(mapkd.name, colors)
# pixel descriptor
yield from render_pixel_descriptor(offset, mapkd, dimensions)
pixel_size = bytes_per_pixel(palette_size) * dimensions[0] * dimensions[1]
#pixel_size = 2 * dimensions[0] * dimensions[1]
yield from render_pixel_descriptor(prefix, offset, mapkd, dimensions)
#pixel_size = bytes_per_pixel(palette_size) * dimensions[0] * dimensions[1]
pixel_size = 2 * dimensions[0] * dimensions[1]
assert int(pixel_size) == pixel_size
offset.pixel += round_up_n(int(pixel_size), 8)
# palette descriptor
yield from render_palette_descriptor(offset, mapkd, palette_size)
offset.palette += round_up_n(colors * 2, 16)
#yield from render_palette_descriptor(offset, mapkd, palette_size)
#offset.palette += round_up_n(colors * 2, 16)
def render_materials(newmtl_mapkd):
yield "struct material_descriptor material[] = {"
def render_materials(prefix, newmtl_mapkd):
yield f"const struct material_descriptor {prefix}_material[] = {{"
offset = Offset(0, 0)
for newmtl, mapkd in newmtl_mapkd:
yield f"[{newmtl.name}] = {{"
yield from render_material(offset, mapkd)
yield f"[{prefix}_{newmtl.name}] = {{"
yield from render_material(prefix, offset, mapkd)
yield "},"
yield "};"
@ -101,7 +104,8 @@ def render_header():
yield ""
if __name__ == "__main__":
material_filenames = sys.argv[1:]
prefix = sys.argv[1]
material_filenames = sys.argv[2:]
assert material_filenames
newmtl_mapkd = []
for material_filename in material_filenames:
@ -114,6 +118,6 @@ if __name__ == "__main__":
render, out = renderer()
render(render_header())
render(render_material_enum(newmtl_mapkd))
render(render_materials(newmtl_mapkd))
render(render_material_enum(prefix, newmtl_mapkd))
render(render_materials(prefix, newmtl_mapkd))
sys.stdout.write(out.getvalue())

43
twiddle.py Normal file
View File

@ -0,0 +1,43 @@
def log2(n):
return {
8: 3,
16: 4,
32: 5,
64: 6,
128: 7,
256: 8,
512: 9,
1024: 10,
}[n]
def from_xy(x, y, width, height):
# maximum texture size : 1024x1024
# maximum 1-dimensional index: 0xfffff
# bits : 19-0
# y bits: 0, 2, 4, 6, 8, 10, 12, 14, 16, 18
# x bits: 1, 3, 5, 7, 9, 11, 13, 15, 17, 19
width_max = log2(width)
height_max = log2(height)
twiddle_ix = 0
for i in range(20 // 2):
if (i < width_max and i < height_max):
twiddle_ix |= ((y >> i) & 1) << (i * 2 + 0)
twiddle_ix |= ((x >> i) & 1) << (i * 2 + 1)
elif (i < width_max):
twiddle_ix |= ((x >> i) & 1) << (i + height_max)
elif (i < height_max):
twiddle_ix |= ((y >> i) & 1) << (i + width_max)
else:
break
return twiddle_ix
def texture(dst, src, width, height):
for y in range(height):
for x in range(width):
twiddle_ix = from_xy(x, y, width, height)
value = src[y * width + x]
dst[twiddle_ix] = value