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.

137 lines
3.8 KiB
Python

import subprocess
import os
import typing
import struct
import re
import io
import enum
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
class DDSFile:
def __init__(self, file = None):
if (file):
self.read_header(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('4I', file.read(16))
# 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('DDS ')
# 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), self.four_cc, 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))
#TODO: handle DX10 data