DXT1 image parsing

main
Andrew Cassidy 3 years ago
parent 1c0de5b963
commit c89d94717d

@ -0,0 +1,72 @@
class Color:
def __init__(self, r = 0, g = 0, b = 0, a = 1):
self.r = r
self.g = g
self.b = b
self.a = a
def __add__(self, a):
return Color(self.r + a.r, self.g + a.g, self.b + a.b, self.a + a.a)
def __mul__(self, c):
return Color(self.r * c, self.g * c, self.b * c, self.a * c)
def __rmul__(self, c):
return Color(self.r * c, self.g * c, self.b * c, self.a * c)
def __iter__(self):
return iter([self.r,self.g,self.b,self.a])
def __repr__(self):
return f'r: {self.r} g: {self.g} b: {self.b} a: {self.a}'
def __str__(self):
return self.to_hex()
@classmethod
def from_565(cls, int_value):
r = float((int_value & 0xF800) >> 11) / 0x1F
g = float((int_value & 0x07E0) >> 5) / 0x3F
b = float(int_value & 0x001F) / 0x1F
return cls(r,g,b)
def to_565(self):
r = int(self.r * 0x1F)
g = int(self.g * 0x3F)
b = int(self.b * 0x1F)
return (r << 11) | (g << 5) | b
@classmethod
def from_rgb24(cls, int_value):
r = float((int_value & 0xFF0000) >> 16) / 0xFF
g = float((int_value & 0x00FF00) >> 8) / 0xFF
b = float(int_value & 0x0000FF) / 0xFF
return cls(r,g,b)
def to_rgb24(self):
r = int(self.r * 0xFF)
g = int(self.g * 0xFF)
b = int(self.b * 0xFF)
return (r << 16) | (g << 8) | b
@classmethod
def from_rgba32(cls, int_value):
r = float((int_value & 0xFF000000) >> 24) / 0xFF
g = float((int_value & 0x00FF0000) >> 16) / 0xFF
b = float((int_value & 0x0000FF00) >> 8) / 0xFF
a = float(int_value & 0x000000FF) / 0xFF
return cls(r,g,b,a)
def to_rgba32(self):
r = int(self.r * 0xFF)
g = int(self.g * 0xFF)
b = int(self.b * 0xFF)
a = int(self.a * 0xFF)
return (r << 24) | (g << 16) | (b << 8) | a
def to_hex(self):
if self.a < 1:
return hex(self.to_rgba32())
else:
return hex(self.to_rgb24())

@ -1,10 +1,9 @@
import subprocess
import os
import typing
import struct
import re
import io
import math
import enum
import s3tc
if os.name == 'nt':
convertcmd = "convert.exe"
@ -73,10 +72,23 @@ class DDSPixelFlags(enum.IntFlag):
YUV = 0x200
LUMINANCE = 0x20000
texture_formats = {
'DXT1' : s3tc.DXT1Texture
}
class DDSFile:
def __init__(self, file = None):
if (file):
self.read_header(file)
self.read(file)
def read(self, file):
self.read_header(file)
self.read_data(file)
def write(self, file):
self.write_header(file)
self.write_data(file)
def read_header(self, file):
# read magic bytes
@ -84,7 +96,7 @@ class DDSFile:
raise ValueError("File is not a valid DDS file: Incorrect magic bytes")
# read header
header = struct.unpack('7I44x', file.read(72))
header = struct.unpack('<7I44x', file.read(72))
# check constant
if header[0] != 124:
@ -95,7 +107,7 @@ class DDSFile:
self.height, self.width, self.linear_size, self.depth, self.mipmapcount = header[2:7]
# read pixelformat
pixelformat = struct.unpack('2I4s5I', file.read(32))
pixelformat = struct.unpack('<2I4s5I', file.read(32))
# check constant
if pixelformat[0] != 32:
@ -106,7 +118,7 @@ class DDSFile:
self.rgb_bit_count, self.r_bitmask, self.g_bitmask, self.b_bitmask, self.a_bitmask = pixelformat[3:8]
# read caps
self.dw_caps, self.dw_caps2, self.dw_caps3, self.dw_caps4 = struct.unpack('4I', file.read(16))
self.dw_caps, self.dw_caps2, self.dw_caps3, self.dw_caps4 = struct.unpack('<4I4x', file.read(20))
# read DX10 header
self.is_DX10 = (self.four_cc == 'DX10')
@ -118,20 +130,41 @@ class DDSFile:
#TODO: actually do something with the DX10 data
def write_header(self, file):
file.write('DDS ')
file.write(bytes('DDS ', 'utf-8'))
# write header
file.write(struct.pack(
'7I44x', 124, int(self.flags), self.height, self.width,
'<7I44x', 124, int(self.flags), self.height, self.width,
self.linear_size, self.depth, self.mipmapcount))
# write pixelformat
file.write(struct.pack(
'2I4s5I', 32, int(self.pixel_flags), self.four_cc, self.rgb_bit_count,
'<2I4s5I', 32, int(self.pixel_flags), bytes(self.four_cc, 'utf-8'), self.rgb_bit_count,
self.r_bitmask, self.g_bitmask, self.b_bitmask, self.a_bitmask))
# write caps
file.write(struct.pack(
'4I', self.dw_caps, self.dw_caps2, self.dw_caps3, self.dw_caps4))
'<4I4x', self.dw_caps, self.dw_caps2, self.dw_caps3, self.dw_caps4))
#TODO: handle DX10 data
#TODO: handle DX10 data
if self.is_DX10:
file.write(struct.pack('20x'))
def read_data(self, file):
self.textures = []
if DDSFlags.MIPMAPCOUNT in self.flags:
mips = self.mipmapcount
else:
mips = 1
if DDSPixelFlags.FOURCC in self.pixel_flags:
textureType = texture_formats[self.four_cc]
for mip in range(mips):
width = math.ceil(self.width / (2 ** mip))
height = math.ceil(self.height / (2 ** mip))
self.textures.append(textureType(width, height, file))
def write_data(self, file):
for texture in self.textures:
texture.write(file)

@ -0,0 +1,62 @@
import struct
import math
from color import Color
def byte_slice(byte):
return [byte & 0x03,
(byte & 0x0C) >> 2,
(byte & 0x30) >> 4,
(byte & 0xC0) >> 6]
def byte_unslice(indices):
return indices[0] | (indices[1] << 2) | (indices[2] << 4) | (indices[3] << 6)
class DXT1Texture:
def __init__(self, width = 4, height = 4, file = None):
self.width = width
self.height = height
self.block_width = math.ceil(width / 4)
self.block_height = math.ceil(height / 4)
self.block_count = self.block_width * self.block_height
self.blocks = []
if file:
self.read(file)
def __repr__(self):
return f'{self.block_count} blocks: {self.blocks}'
def read(self, file):
for x in range(self.block_count):
self.blocks.append(DXT1Block(file))
def write(self, file):
for block in self.blocks:
block.write(file)
class DXT1Block:
size = 8
def __init__(self, file = None):
self.color0 = Color()
self.color1 = Color()
self.indices = [[0] * 4] * 4
if file:
self.read(file)
def __repr__(self):
return f'color0: ({repr(self.color0)}) color1: ({repr(self.color1)}), indices:{self.indices}'
def __str__(self):
return f'color0: {str(self.color0)} color1: {str(self.color1)}, indices:{self.indices}'
def read(self, file):
block = struct.unpack_from('<HHBBBB', file.read(self.size))
self.color0 = Color.from_565(block[0])
self.color1 = Color.from_565(block[1])
self.indices = list(map(byte_slice, block[2:6]))
def write(self, file):
file.write(struct.pack('<HHBBBB', self.color0.to_565(), self.color1.to_565(), *map(byte_unslice, self.indices)))
Loading…
Cancel
Save