color_convert: add mipmap generation

This commit is contained in:
Zack Buhman 2025-04-25 22:56:23 -05:00
parent 5f4b0070a6
commit 9b07ba183c
2 changed files with 74 additions and 14 deletions

View File

@ -2,7 +2,7 @@ import struct
import sys import sys
from PIL import Image from PIL import Image
from twiddle import texture as twiddle_texture from twiddle import texture as twiddle_texture, npot
class color_format: class color_format:
def gbgr1555(r, g, b, a): # nintendo ds def gbgr1555(r, g, b, a): # nintendo ds
@ -59,18 +59,19 @@ def convert_indices(palette, pixels):
d = pixels[i * 4 + 3] d = pixels[i * 4 + 3]
assert a <= 3 and b <= 3 and c <= 3 and d <= 3, (a, b, c, d) assert a <= 3 and b <= 3 and c <= 3 and d <= 3, (a, b, c, d)
pixel = (d << 6) | (c << 4) | (b << 2) | (a << 0) pixel = (d << 6) | (c << 4) | (b << 2) | (a << 0)
f.write(struct.pack("<B", pixel)) assert False, len(palette)
yield pixel
elif len(palette) <= 16: elif len(palette) <= 16:
for i in range(len(pixels) // 2): for i in range(len(pixels) // 2):
a = pixels[i * 2 + 0] a = pixels[i * 2 + 0]
b = pixels[i * 2 + 1] b = pixels[i * 2 + 1]
assert a <= 15 and b <= 15, (a, b) assert a <= 15 and b <= 15, (a, b)
pixel = (b << 4) | (a << 0) pixel = (b << 4) | (a << 0)
f.write(struct.pack("<B", pixel)) yield pixel
elif len(palette) <= 256: elif len(palette) <= 256:
for pixel in pixels: for pixel in pixels:
assert pixel <= 255 assert pixel <= 255
f.write(struct.pack("<B", pixel)) yield pixel
else: else:
assert False, len(palette) assert False, len(palette)
@ -82,33 +83,74 @@ def pack_indices(f, indices):
for value in indices: for value in indices:
f.write(struct.pack("<B", value)) f.write(struct.pack("<B", value))
def mip_levels(n):
while True:
n = n >> 1
yield n
if n == 1:
break
def generate_mips(im):
w, h = im.size
assert w == h and npot(w) == w, (w, h)
assert w >= 8, (w, h)
images = [im] + [
im.resize(
size=(l, l),
resample=Image.Resampling.LANCZOS,
)
for l in mip_levels(w)
]
return list(reversed(images))
def write_mip(f, mip: Image, is_twiddled: bool, convert):
width, height = mip.size
print("mip", width, "offset", f"{f.tell():06x}")
pixels = list(im.convert("RGBA").getdata())
colors = list(convert_colors(convert, pixels))
if is_twiddled:
new_colors = [0] * width * height
max_twiddle_ix = twiddle_texture(new_colors, colors, width, height)
assert max_twiddle_ix + 1 == width * height, (max_twiddle_ix, width, height)
colors = new_colors[:max_twiddle_ix+1]
pack_colors(f, colors)
def write_mips(f, im: Image, is_twiddled: bool, convert):
for mip in generate_mips(im):
write_mip(f, mip, is_twiddled, convert)
if __name__ == "__main__": if __name__ == "__main__":
in_file = sys.argv[1] in_file = sys.argv[1]
format = sys.argv[2] format = sys.argv[2]
assert sys.argv[3] in {"twiddled", "non_twiddled"} assert sys.argv[3] in {"twiddled", "non_twiddled"}
is_twiddled = sys.argv[3] == "twiddled" is_twiddled = sys.argv[3] == "twiddled"
out_file = sys.argv[4] assert sys.argv[4] in {"mipmapped", "non_mipmapped"}
is_mipmapped = sys.argv[4] == "mipmapped"
out_file = sys.argv[5]
convert = color_format.from_string(format) convert = color_format.from_string(format)
with Image.open(in_file) as im: with Image.open(in_file) as im:
width, height = im.size width, height = im.size
if not im.palette: assert width <= 1024 and height <= 1024, (width, height)
pixels = list(im.convert("RGBA").getdata()) assert not im.palette
colors = list(convert_colors(convert, pixels)) #if not im.palette:
if is_twiddled: if True:
new_colors = [0] * len(colors)
twiddle_texture(new_colors, colors, width, height)
colors = new_colors
with open(out_file, 'wb') as f: with open(out_file, 'wb') as f:
pack_colors(f, colors) if is_mipmapped:
f.write(bytes([0] * 6))
write_mips(f, im, is_twiddled, convert)
else:
write_mip(f, im, is_twiddled, convert)
else: else:
pixels = list(im.convert("P").getdata()) pixels = list(im.convert("P").getdata())
palette = list(im.palette.colors) palette = list(im.palette.colors)
indices = list(convert_indices(palette, pixels)) indices = list(convert_indices(palette, pixels))
colors = list(convert_colors(convert, [(*c, 255) for c in palette])) colors = list(convert_colors(convert, [(*c, 255) for c in palette]))
if twiddle: if is_twiddled:
new_indices = [0] * len(indices) new_indices = [0] * len(indices)
twiddle_texture(new_indices, indices, width, height) twiddle_texture(new_indices, indices, width, height)
indices = new_indices indices = new_indices

View File

@ -1,5 +1,8 @@
def log2(n): def log2(n):
return { return {
1: 0,
2: 1,
4: 2,
8: 3, 8: 3,
16: 4, 16: 4,
32: 5, 32: 5,
@ -35,9 +38,24 @@ def from_xy(x, y, width, height):
return twiddle_ix return twiddle_ix
def npot(v):
v -= 1;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v += 1;
return v
def texture(dst, src, width, height): def texture(dst, src, width, height):
#pot_height = npot(height)
max_twiddle_ix = -1
for y in range(height): for y in range(height):
for x in range(width): for x in range(width):
twiddle_ix = from_xy(x, y, width, height) twiddle_ix = from_xy(x, y, width, height)
value = src[y * width + x] value = src[y * width + x]
dst[twiddle_ix] = value dst[twiddle_ix] = value
if twiddle_ix > max_twiddle_ix:
max_twiddle_ix = twiddle_ix
return max_twiddle_ix