You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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)