model_generator/color_convert.py

198 lines
6.1 KiB
Python

import struct
import sys
from PIL import Image
from twiddle import texture as twiddle_texture, npot
class color_format:
def gbgr1555(r, g, b, a): # nintendo ds
r5 = (r >> 3) & 31
g6 = (g >> 3) & 31
g6_l = (g >> 2) & 1
b5 = (b >> 3) & 31
return (g6_l << 15) | (b5 << 10) | (g6 << 5) | (r5 << 0)
def argb4444(r, g, b, a):
a4 = (a >> 4) & 15
r4 = (r >> 4) & 15
g4 = (g >> 4) & 15
b4 = (b >> 4) & 15
return (a4 << 12) | (r4 << 8) | (g4 << 4) | (b4 << 0)
def argb1555(r, g, b, a):
a1 = (a >> 7) & 1
r5 = (r >> 3) & 31
g5 = (g >> 3) & 31
b5 = (b >> 3) & 31
return (a1 << 15) | (r5 << 10) | (g5 << 5) | (b5 << 0)
def rgb565(r, g, b, a):
r5 = (r >> 3) & 31
g6 = (g >> 2) & 63
b5 = (b >> 3) & 31
return (r5 << 11) | (g6 << 5) | (b5 << 0)
def axxx4444(r, g, b, a):
a4 = (a >> 4) & 15
return (a4 << 12)
def from_string(s):
return dict([
("gbgr1555", color_format.gbgr1555),
("argb4444", color_format.argb4444),
("argb1555", color_format.argb1555),
("rgb565", color_format.rgb565),
("axxx4444", color_format.axxx4444),
])[s]
class unconvert_color_format:
def argb1555(value):
a = (value >> 15) & 0b1
r = (value >> 10) & 0b11111
g = (value >> 5 ) & 0b11111
b = (value >> 0 ) & 0b11111
return (r * 8, g * 8, b * 8, a * 255)
def from_string(s):
return dict([
#("gbgr1555", unconvert_color_format.gbgr1555),
#("argb4444", unconvert_color_format.argb4444),
("argb1555", unconvert_color_format.argb1555),
#("rgb565", unconvert_color_format.rgb565),
#("axxx4444", unconvert_color_format.axxx4444),
])[s]
def convert_colors(convert, colors):
for color in colors:
value = convert(*color)
yield value
def convert_indices(palette, pixels):
if len(palette) <= 4:
for i in range(len(pixels) // 4):
a = pixels[i * 4 + 0]
b = pixels[i * 4 + 1]
c = pixels[i * 4 + 2]
d = pixels[i * 4 + 3]
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)
assert False, len(palette)
yield pixel
elif len(palette) <= 16:
for i in range(len(pixels) // 2):
a = pixels[i * 2 + 0]
b = pixels[i * 2 + 1]
assert a <= 15 and b <= 15, (a, b)
pixel = (b << 4) | (a << 0)
yield pixel
elif len(palette) <= 256:
for pixel in pixels:
assert pixel <= 255
yield pixel
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))
def mip_levels(n):
while n > 0:
yield n
n = n >> 1
def resize(im, l):
new = im.resize(
size=(l, l),
resample=Image.Resampling.LANCZOS,
)
#new.save(f"{l:03}.png")
return new
def generate_mips(im):
w, h = im.size
assert w == h and npot(w) == w, (w, h)
assert w >= 8, (w, h)
images = [im] + [resize(im, l) for l in mip_levels(w >> 1)]
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(mip.convert("RGBA").getdata())
colors = list(convert_colors(convert, pixels))
assert len(colors) == width * height, len(colors)
if is_twiddled:
npot_height = npot(height)
new_colors = [0] * width * npot_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)
def write_png_reference(size, filename, unconvert, colors):
im = Image.new("RGBA", size)
data = [unconvert(value) for value in colors]
im.putdata(data)
im.save(filename)
def main():
in_file = sys.argv[1]
format = sys.argv[2]
assert sys.argv[3] in {"twiddled", "non_twiddled"}
is_twiddled = sys.argv[3] == "twiddled"
assert sys.argv[4] in {"mipmapped", "non_mipmapped"}
is_mipmapped = sys.argv[4] == "mipmapped"
out_file = sys.argv[5]
ref_file = sys.argv[6] if len(sys.argv) > 6 else None
convert = color_format.from_string(format)
with Image.open(in_file) as im:
width, height = im.size
assert width <= 1024 and height <= 1024, (width, height)
assert not im.palette
#if not im.palette:
if True:
with open(out_file, 'wb') as f:
if is_mipmapped:
f.write(bytes([0] * 6))
write_mips(f, im, is_twiddled, convert)
else:
write_mip(f, im, is_twiddled, convert)
"""
if ref_file is not None:
unconvert = unconvert_color_format.from_string(format)
write_png_reference(im.size, ref_file, unconvert, colors)
"""
else:
assert False
pixels = list(im.convert("P").getdata())
palette = list(im.palette.colors)
indices = list(convert_indices(palette, pixels))
colors = list(convert_colors(convert, [(*c, 255) for c in palette]))
if is_twiddled:
new_indices = [0] * len(indices)
twiddle_texture(new_indices, indices, width, height)
indices = new_indices
with open(out_file + '.pal', 'wb') as f:
pack_colors(f, colors)
with open(out_file, 'wb') as f:
pack_indices(f, indices)
if __name__ == "__main__":
main()