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 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 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): info = unpack(">HBBII", f.read(12)) f.seek(info[4]) #data addr palette = [] if info[3] == 0: pass elif info[3] == 1: pass elif info[3] == 2: #RGB5A3 for i in range(info[0]): #num_entries pixel = unpack(">H", f.read(2))[0] color = self.color.RGB5A3ToColor(pixel) palette.append(color) 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]) if __name__ == "__main__": with open(sys.argv[1], "rb") as f: name = sys.argv[1].split(".")[:-1] name = ".".join(name) imginfo = unpack(">2B2H2B2H2I2BH2BHI", f.read(0x20)) #we're now at 0x20 aka start of raw data color = ColorConversion() if imginfo[0] == 0: tex = I4_Texture(imginfo[3], imginfo[2], color) tex.DecodeImage(f) tex.image.save("%s.png" % name) elif imginfo[0] == 1: tex = I8_Texture(imginfo[3], imginfo[2], color) tex.DecodeImage(f) tex.image.save("%s.png" % name) elif imginfo[0] == 2: tex = IA4_Texture(imginfo[3], imginfo[2], color) tex.DecodeImage(f) tex.image.save("%s.png" % name) elif imginfo[0] == 4: tex = RGB565_Texture(imginfo[3], imginfo[2], color) tex.DecodeImage(f) tex.image.save("%s.png" % name) elif imginfo[0] == 5: tex = RGB5A3_Texture(imginfo[3], imginfo[2], color) tex.DecodeImage(f) tex.image.save("%s.png" % name) elif imginfo[0] == 6: tex = RGBA32_Texture(imginfo[3], imginfo[2], color) tex.DecodeImage(f) tex.image.save("%s.png" % name) elif imginfo[0] == 7: tex = RGB565_Texture(imginfo[3], imginfo[2], color) tex.DecodeImage(f) tex.image.save("%s.png" % name) #TODO: double check these: elif imginfo[0] == 9: tex = CI8_Texture(imginfo[3], imginfo[2], color) assert imginfo[8] != 0 #palette f.seek(imginfo[8]) palette = tex.DecodePalette(f) tex.DecodeImage(f, palette) tex.image.save("%s.png" % name) elif imginfo[0] == 14: tex = CMPR_Texture(imginfo[3], imginfo[2], color) tex.DecodeImage(f) tex.image.save("%s.png" % name)