diff --git a/Scripts/color.py b/Scripts/color.py new file mode 100644 index 0000000..a85a705 --- /dev/null +++ b/Scripts/color.py @@ -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()) diff --git a/Scripts/dds.py b/Scripts/dds.py index 4176fce..28fc498 100644 --- a/Scripts/dds.py +++ b/Scripts/dds.py @@ -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 \ No newline at end of file + #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) \ No newline at end of file diff --git a/Scripts/s3tc.py b/Scripts/s3tc.py new file mode 100644 index 0000000..9d0c1b5 --- /dev/null +++ b/Scripts/s3tc.py @@ -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('