2020-11-06 03:08:21 +01:00
|
|
|
"""
|
|
|
|
S3TC DXT1/DXT5 Texture Decompression
|
|
|
|
|
|
|
|
Adapted from https://github.com/leamsii/Python-DXT-Decompress to add types
|
|
|
|
and take in bytes instead of file pointers. Inspired by Benjamin Dobell.
|
|
|
|
|
|
|
|
Original C++ code https://github.com/Benjamin-Dobell/s3tc-dxt-decompression
|
|
|
|
"""
|
|
|
|
|
|
|
|
import io
|
|
|
|
import struct
|
|
|
|
|
|
|
|
from typing import List, Optional, Tuple
|
|
|
|
|
|
|
|
|
|
|
|
class DXTBuffer:
|
|
|
|
def __init__(self, width: int, height: int):
|
|
|
|
self.width = width
|
|
|
|
self.height = height
|
|
|
|
|
|
|
|
self.block_countx = self.width // 4
|
|
|
|
self.block_county = self.height // 4
|
|
|
|
|
2020-12-22 00:40:18 +01:00
|
|
|
self.decompressed_buffer: List[Optional[bytes]] = [None] * ((width * height) * 2)
|
|
|
|
|
|
|
|
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)
|
2020-11-06 03:08:21 +01:00
|
|
|
|
2020-12-22 00:40:18 +01:00
|
|
|
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:
|
2020-11-06 03:08:21 +01:00
|
|
|
# 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):
|
2020-12-22 00:40:18 +01:00
|
|
|
# Get the alpha values, and color lookup table
|
|
|
|
a0, a1, acode0, acode1, c0, c1, ctable = struct.unpack("<BBHIHHI", self.swapbytes(file.read(16), swap))
|
2020-11-06 03:08:21 +01:00
|
|
|
|
|
|
|
# The 4x4 Lookup table loop
|
|
|
|
for j in range(4):
|
|
|
|
for i in range(4):
|
|
|
|
self.getColors(
|
|
|
|
col * 4,
|
2020-11-06 19:40:41 +01:00
|
|
|
row * 4,
|
2020-11-06 03:08:21 +01:00
|
|
|
i,
|
|
|
|
j,
|
|
|
|
ctable,
|
2020-12-22 00:40:18 +01:00
|
|
|
c0,
|
|
|
|
c1,
|
|
|
|
self.getAlpha(i, j, a0, a1, (acode1 << 16) | acode0),
|
2020-11-06 03:08:21 +01:00
|
|
|
) # Set the color for the current pixel
|
|
|
|
|
|
|
|
return b''.join([x for x in self.decompressed_buffer if x is not None])
|
|
|
|
|
2020-12-22 00:40:18 +01:00
|
|
|
def DXT1Decompress(self, filedata: bytes, swap: bool = False) -> bytes:
|
2020-11-06 03:08:21 +01:00
|
|
|
# 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
|
2020-12-22 00:40:18 +01:00
|
|
|
c0, c1, ctable = struct.unpack("<HHI", self.swapbytes(file.read(8), swap))
|
2020-11-06 03:08:21 +01:00
|
|
|
|
|
|
|
# The 4x4 Lookup table loop
|
|
|
|
for j in range(4):
|
|
|
|
for i in range(4):
|
|
|
|
self.getColors(
|
|
|
|
col * 4,
|
2020-11-06 19:40:41 +01:00
|
|
|
row * 4,
|
2020-11-06 03:08:21 +01:00
|
|
|
i,
|
|
|
|
j,
|
|
|
|
ctable,
|
2020-12-22 00:40:18 +01:00
|
|
|
c0,
|
|
|
|
c1,
|
2020-11-06 03:08:21 +01:00
|
|
|
255,
|
|
|
|
) # Set the color for the current pixel
|
|
|
|
|
2020-11-11 04:39:21 +01:00
|
|
|
return b''.join([x for x in self.decompressed_buffer if x is not None])
|
2020-11-06 03:08:21 +01:00
|
|
|
|
|
|
|
def getColors(
|
|
|
|
self,
|
|
|
|
x: int,
|
|
|
|
y: int,
|
|
|
|
i: int,
|
|
|
|
j: int,
|
|
|
|
ctable: int,
|
2020-12-22 00:40:18 +01:00
|
|
|
c0: int,
|
|
|
|
c1: int,
|
2020-11-06 03:08:21 +01:00
|
|
|
alpha: int,
|
|
|
|
) -> None:
|
2020-11-06 20:02:41 +01:00
|
|
|
code = (ctable >> (2 * ((4 * j) + i))) & 0x03 # Get the color of the current pixel
|
2020-11-06 03:08:21 +01:00
|
|
|
pixel_color = None
|
|
|
|
|
2020-12-22 00:40:18 +01:00
|
|
|
r0, g0, b0 = self.unpackRGB(c0)
|
|
|
|
r1, g1, b1 = self.unpackRGB(c1)
|
2020-11-06 03:08:21 +01:00
|
|
|
|
2020-11-06 19:40:41 +01:00
|
|
|
# Sliding scale between colors.
|
2020-11-06 03:08:21 +01:00
|
|
|
if code == 0:
|
|
|
|
pixel_color = (r0, g0, b0, alpha)
|
|
|
|
if code == 1:
|
|
|
|
pixel_color = (r1, g1, b1, alpha)
|
2020-11-06 19:40:41 +01:00
|
|
|
if code == 2:
|
2020-12-22 00:40:18 +01:00
|
|
|
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)
|
2020-11-06 19:40:41 +01:00
|
|
|
if code == 3:
|
2020-12-22 00:40:18 +01:00
|
|
|
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)
|
2020-11-06 03:08:21 +01:00
|
|
|
|
2020-11-06 19:40:41 +01:00
|
|
|
# While not surpassing the image dimensions, assign pixels the colors.
|
|
|
|
if (x + i) < self.width and (y + j) < self.height:
|
2020-11-06 03:08:21 +01:00
|
|
|
self.decompressed_buffer[(y + j) * self.width + (x + i)] = (
|
2020-11-06 19:40:41 +01:00
|
|
|
struct.pack('<BBBB', *pixel_color)
|
2020-11-06 03:08:21 +01:00
|
|
|
)
|
|
|
|
|
2020-12-22 00:40:18 +01:00
|
|
|
def getAlpha(self, i: int, j: int, a0: int, a1: int, acode: int) -> int:
|
2020-11-06 03:08:21 +01:00
|
|
|
# Using the same method as the colors calculate the alpha values
|
2020-12-22 00:40:18 +01:00
|
|
|
alpha_code = (acode >> (3 * ((4 * j) + i))) & 0x07
|
2020-11-06 03:08:21 +01:00
|
|
|
|
|
|
|
if alpha_code == 0:
|
|
|
|
alpha = a0
|
|
|
|
elif alpha_code == 1:
|
|
|
|
alpha = a1
|
|
|
|
else:
|
|
|
|
if a0 > a1:
|
|
|
|
alpha = ((8 - alpha_code) * a0 + (alpha_code - 1) * a1) // 7
|
|
|
|
else:
|
|
|
|
if alpha_code == 6:
|
|
|
|
alpha = 0
|
|
|
|
elif alpha_code == 7:
|
|
|
|
alpha = 255
|
|
|
|
else:
|
2020-11-06 19:40:41 +01:00
|
|
|
alpha = ((6 - alpha_code) * a0 + (alpha_code - 1) * a1) // 5
|
2020-11-06 03:08:21 +01:00
|
|
|
return alpha
|