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(" 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: 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) 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()