Fix DXT1/DXT5 handling for DDR PS3 files.
This commit is contained in:
parent
5522b80994
commit
47330ca37d
@ -13,29 +13,6 @@ import struct
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
|
||||
def unpack(endian: str, _bytes: bytes) -> int:
|
||||
STRUCT_SIGNS = {
|
||||
1: 'B',
|
||||
2: 'H',
|
||||
4: 'I',
|
||||
8: 'Q'
|
||||
}
|
||||
return struct.unpack(endian + STRUCT_SIGNS[len(_bytes)], _bytes)[0]
|
||||
|
||||
|
||||
# This function converts RGB565 format to raw pixels
|
||||
def unpackRGB(packed: int) -> Tuple[int, int, int, int]:
|
||||
R = (packed >> 11) & 0x1F
|
||||
G = (packed >> 5) & 0x3F
|
||||
B = (packed) & 0x1F
|
||||
|
||||
R = (R << 3) | (R >> 2)
|
||||
G = (G << 2) | (G >> 4)
|
||||
B = (B << 3) | (B >> 2)
|
||||
|
||||
return (R, G, B, 255)
|
||||
|
||||
|
||||
class DXTBuffer:
|
||||
def __init__(self, width: int, height: int):
|
||||
self.width = width
|
||||
@ -44,53 +21,60 @@ class DXTBuffer:
|
||||
self.block_countx = self.width // 4
|
||||
self.block_county = self.height // 4
|
||||
|
||||
self.decompressed_buffer: List[Optional[bytes]] = [None] * ((width * height) * 2) # Dont ask me why
|
||||
self.decompressed_buffer: List[Optional[bytes]] = [None] * ((width * height) * 2)
|
||||
|
||||
def DXT5Decompress(self, filedata: bytes, endian: str = "<") -> bytes:
|
||||
def unpackRGB(self, packed: int) -> Tuple[int, int, int]:
|
||||
# This function converts RGB565 format to raw pixels
|
||||
R = (packed >> 11) & 0x1F
|
||||
G = (packed >> 5) & 0x3F
|
||||
B = (packed) & 0x1F
|
||||
|
||||
R = (R << 3) | (R >> 2)
|
||||
G = (G << 2) | (G >> 4)
|
||||
B = (B << 3) | (B >> 2)
|
||||
|
||||
return (R, G, B)
|
||||
|
||||
def swapbytes(self, data: bytes, swap: bool) -> bytes:
|
||||
if swap:
|
||||
return b"".join([
|
||||
data[(x + 1):(x + 2)] + data[x:(x + 1)]
|
||||
for x in range(0, len(data), 2)
|
||||
])
|
||||
return data
|
||||
|
||||
def DXT5Decompress(self, filedata: bytes, swap: bool = False) -> bytes:
|
||||
# Loop through each block and decompress it
|
||||
file = io.BytesIO(filedata)
|
||||
for row in range(self.block_county):
|
||||
for col in range(self.block_countx):
|
||||
# Get the alpha values
|
||||
a0 = unpack(endian, file.read(1))
|
||||
a1 = unpack(endian, file.read(1))
|
||||
atable = file.read(6)
|
||||
|
||||
acode0 = atable[2] | (atable[3] << 8) | (atable[4] << 16) | (atable[5] << 24)
|
||||
acode1 = atable[0] | (atable[1] << 8)
|
||||
|
||||
# Color 1 color 2, color look up table
|
||||
c0 = unpack(endian, file.read(2))
|
||||
c1 = unpack(endian, file.read(2))
|
||||
ctable = unpack(endian, file.read(4))
|
||||
# Get the alpha values, and color lookup table
|
||||
a0, a1, acode0, acode1, c0, c1, ctable = struct.unpack("<BBHIHHI", self.swapbytes(file.read(16), swap))
|
||||
|
||||
# The 4x4 Lookup table loop
|
||||
for j in range(4):
|
||||
for i in range(4):
|
||||
alpha = self.getAlpha(i, j, a0, a1, acode0, acode1)
|
||||
self.getColors(
|
||||
col * 4,
|
||||
row * 4,
|
||||
i,
|
||||
j,
|
||||
ctable,
|
||||
unpackRGB(c0),
|
||||
unpackRGB(c1),
|
||||
alpha,
|
||||
c0,
|
||||
c1,
|
||||
self.getAlpha(i, j, a0, a1, (acode1 << 16) | acode0),
|
||||
) # Set the color for the current pixel
|
||||
|
||||
return b''.join([x for x in self.decompressed_buffer if x is not None])
|
||||
|
||||
def DXT1Decompress(self, filedata: bytes, endian: str = "<") -> bytes:
|
||||
def DXT1Decompress(self, filedata: bytes, swap: bool = False) -> bytes:
|
||||
# Loop through each block and decompress it
|
||||
file = io.BytesIO(filedata)
|
||||
for row in range(self.block_county):
|
||||
for col in range(self.block_countx):
|
||||
|
||||
# Color 1 color 2, color look up table
|
||||
c0 = unpack(endian, file.read(2))
|
||||
c1 = unpack(endian, file.read(2))
|
||||
ctable = unpack(endian, file.read(4))
|
||||
c0, c1, ctable = struct.unpack("<HHI", self.swapbytes(file.read(8), swap))
|
||||
|
||||
# The 4x4 Lookup table loop
|
||||
for j in range(4):
|
||||
@ -101,8 +85,8 @@ class DXTBuffer:
|
||||
i,
|
||||
j,
|
||||
ctable,
|
||||
unpackRGB(c0),
|
||||
unpackRGB(c1),
|
||||
c0,
|
||||
c1,
|
||||
255,
|
||||
) # Set the color for the current pixel
|
||||
|
||||
@ -115,20 +99,15 @@ class DXTBuffer:
|
||||
i: int,
|
||||
j: int,
|
||||
ctable: int,
|
||||
c0: Tuple[int, int, int, int],
|
||||
c1: Tuple[int, int, int, int],
|
||||
c0: int,
|
||||
c1: int,
|
||||
alpha: int,
|
||||
) -> None:
|
||||
code = (ctable >> (2 * ((4 * j) + i))) & 0x03 # Get the color of the current pixel
|
||||
pixel_color = None
|
||||
|
||||
r0 = c0[0]
|
||||
g0 = c0[1]
|
||||
b0 = c0[2]
|
||||
|
||||
r1 = c1[0]
|
||||
g1 = c1[1]
|
||||
b1 = c1[2]
|
||||
r0, g0, b0 = self.unpackRGB(c0)
|
||||
r1, g1, b1 = self.unpackRGB(c1)
|
||||
|
||||
# Sliding scale between colors.
|
||||
if code == 0:
|
||||
@ -136,9 +115,15 @@ class DXTBuffer:
|
||||
if code == 1:
|
||||
pixel_color = (r1, g1, b1, alpha)
|
||||
if code == 2:
|
||||
pixel_color = ((2 * r0 + r1) // 3, (2 * g0 + g1) // 3, (2 * b0 + b1) // 3, alpha)
|
||||
if c0 > c1:
|
||||
pixel_color = ((2 * r0 + r1) // 3, (2 * g0 + g1) // 3, (2 * b0 + b1) // 3, alpha)
|
||||
else:
|
||||
pixel_color = ((r0 + r1) // 2, (g0 + g1) // 2, (b0 + b1) // 2, alpha)
|
||||
if code == 3:
|
||||
pixel_color = ((r0 + 2 * r1) // 3, (g0 + 2 * g1) // 3, (b0 + 2 * b1) // 3, alpha)
|
||||
if c0 > c1:
|
||||
pixel_color = ((r0 + 2 * r1) // 3, (g0 + 2 * g1) // 3, (b0 + 2 * b1) // 3, alpha)
|
||||
else:
|
||||
pixel_color = (0, 0, 0, alpha)
|
||||
|
||||
# While not surpassing the image dimensions, assign pixels the colors.
|
||||
if (x + i) < self.width and (y + j) < self.height:
|
||||
@ -146,17 +131,9 @@ class DXTBuffer:
|
||||
struct.pack('<BBBB', *pixel_color)
|
||||
)
|
||||
|
||||
def getAlpha(self, i: int, j: int, a0: int, a1: int, acode0: int, acode1: int) -> int:
|
||||
def getAlpha(self, i: int, j: int, a0: int, a1: int, acode: int) -> int:
|
||||
# Using the same method as the colors calculate the alpha values
|
||||
alpha_index = 3 * ((4 * j) + i)
|
||||
alpha_code = None
|
||||
|
||||
if alpha_index <= 12:
|
||||
alpha_code = (acode1 >> alpha_index) & 0x07
|
||||
elif alpha_index == 15:
|
||||
alpha_code = (acode1 >> 15) | ((acode0 << 1) & 0x06)
|
||||
else:
|
||||
alpha_code = (acode0 >> (alpha_index - 16)) & 0x07
|
||||
alpha_code = (acode >> (3 * ((4 * j) + i))) & 0x07
|
||||
|
||||
if alpha_code == 0:
|
||||
alpha = a0
|
||||
|
@ -633,11 +633,14 @@ class AFPFile:
|
||||
)
|
||||
elif fmt == 0x16:
|
||||
# DXT1 format. Game references D3D9 DXT1 texture format.
|
||||
# Konami seems to have screwed up with DDR PS3 where they
|
||||
# swap every other byte in the format, even though its specified
|
||||
# as little-endian by all DXT1 documentation.
|
||||
dxt = DXTBuffer(width, height)
|
||||
img = Image.frombuffer(
|
||||
'RGBA',
|
||||
(width, height),
|
||||
dxt.DXT1Decompress(raw_data[64:], endian=self.endian),
|
||||
dxt.DXT1Decompress(raw_data[64:], swap=self.endian != "<"),
|
||||
'raw',
|
||||
'RGBA',
|
||||
0,
|
||||
@ -645,11 +648,14 @@ class AFPFile:
|
||||
)
|
||||
elif fmt == 0x1A:
|
||||
# DXT5 format. Game references D3D9 DXT5 texture format.
|
||||
# Konami seems to have screwed up with DDR PS3 where they
|
||||
# swap every other byte in the format, even though its specified
|
||||
# as little-endian by all DXT5 documentation.
|
||||
dxt = DXTBuffer(width, height)
|
||||
img = Image.frombuffer(
|
||||
'RGBA',
|
||||
(width, height),
|
||||
dxt.DXT5Decompress(raw_data[64:], endian=self.endian),
|
||||
dxt.DXT5Decompress(raw_data[64:], swap=self.endian != "<"),
|
||||
'raw',
|
||||
'RGBA',
|
||||
0,
|
||||
|
Loading…
x
Reference in New Issue
Block a user