198 lines
6.1 KiB
Python
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()
|