ikaruga/PowerVR.py
2023-11-26 16:13:32 +00:00

500 lines
12 KiB
Python

"""
MIT License
Copyright (c) 2020 Benjamin Collins (kion @ dashgl.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import os
import sys
import png
import math
import struct
from NinjaTexture import NinjaTexture
class PowerVR: # {
# Cache Positions
LOOKUP_TABLE = {}
# Color Formats
ARGB_1555 = 0x00
RGB_565 = 0x01
ARGB_4444 = 0x02
YUV_422 = 0x03
BUMP = 0x04
RGB_555 = 0x05
ARGB_8888 = 0x06
YUV_420 = 0x06
# Data Formats
TWIDDLED = 0x01
TWIDDLED_MM = 0x02
VQ = 0x03
VQ_MM = 0x04
PALETTIZE4 = 0x05
PALETTIZE4_MM = 0x06
PALETTIZE8 = 0x07
PALETTIZE8_MM = 0x08
RECTANGLE = 0x09
STRIDE = 0x0B
TWIDDLED_RECTANGLE = 0x0D
ABGR = 0x0E
ABGR_MM = 0x0F
SMALLVQ = 0x10
SMALLVQ_MM = 0x11
TWIDDLED_MM_ALIAS = 0x12
def __init__(self, filename): # {
# File Information
self.debug = False
base = os.path.basename(filename)
self.name = os.path.splitext(base)[0]
print(self.name)
self.path = 'input/' + filename
# Textures
self.tex_list = []
self.pal_list = []
return None
def parse(self):
self.file = open(self.path, 'rb')
self.length = os.path.getsize(self.path)
while self.file.tell() < self.length:
bytes = self.file.read(4)
if bytes == b'PVMH':
self.readPvm()
break
elif bytes == b'PVRT':
print("-- READ PVRT--")
bytes = self.file.read(4)
p = struct.unpack('I', bytes)
pvrt_len = p[0]
tex = NinjaTexture()
tex.setIndex(0)
tex.setName(self.name)
raw = self.readPvr()
tex.setRaw(raw)
tex.setWidth(self.width)
tex.setHeight(self.height)
self.tex_list.append(tex)
break
for tex in self.tex_list:
if not tex.raw:
continue
imgName = "output/" + tex.name + '.png'
w = png.Writer(tex.width, tex.height, greyscale=False, alpha=True,compression=9)
f = open(imgName, 'wb')
w.write(f, tex.raw)
f.close()
f = open(imgName, 'rb')
data = f.read()
f.close()
tex.setData(data)
os.remove(imgName)
self.file.close()
return self.tex_list
def readPvm(self):
if self.debug:
print("--- READING PVM FIL ---")
bytes = self.file.read(4)
p = struct.unpack('I', bytes)
pvmh_len = p[0]
save_pos = self.file.tell()
bytes = self.file.read(4)
p = struct.unpack('HH', bytes)
flags = p[0]
tex_count = p[1]
for i in range(tex_count):
bytes = self.file.read(2)
p = struct.unpack('H', bytes)
tex = NinjaTexture()
index = p[0]
tex.setIndex(p[0])
if flags & 0x08:
bytes = self.file.read(0x1c)
name = bytes.decode()
name = name.replace('\x00', '')
tex.setName(name)
if self.debug:
print("[%02d] %s" % (index, name))
if flags & 0x04:
bytes = self.file.read(2)
p = struct.unpack('H', bytes)
format = p[0] >> 8
if flags & 0x02:
bytes = self.file.read(2)
p = struct.unpack('H', bytes)
size = p[0]
lo_bytes = size & 0x0f
hi_bytes = (size >> 4) & 0x0f
width = (1 << (lo_bytes + 2))
height = (1 << (hi_bytes + 2))
if flags & 0x01:
bytes = self.file.read(4)
p = struct.unpack('I', bytes)
tex.setGlobalIndex(p[0])
self.tex_list.append(tex)
self.file.seek(save_pos + pvmh_len, 0)
tex_len = len(self.tex_list)
for tex in self.tex_list:
if self.debug:
print("PVR Reading %d of %d" % (tex.index, tex_len))
print("Position: 0x%08x" % self.file.tell())
while self.file.tell() < self.length:
bytes = self.file.read(4)
if bytes != b'PVRT':
continue
break
if self.debug:
print("Found PVRT @ 0x%08x" % (self.file.tell() - 4))
bytes = self.file.read(4)
p = struct.unpack('I', bytes)
save_pos = self.file.tell()
pvrt_len = p[0]
raw = self.readPvr()
tex.setRaw(raw)
tex.setWidth(self.width)
tex.setHeight(self.height)
self.file.seek(pvrt_len + save_pos, 0)
return None
def readPvr(self):
# Read Header
bytes = self.file.read(8)
p = struct.unpack('BBHHH', bytes)
self.color_format = p[0]
self.data_format = p[1]
self.width = p[3]
self.height = p[4]
format_list = [
"",
"TWIDDLED",
"TWIDDLED_MM",
"VQ",
"VQ_MM",
"PALETTIZE4",
"PALETTIZE4_MM",
"PALETTIZE8",
"PALETTIZE8_MM",
"RECTANGLE",
"",
"STRIDE",
"",
"TWIDDLED_RECTANGLE",
"ABGR",
"ABGR_MM",
"SMALLVQ",
"SMALLVQ_MM",
"TWIDDLED_MM_ALIAS"
]
if self.debug:
print("Data Format: %s" % format_list[self.data_format])
print("Width: %d" % self.width)
print("Height: %d" % self.height)
self.isTwiddled = False
self.isMipmap = False
self.isCompressed = False
self.codebook_size = 256
self.bitmap = [[0 for x in range(self.width * 4)] for y in range(self.height)]
twiddled = (
PowerVR.TWIDDLED,
PowerVR.TWIDDLED_MM,
PowerVR.TWIDDLED_RECTANGLE,
PowerVR.TWIDDLED_MM_ALIAS
)
mipmap = (
PowerVR.TWIDDLED_MM,
PowerVR.PALETTIZE4_MM,
PowerVR.PALETTIZE8_MM,
PowerVR.ABGR_MM,
PowerVR.VQ_MM,
PowerVR.SMALLVQ_MM
)
compressed = (
PowerVR.VQ,
PowerVR.VQ_MM,
PowerVR.SMALLVQ,
PowerVR.SMALLVQ_MM
)
palette = (
PowerVR.PALETTIZE4,
PowerVR.PALETTIZE4_MM,
PowerVR.PALETTIZE8,
PowerVR.PALETTIZE8_MM
)
not_supported = (
PowerVR.STRIDE,
PowerVR.ABGR,
PowerVR.ABGR_MM
)
if self.data_format in compressed:
self.isCompressed = True
if self.data_format in twiddled:
self.isTwiddled = True
if self.data_format in mipmap:
self.isMipmap = True
if self.data_format in palette:
print("NEED TO DEFINE A PALETTE!!!")
sys.exit()
if self.data_format in not_supported:
print("THIS DATA FORMAT IS NOT SUPPORTED")
sys.exit()
if self.data_format == PowerVR.SMALLVQ:
if self.width <= 16:
self.codebook_size = 16
elif self.width == 32:
self.codebook_size = 32
elif self.width == 64:
self.codebook_size = 128
else:
self.codebook_size = 256
elif self.data_format == PowerVR.SMALLVQ_MM:
if self.width <= 16:
self.codebook_size = 16
elif self.width == 32:
self.codebook_size = 64
else:
self.codebook_size = 256
if self.isCompressed:
self.codebook = []
for i in range(self.codebook_size):
bytes = self.file.read(8)
color = struct.unpack('HHHH', bytes)
entry = []
for p in range(4):
if self.color_format == 0:
rgba = self.ARGB_1555(color[p])
elif self.color_format == 1:
rgba = self.RGB_565(color[p])
elif self.color_format == 2:
rgba = self.ARGB_4444(color[p])
entry.append(rgba)
self.codebook.append(entry)
if self.isMipmap:
seek_ofs = self.get_mipmap_size()
self.file.read(seek_ofs)
self.startOfs = self.file.tell()
if self.data_format == PowerVR.TWIDDLED_RECTANGLE:
size = 0
dir = ''
count = 0
if self.width > self.height:
size = self.height
dir = 'h'
count = int(self.width / self.height)
else:
size = self.width
dir = 'w'
count = int(self.height / self.width)
if self.debug:
print("Size: %d" % size)
for n in range(count):
if self.debug:
print("File Position: 0x%08x" % self.file.tell())
print("Reading square %d of %d" % (n, count))
self.startOfs = self.file.tell()
for y in range(size):
for x in range(size):
i = self.untwiddle(x, y)
self.file.seek(self.startOfs + (i * 2), 0)
b = self.file.read(2)
color = struct.unpack('H', b)[0]
if self.color_format == 0:
rgba = self.ARGB_1555(color)
elif self.color_format == 1:
rgba = self.RGB_565(color)
elif self.color_format == 2:
rgba = self.ARGB_4444(color)
if self.width > self.height:
self.bitmap[y][(n*size*4) + x*4 + 0] = rgba[0]
self.bitmap[y][(n*size*4) + x*4 + 1] = rgba[1]
self.bitmap[y][(n*size*4) + x*4 + 2] = rgba[2]
self.bitmap[y][(n*size*4) + x*4 + 3] = rgba[3]
else:
self.bitmap[y + (n*size)][x*4 + 0] = rgba[0]
self.bitmap[y + (n*size)][x*4 + 1] = rgba[1]
self.bitmap[y + (n*size)][x*4 + 2] = rgba[2]
self.bitmap[y + (n*size)][x*4 + 3] = rgba[3]
return self.bitmap
if self.isTwiddled:
for y in range(self.height):
for x in range(self.width):
i = self.untwiddle(x, y)
self.file.seek(self.startOfs + (i * 2), 0)
b = self.file.read(2)
color = struct.unpack('H', b)[0]
if self.color_format == 0:
rgba = self.ARGB_1555(color)
elif self.color_format == 1:
rgba = self.RGB_565(color)
elif self.color_format == 2:
rgba = self.ARGB_4444(color)
self.bitmap[y][x*4 + 0] = rgba[0]
self.bitmap[y][x*4 + 1] = rgba[1]
self.bitmap[y][x*4 + 2] = rgba[2]
self.bitmap[y][x*4 + 3] = rgba[3]
return self.bitmap
elif self.isCompressed:
for y in range(int(self.height / 2)):
for x in range(int(self.width / 2)):
i = self.untwiddle(x, y)
self.file.seek(self.startOfs + i , 0)
b = self.file.read(1)
index = struct.unpack('B', b)[0]
rgba = self.codebook[index]
# Thanks to VincentNL for order fix
bitmap[y*2 + 0][x*8 + 0] = rgba[0][0]
bitmap[y*2 + 0][x*8 + 1] = rgba[0][1]
bitmap[y*2 + 0][x*8 + 2] = rgba[0][2]
bitmap[y*2 + 0][x*8 + 3] = rgba[0][3]
bitmap[y*2 + 0][x*8 + 4] = rgba[2][0]
bitmap[y*2 + 0][x*8 + 5] = rgba[2][1]
bitmap[y*2 + 0][x*8 + 6] = rgba[2][2]
bitmap[y*2 + 0][x*8 + 7] = rgba[2][3]
bitmap[y*2 + 1][x*8 + 0] = rgba[1][0]
bitmap[y*2 + 1][x*8 + 1] = rgba[1][1]
bitmap[y*2 + 1][x*8 + 2] = rgba[1][2]
bitmap[y*2 + 1][x*8 + 3] = rgba[1][3]
bitmap[y*2 + 1][x*8 + 4] = rgba[3][0]
bitmap[y*2 + 1][x*8 + 5] = rgba[3][1]
bitmap[y*2 + 1][x*8 + 6] = rgba[3][2]
bitmap[y*2 + 1][x*8 + 7] = rgba[3][3]
return self.bitmap
return None
def get_mipmap_size(self):
mipCount = 0
seek_ofs = 0
width = self.width
while width :
mipCount = mipCount + 1
width = int(width / 2)
while mipCount:
mipWidth = (self.width >> (mipCount - 1))
mipHeight = (self.height >> (mipCount - 1))
mipSize = mipWidth * mipHeight
mipCount = mipCount - 1
if mipCount > 0:
if self.isCompressed:
seek_ofs = seek_ofs + int(mipSize / 4)
else:
seek_ofs = seek_ofs + (2 * mipSize)
else:
if self.isCompressed:
seek_ofs = seek_ofs + 1
else:
seek_ofs = seek_ofs + 2
return seek_ofs
def untwiddle(self, x, y):
key = "{0}_{1}".format(x,y)
if key in PowerVR.LOOKUP_TABLE:
return PowerVR.LOOKUP_TABLE[key]
def UntwiddleValue(val):
untwiddled = 0
for i in range(10):
shift = int(math.pow(2, i))
if val & shift :
untwiddled = untwiddled | (shift << i)
return untwiddled
pos = UntwiddleValue(y) | UntwiddleValue(x) << 1
PowerVR.LOOKUP_TABLE[key] = pos
return pos
def ARGB_1555 (self, v):
a = 0xFF if (v & (1<<15)) else 0
r = (v >> (10-3)) & 0xf8
g = (v >> (5-3)) & 0xf8
b = (v << 3) & 0xf8
return (r,g,b,a)
def ARGB_4444 (self, v):
a = (v >> (12-4)) & 0xf0
r = (v >> (8-4)) & 0xf0
g = (v >> (4-4)) & 0xf0
b = (v << 4) & 0xf0
return (r,g,b,a)
def RGB_565(self, v):
a = 0xff
r = (v >> (11-3)) & (0x1f<<3)
g = (v >> (5-2)) & (0x3f<<2)
b = (v << 3) & (0x1f<<3)
return (r,g,b,a)