forked from drewcassidy/KSP-Toolkit
172 lines
4.8 KiB
Python
172 lines
4.8 KiB
Python
import os
|
|
import subprocess
|
|
import struct
|
|
import re
|
|
import math
|
|
import enum
|
|
import s3tc
|
|
|
|
if os.name == 'nt':
|
|
convertcmd = "convert.exe"
|
|
compresscmd = "nvcompress.exe"
|
|
decompresscmd = "nvdecompress.exe"
|
|
infocmd = "nvddsinfo.exe"
|
|
else:
|
|
convertcmd = "convert"
|
|
compresscmd = "nvcompress"
|
|
decompresscmd = "nvdecompress"
|
|
infocmd = "nvddsinfo"
|
|
|
|
def alpha(file):
|
|
result = subprocess.run([convertcmd, file, "-resize", "1x1",
|
|
"-format", "%[fx:int(255*a+.5)]", "info:-"], capture_output=True)
|
|
result.check_returncode()
|
|
return int(result.stdout)
|
|
|
|
|
|
def flip(file, output):
|
|
result = subprocess.run(
|
|
[convertcmd, '-flip', file, output], capture_output=True)
|
|
result.check_returncode()
|
|
|
|
|
|
def nvcompress(format, file, output, mips=True):
|
|
args = [format]
|
|
if not mips:
|
|
args.append('-nomips')
|
|
|
|
result = subprocess.run([compresscmd] + args +
|
|
[file, output], capture_output=True)
|
|
result.check_returncode()
|
|
|
|
|
|
def nvdecompress(file, output):
|
|
result = subprocess.run([decompresscmd, file, output], capture_output=True)
|
|
result.check_returncode()
|
|
|
|
|
|
def nvinfo(file):
|
|
result = subprocess.run([infocmd, file], capture_output=True)
|
|
result.check_returncode()
|
|
|
|
info = {
|
|
"format": re.search(r"FourCC: '(.{4})'", str(result.stdout)).group(1)
|
|
}
|
|
|
|
return info
|
|
|
|
class DDSFlags(enum.IntFlag):
|
|
CAPS = 0x1
|
|
HEIGHT = 0x2
|
|
WIDTH = 0x4
|
|
PITCH = 0x8
|
|
PIXEL_FORMAT = 0x1000
|
|
MIPMAPCOUNT = 0x20000
|
|
LINEAR_SIZE = 0x80000
|
|
DEPTH = 0x800000
|
|
|
|
class DDSPixelFlags(enum.IntFlag):
|
|
ALPHAPIXELS = 0x1
|
|
ALPHA = 0x2
|
|
FOURCC = 0x4
|
|
RGB = 0x40
|
|
YUV = 0x200
|
|
LUMINANCE = 0x20000
|
|
|
|
|
|
texture_formats = {
|
|
'DXT1' : s3tc.DXT1Texture,
|
|
'DXT5' : s3tc.DXT5Texture
|
|
}
|
|
|
|
class DDSFile:
|
|
def __init__(self, file = None):
|
|
if (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
|
|
if file.read(4).decode() != 'DDS ':
|
|
raise ValueError("File is not a valid DDS file: Incorrect magic bytes")
|
|
|
|
# read header
|
|
header = struct.unpack('<7I44x', file.read(72))
|
|
|
|
# check constant
|
|
if header[0] != 124:
|
|
raise ValueError("File is not a valid DDS file: Incorrect header size")
|
|
|
|
# read basic info
|
|
self.flags = DDSFlags(header[1])
|
|
self.height, self.width, self.linear_size, self.depth, self.mipmapcount = header[2:7]
|
|
|
|
# read pixelformat
|
|
pixelformat = struct.unpack('<2I4s5I', file.read(32))
|
|
|
|
# check constant
|
|
if pixelformat[0] != 32:
|
|
raise ValueError("File is not a valid DDS file: Incorrect pixelformat size")
|
|
|
|
self.pixel_flags = DDSPixelFlags(pixelformat[1])
|
|
self.four_cc = pixelformat[2].decode()
|
|
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('<4I4x', file.read(20))
|
|
|
|
# read DX10 header
|
|
self.is_DX10 = (self.four_cc == 'DX10')
|
|
if self.is_DX10:
|
|
self.read_dx10header(file)
|
|
|
|
def read_dx10header(self, file):
|
|
dx10header = file.read(20)
|
|
#TODO: actually do something with the DX10 data
|
|
|
|
def write_header(self, file):
|
|
file.write(bytes('DDS ', 'utf-8'))
|
|
|
|
# write header
|
|
file.write(struct.pack(
|
|
'<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), 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(
|
|
'<4I4x', self.dw_caps, self.dw_caps2, self.dw_caps3, self.dw_caps4))
|
|
|
|
#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) |