DXT1 image parsing
This commit is contained in:
parent
1c0de5b963
commit
c89d94717d
72
Scripts/color.py
Normal file
72
Scripts/color.py
Normal file
@ -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)
|
62
Scripts/s3tc.py
Normal file
62
Scripts/s3tc.py
Normal 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…
Reference in New Issue
Block a user