Skip to content

Instantly share code, notes, and snippets.

@NWPlayer123
Created November 19, 2018 03:23
Show Gist options
  • Save NWPlayer123/eaa124d45b8ad31b83ebbc26be55c629 to your computer and use it in GitHub Desktop.
Save NWPlayer123/eaa124d45b8ad31b83ebbc26be55c629 to your computer and use it in GitHub Desktop.
NUT converter for Donkey Konga 2 (and others?)
from PIL import Image
from struct import unpack
from math import ceil
import sys
class I4_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGB", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #8x8 block size
for y in range(int(ceil(self.height / 8.0))):
for x in range(int(ceil(self.width / 8.0))):
self.DecodeBlock(f, x * 8, y * 8)
def DecodeBlock(self, f, x, y):
for ty in range(8): #8 pixel block height
for tx in range(4): #cuz we read 2 pixels worth
data = ord(f.read(1))
color1, color2 = self.color.I4ToColor(data)
try:
self.img[x + (tx * 2), y + ty] = color1
self.img[x + (tx * 2) + 1, y + ty] = color2
except: pass
class I8_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGB", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #8x4 block size
for y in range(int(ceil(self.height / 4.0))):
for x in range(int(ceil(self.width / 8.0))):
self.DecodeBlock(f, x * 8, y * 4)
def DecodeBlock(self, f, x, y):
for ty in range(4): #4 pixel block height
for tx in range(8): #8 pixel block width
i = ord(f.read(1))
try: self.img[x + tx, y + ty] = (i, i, i)
except: pass
class IA4_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.height = height
self.width = width
self.color = color
def DecodeImage(self, f): #8x4 block size
for y in range(int(ceil(self.height / 4.0))):
for x in range(int(ceil(self.width / 8.0))):
self.DecodeBlock(f, x * 8, y * 4)
def DecodeBlock(self, f, x, y):
for ty in range(4): #4 pixel block height
for tx in range(8): #8 pixel block width
color = self.color.IA4ToColor(ord(f.read(1)))
try: self.img[x + tx, y + ty] = color
except: pass
class IA8_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #8x4 block size
for y in range(int(ceil(self.height / 4.0))):
for x in range(int(ceil(self.width / 4.0))):
self.DecodeBlock(f, x * 4, y * 4)
def DecodeBlock(self, f, x, y):
for ty in range(4): #4 pixel block height
for tx in range(4): #4 pixel block width
a = ord(f.read(1))
i = ord(f.read(1))
try: self.img[x + tx, y + ty] = (i, i, i, a)
except: pass
class RGB565_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGB", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #4x4 block size
for y in range(int(ceil(self.height / 4.0))):
for x in range(int(ceil(self.width / 4.0))):
self.DecodeBlock(f, x * 4, y * 4)
def DecodeBlock(self, f, x, y):
for ty in range(4): #4 pixel block height
for tx in range(4): #4 pixel block width
pixel = unpack(">H", f.read(2))[0]
color = self.color.RGB565ToColor(pixel)
try: self.img[x + tx, y + ty] = color
except: pass
class RGB5A3_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f): #4x4 block size
for y in range(int(ceil(self.height / 4.0))):
for x in range(int(ceil(self.width / 4.0))):
self.DecodeBlock(f, x * 4, y * 4)
def DecodeBlock(self, f, x, y):
for ty in range(4): #4 pixel block height
for tx in range(4): #4 pixel block width
pixel = unpack(">H", f.read(2))[0]
color = self.color.RGB5A3ToColor(pixel)
try: self.img[x + tx, y + ty] = color
except: pass
class CI4_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.height = height
self.width = width
self.color = color
def DecodePalette(self, f, type, count):
palette = []
if type == 1: #RGB565
for i in range(count): #num_entries
pixel = unpack(">H", f.read(2))[0]
color = self.color.RGB565ToColor(pixel)
palette.append(color)
elif type == 2: #RGB5A3
for i in range(count): #num_entries
pixel = unpack(">H", f.read(2))[0]
color = self.color.RGB5A3ToColor(pixel)
palette.append(color)
#3, 11 = IA8(?)
return palette
def DecodeImage(self, f, palette):
for y in range(int(ceil(self.height / 8.0))):
for x in range(int(ceil(self.width / 8.0))):
self.DecodeBlock(f, palette, x * 8, y * 8)
def DecodeBlock(self, f, palette, x, y):
for ty in range(8):
for tx in range(4):
index = ord(f.read(1))
index = [index >> 4, index & 15]
for i in range(2):
try: self.img[x + (tx * 2) + i, y + ty] = palette[index[i]]
except: pass
class CI8_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.height = height
self.width = width
self.color = color
def DecodePalette(self, f, type, count):
palette = []
if type == 1: #RGB565
for i in range(count): #num_entries
pixel = unpack(">H", f.read(2))[0]
color = self.color.RGB565ToColor(pixel)
palette.append(color)
elif type == 2: #RGB5A3
for i in range(count): #num_entries
pixel = unpack(">H", f.read(2))[0]
color = self.color.RGB5A3ToColor(pixel)
palette.append(color)
#3, 11 = IA8(?)
return palette
def DecodeImage(self, f, palette):
for y in range(int(ceil(self.height / 4.0))):
for x in range(int(ceil(self.width / 8.0))):
self.DecodeBlock(f, palette, x * 8, y * 4)
def DecodeBlock(self, f, palette, x, y):
for ty in range(4):
for tx in range(8):
index = ord(f.read(1))
try: self.img[x + tx, y + ty] = palette[index]
except: pass
class RGBA32_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color #we don't need it but add anyways
def DecodeImage(self, f):
for y in range(int(ceil(self.height / 4.0))):
for x in range(int(ceil(self.width / 4.0))):
self.DecodeBlock(f, x * 4, y * 4)
def DecodeBlock(self, f, x, y):
r = [];g = [];b = [];a = []
for i in range(16):
a.append(ord(f.read(1)))
r.append(ord(f.read(1)))
for i in range(16):
g.append(ord(f.read(1)))
b.append(ord(f.read(1)))
for ty in range(4):
for tx in range(4):
i = tx + (ty * 4)
try: self.img[x + tx, y + ty] = (r[i], g[i], b[i], a[i])
except: pass
class CMPR_Texture:
def __init__(self, height, width, color):
self.image = Image.new("RGBA", (width, height))
self.img = self.image.load()
self.width = width
self.height = height
self.color = color
def DecodeImage(self, f):
for y in range(int(ceil(self.height / 8.0))):
for x in range(int(ceil(self.width / 8.0))):
self.DecodeBlock(f, x * 8, y * 8)
def DecodeBlock(self, f, x, y):
self.DecodeTile(f.read(8), x + 0, y + 0)
self.DecodeTile(f.read(8), x + 4, y + 0)
self.DecodeTile(f.read(8), x + 0, y + 4)
self.DecodeTile(f.read(8), x + 4, y + 4)
def DecodeTile(self, data, x, y):
c1, c2 = unpack(">2H", data[0:4])
r1, g1, b1 = self.color.RGB565ToColor(c1)
r2, g2, b2 = self.color.RGB565ToColor(c2)
colors = [(), (), (), ()]
colors[0] = (r1, g1, b1, 0xFF)
colors[1] = (r2, g2, b2, 0xFF)
if c1 > c2:
r3 = ((r2 - r1) >> 1) - ((r2 - r1) >> 3)
g3 = ((g2 - g1) >> 1) - ((g2 - g1) >> 3)
b3 = ((b2 - b1) >> 1) - ((b2 - b1) >> 3)
colors[2] = (r1 + r3, g1 + g3, b1 + b3, 0xFF)
colors[3] = (r2 - r3, g2 - g3, b2 - b3, 0xFF)
else:
colors[2] = (int((r1 + r2 + 1) / 2.0),
int((g1 + g2 + 1) / 2.0),
int((b1 + b2 + 1) / 2.0),
0xFF)
colors[3] = (r2, g2, b2, 0x00)
index = unpack(">4B", data[4:])
for ty in range(4):
val = index[ty]
for tx in range(4):
color = colors[(val >> 6) & 3]
try:
self.img[x + tx, y + ty] = color
val <<= 2
except: pass
class ColorConversion:
def __init__(self):
self.Bits3To8 = self.MakeDepthConversionTable(3, 8)
self.Bits8To3 = self.MakeDepthConversionTable(8, 3)
self.Bits4To8 = self.MakeDepthConversionTable(4, 8)
self.Bits8To4 = self.MakeDepthConversionTable(8, 4)
self.Bits5To8 = self.MakeDepthConversionTable(5, 8)
self.Bits8To5 = self.MakeDepthConversionTable(8, 5)
self.Bits6To8 = self.MakeDepthConversionTable(6, 8)
self.Bits8To6 = self.MakeDepthConversionTable(8, 6)
def MakeDepthConversionTable(self, a, b):
a = 1 << a
b = 1 << b
result = []
for i in range(a):
result.append(int(float(i) / (a - 1) * (b - 1)) & 0xFF)
return result
def I4ToColor(self, v):
i1 = self.Bits4To8[v >> 4]
i2 = self.Bits4To8[v & 15]
return [(i1, i1, i1), (i2, i2, i2)]
def IA4ToColor(self, v):
i = self.Bits4To8[v & 15]
a = self.Bits4To8[v >> 4]
return (i, i, i, a)
def RGB565ToColor(self, v):
return (self.Bits5To8[(v >> 11) & 31],
self.Bits6To8[(v >> 5) & 63],
self.Bits5To8[(v >> 0) & 31])
def RGB5A3ToColor(self, v):
if v & 0x8000: #RGB555
return (self.Bits5To8[(v >> 10) & 31],
self.Bits5To8[(v >> 5) & 31],
self.Bits5To8[(v >> 0) & 31],
0xFF)
else: #RGB4A3
return (self.Bits4To8[(v >> 8) & 15],
self.Bits4To8[(v >> 4) & 15],
self.Bits4To8[(v >> 0) & 15],
self.Bits3To8[(v >> 12) & 7])
def half(f):
return unpack(">H", f.read(2))[0]
def full(f):
return unpack(">I", f.read(4))[0]
palette_types = ["N/A", "RGB565", "RGB5A3", "IA8", "", "", "", "", "", "", "", "IA8(?)"]
texture_types = ["", "", "", "ARGB8", "CMPR", "CI4", "CI8", "", "", "", "I8", "IA8"]
printinfo = 0
if __name__ == "__main__":
with open(sys.argv[1], "rb") as f:
name = sys.argv[1].split(".")[:-1]
name = ".".join(name)
assert f.read(4) == b"NUTC"
assert half(f) == 0x8002 #format version
ntextures = half(f)
f.seek(0x18, 1)
color = ColorConversion()
for i in range(ntextures):
base = f.tell()
texinfo = unpack(">3I2H4B2H", f.read(0x18))
if printinfo:
#print(i) for filename debug
print("File position: %08X" % base)
print("Texture size: 0x%X" % texinfo[0])
print("Palette size: 0x%02X" % texinfo[1])
print("Image size: 0x%X" % texinfo[2])
print("Header size: 0x%X" % texinfo[3])
print("Palette count: 0x%02X" % texinfo[4])
print("Mipmap count: 0x%02X" % texinfo[6])
print("Palette format: %s" % (palette_types[texinfo[7]]))
print("Image format: %s" % (texture_types[texinfo[8]]))
print("Dimensions: %dx%d" % (texinfo[9], texinfo[10]))
print
if texinfo[8] == 3:
tex = RGBA32_Texture(texinfo[10], texinfo[9], color)
f.seek(base + texinfo[3])
tex.DecodeImage(f)
if ntextures > 1:
tex.image.save("%s%02d.png" % (name, i))
else:
tex.image.save("%s.png" % name)
elif texinfo[8] == 4:
tex = CMPR_Texture(texinfo[10], texinfo[9], color)
f.seek(base + texinfo[3])
tex.DecodeImage(f)
if ntextures > 1:
tex.image.save("%s%02d.png" % (name, i))
else:
tex.image.save("%s.png" % name)
elif texinfo[8] == 5:
tex = CI4_Texture(texinfo[10], texinfo[9], color)
f.seek(base + texinfo[3] + texinfo[2]) #skip to palette
palette = tex.DecodePalette(f, texinfo[7], texinfo[4])
f.seek(base + texinfo[3]) #skip to image data
tex.DecodeImage(f, palette)
if ntextures > 1:
tex.image.save("%s%02d.png" % (name, i))
else:
tex.image.save("%s.png" % name)
elif texinfo[8] == 6:
tex = CI8_Texture(texinfo[10], texinfo[9], color)
f.seek(base + texinfo[3] + texinfo[2]) #skip to palette
palette = tex.DecodePalette(f, texinfo[7], texinfo[4])
f.seek(base + texinfo[3]) #skip to image data
tex.DecodeImage(f, palette)
if ntextures > 1:
tex.image.save("%s%02d.png" % (name, i))
else:
tex.image.save("%s.png" % name)
elif texinfo[8] == 10:
tex = I8_Texture(texinfo[10], texinfo[9], color)
f.seek(base + texinfo[3])
tex.DecodeImage(f)
if ntextures > 1:
tex.image.save("%s%02d.png" % (name, i))
else:
tex.image.save("%s.png" % name)
elif texinfo[8] == 11:
tex = IA8_Texture(texinfo[10], texinfo[9], color)
f.seek(base + texinfo[3])
tex.DecodeImage(f)
if ntextures > 1:
tex.image.save("%s%02d.png" % (name, i))
else:
tex.image.save("%s.png" % name)
else: raise BaseException("Unknown image type: %d" % texinfo[8])
f.seek(base + texinfo[0]) #skip to next
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment