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 os
|
||||||
import typing
|
|
||||||
import struct
|
import struct
|
||||||
import re
|
import re
|
||||||
import io
|
import math
|
||||||
import enum
|
import enum
|
||||||
|
import s3tc
|
||||||
|
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
convertcmd = "convert.exe"
|
convertcmd = "convert.exe"
|
||||||
@ -73,10 +72,23 @@ class DDSPixelFlags(enum.IntFlag):
|
|||||||
YUV = 0x200
|
YUV = 0x200
|
||||||
LUMINANCE = 0x20000
|
LUMINANCE = 0x20000
|
||||||
|
|
||||||
|
|
||||||
|
texture_formats = {
|
||||||
|
'DXT1' : s3tc.DXT1Texture
|
||||||
|
}
|
||||||
|
|
||||||
class DDSFile:
|
class DDSFile:
|
||||||
def __init__(self, file = None):
|
def __init__(self, file = None):
|
||||||
if (file):
|
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):
|
def read_header(self, file):
|
||||||
# read magic bytes
|
# read magic bytes
|
||||||
@ -84,7 +96,7 @@ class DDSFile:
|
|||||||
raise ValueError("File is not a valid DDS file: Incorrect magic bytes")
|
raise ValueError("File is not a valid DDS file: Incorrect magic bytes")
|
||||||
|
|
||||||
# read header
|
# read header
|
||||||
header = struct.unpack('7I44x', file.read(72))
|
header = struct.unpack('<7I44x', file.read(72))
|
||||||
|
|
||||||
# check constant
|
# check constant
|
||||||
if header[0] != 124:
|
if header[0] != 124:
|
||||||
@ -95,7 +107,7 @@ class DDSFile:
|
|||||||
self.height, self.width, self.linear_size, self.depth, self.mipmapcount = header[2:7]
|
self.height, self.width, self.linear_size, self.depth, self.mipmapcount = header[2:7]
|
||||||
|
|
||||||
# read pixelformat
|
# read pixelformat
|
||||||
pixelformat = struct.unpack('2I4s5I', file.read(32))
|
pixelformat = struct.unpack('<2I4s5I', file.read(32))
|
||||||
|
|
||||||
# check constant
|
# check constant
|
||||||
if pixelformat[0] != 32:
|
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]
|
self.rgb_bit_count, self.r_bitmask, self.g_bitmask, self.b_bitmask, self.a_bitmask = pixelformat[3:8]
|
||||||
|
|
||||||
# read caps
|
# 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
|
# read DX10 header
|
||||||
self.is_DX10 = (self.four_cc == 'DX10')
|
self.is_DX10 = (self.four_cc == 'DX10')
|
||||||
@ -118,20 +130,41 @@ class DDSFile:
|
|||||||
#TODO: actually do something with the DX10 data
|
#TODO: actually do something with the DX10 data
|
||||||
|
|
||||||
def write_header(self, file):
|
def write_header(self, file):
|
||||||
file.write('DDS ')
|
file.write(bytes('DDS ', 'utf-8'))
|
||||||
|
|
||||||
# write header
|
# write header
|
||||||
file.write(struct.pack(
|
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))
|
self.linear_size, self.depth, self.mipmapcount))
|
||||||
|
|
||||||
# write pixelformat
|
# write pixelformat
|
||||||
file.write(struct.pack(
|
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))
|
self.r_bitmask, self.g_bitmask, self.b_bitmask, self.a_bitmask))
|
||||||
|
|
||||||
# write caps
|
# write caps
|
||||||
file.write(struct.pack(
|
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