XPlor/libs/ddsfile/ddsfile.cpp
2025-03-01 20:38:52 -05:00

346 lines
9.7 KiB
C++

#include "ddsfile.h"
#include "iwifile.h"
#include "qdir.h"
DDSPixelFormat DDSFile::CalculatePixelFormat(quint8 aIWIFormat) {
DDSPixelFormat ddsPixelFormat = {};
ddsPixelFormat.size = 16; // Fixed
ddsPixelFormat.format = 0;
switch (aIWIFormat) {
case IWI_FORMAT_ARGB32:
ddsPixelFormat.flags = DDPF_RGB | DDPF_ALPHAPIXELS;
ddsPixelFormat.rgbBitCount = 32;
ddsPixelFormat.rBitMask = 0x00ff0000;
ddsPixelFormat.gBitMask = 0x0000ff00;
ddsPixelFormat.bBitMask = 0x000000ff;
ddsPixelFormat.aBitMask = 0xff000000;
break;
case IWI_FORMAT_DXT1:
ddsPixelFormat.flags = DDPF_FOURCC;
ddsPixelFormat.format = 0x31545844;
break;
case IWI_FORMAT_DXT3:
ddsPixelFormat.flags = DDPF_FOURCC;
ddsPixelFormat.format = 0x33545844;
break;
case IWI_FORMAT_DXT5:
ddsPixelFormat.flags = DDPF_FOURCC;
ddsPixelFormat.format = 0x35545844;
break;
default:
qDebug() << "Error: Invalid IWI format: " << aIWIFormat;
return {};
}
return ddsPixelFormat;
}
void DDSFile::SetupExportDirs() {
QDir dir = QDir::currentPath();
if (!dir.exists("exports/")) {
dir.mkdir("exports/");
}
if (!dir.exists("exports/iwi/")) {
dir.mkdir("exports/iwi/");
}
if (!dir.exists("exports/dds/")) {
dir.mkdir("exports/dds/");
}
if (!dir.exists("exports/png/")) {
dir.mkdir("exports/png/");
}
if (!dir.exists("exports/jpg/")) {
dir.mkdir("exports/jpg/");
}
}
DDSFile::DDSFile() :
fileStem(),
header(),
mipmaps() {
}
QByteArray DDSFile::ReadDDSFile(const QString &aFilePath) {
if (!aFilePath.endsWith(".dds", Qt::CaseInsensitive)) {
qDebug() << "Error: Invalid filename " << aFilePath;
return {};
}
if (!QFile::exists(aFilePath)) {
qDebug() << "Error: File does not exist!";
return {};
}
QFile file(aFilePath);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "Error: Failed to read DDS file.";
return {};
}
const QByteArray ddsData = file.readAll();
file.close();
return ddsData;
}
DDSFile::DDSFile(const QString &aFilePath)
: DDSFile(ReadDDSFile(aFilePath), aFilePath.split('.').first().split('/').last()) {
}
DDSFile::DDSFile(const QByteArray aDDSData, const QString aFileStem) {
QDataStream ddsIn(aDDSData);
ddsIn.setByteOrder(QDataStream::LittleEndian);
DDSHeader ddsHeader;
if (ddsIn.readRawData(reinterpret_cast<char*>(&ddsHeader), sizeof(DDSHeader)) != sizeof(DDSHeader)) {
qDebug() << "Error: Failed to read DDSHeader from QByteArray!";
return;
}
fileStem = aFileStem;
header = ddsHeader;
// Ensure DevIL is initialized once globally
static bool devilInitialized = false;
if (!devilInitialized) {
ilInit();
devilInitialized = true;
}
// Generate and bind an image
ILuint imageID;
ilGenImages(1, &imageID);
ilBindImage(imageID);
ilEnable(IL_ORIGIN_SET);
ilOriginFunc(IL_ORIGIN_UPPER_LEFT);
// Load DDS file
if (!ilLoadL(IL_DDS, aDDSData.constData(), aDDSData.size())) {
ILuint devilError = ilGetError();
qDebug() << "DevIL Error while loading DDS: " << devilError;
ilDeleteImages(1, &imageID);
return;
}
// Get mipmap count
ILint numMipmaps = ilGetInteger(IL_NUM_MIPMAPS);
qDebug() << "Number of mipmaps: " << numMipmaps;
// Loop over all mipmap levels (0 is the base image)
for (ILint level = 0; level <= numMipmaps; ++level) {
ilBindImage(imageID);
if (!ilActiveMipmap(level)) {
qDebug() << "DevIL failed to activate mipmap level" << level;
continue;
}
// Get mipmap properties
int width = ilGetInteger(IL_IMAGE_WIDTH);
int height = ilGetInteger(IL_IMAGE_HEIGHT);
int depth = ilGetInteger(IL_IMAGE_DEPTH);
int format = ilGetInteger(IL_IMAGE_FORMAT);
int bpp = 0;
switch (format) {
case IL_RGB:
bpp = 3;
break;
case IL_RGBA:
bpp = 4;
break;
default:
qDebug() << "Unsupported image format.";
continue;
}
int dataSize = width * height * depth * bpp;
ILubyte *data = ilGetData();
if (!data) {
qDebug() << "Error: DevIL returned null data for mipmap level" << level;
continue;
}
// Create a mipmap structure
DDSMipmap mipmap;
mipmap.width = width;
mipmap.height = height;
mipmap.data = QByteArray(reinterpret_cast<const char*>(data), dataSize);
mipmap.size = dataSize;
// Store in DDS file
mipmaps.append(mipmap);
}
ilDeleteImages(1, &imageID);
}
DDSFile::DDSFile(const DDSFile &ddsFile) :
fileStem(ddsFile.fileStem),
header(ddsFile.header),
mipmaps(ddsFile.mipmaps) {
}
DDSFile::DDSFile(const IWIFile &aIWIFile)
: DDSFile(IWItoDDSData(aIWIFile), aIWIFile.fileStem) {
}
QByteArray DDSFile::IWItoDDSData(const IWIFile &aIWIFile) {
DDSHeader ddsHeader = {};
ddsHeader.magic = 0x20534444; // 'DDS '
ddsHeader.size = 124;
ddsHeader.flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE;
ddsHeader.height = aIWIFile.info.Height;
ddsHeader.width = aIWIFile.info.Width;
ddsHeader.depth = 0;
ddsHeader.mipMapCount = aIWIFile.mipmaps.size();
DDSPixelFormat ddsPixelFormat = CalculatePixelFormat(aIWIFile.info.Format);
if (ddsPixelFormat.flags & DDPF_FOURCC) {
ddsHeader.flags |= DDSD_LINEARSIZE;
} else {
ddsHeader.flags |= DDSD_PITCH;
}
ddsHeader.pixelFormat = ddsPixelFormat;
// Calculate pitch/linear size
if (ddsPixelFormat.flags & DDPF_FOURCC) {
int blockSize = (ddsPixelFormat.format == 0x31545844) ? 8 : 16;
ddsHeader.pitchOrLinearSize = fmax(1, (ddsHeader.width + 3) / 4) * blockSize * (ddsHeader.height / 4);
} else {
ddsHeader.pitchOrLinearSize = ddsHeader.width * (ddsPixelFormat.rgbBitCount / 8);
}
DDSCaps ddsCaps = {};
ddsCaps.caps1 = DDSCAPS_TEXTURE;
ddsCaps.caps2 = 0;
ddsCaps.dDSX = 0;
ddsCaps.reserved = 0;
ddsHeader.caps = ddsCaps;
QByteArray ddsData;
QDataStream out(&ddsData, QIODevice::WriteOnly);
out.setByteOrder(QDataStream::LittleEndian);
// Write DDS header
out.writeRawData(reinterpret_cast<const char*>(&ddsHeader), sizeof(DDSHeader));
for (auto mipmap : aIWIFile.mipmaps) {
if (!mipmap.data.size()) { continue; }
// Write mipmap data
out.writeRawData(reinterpret_cast<const char*>(mipmap.data.constData()), mipmap.data.size());
}
return ddsData;
}
DDSFile &DDSFile::operator=(const DDSFile &other) {
if (this != &other) {
fileStem = other.fileStem;
header = other.header;
mipmaps = other.mipmaps;
}
return *this;
}
// Write a DDS file from a DDSFile object
bool DDSFile::SaveDDS() {
SetupExportDirs();
QFile file("exports/dds/" + fileStem + ".dds");
if (!file.open(QIODevice::WriteOnly)) {
qDebug() << "Error: Unable to write DDS file" << fileStem + ".dds";
return false;
}
QDataStream out(&file);
out.setByteOrder(QDataStream::LittleEndian);
// Write DDS Header
out.writeRawData(reinterpret_cast<const char*>(&header), sizeof(DDSHeader));
file.close();
qDebug() << "Successfully wrote DDS file: " << fileStem + ".dds";
return true;
}
bool DDSFile::SaveIWI() {
SetupExportDirs();
IWIFile iwiFile(*this);
if (!iwiFile.SaveIWI()) {
qDebug() << "Error: Unable to write IWI file" << fileStem + ".iwi";
return false;
}
return true;
}
bool DDSFile::SavePNG() {
SetupExportDirs();
int mipmapIndex = 1;
for (const DDSMipmap &mipmap : mipmaps) {
QString subFilePath = "exports/png/" + fileStem + ".png";
if (mipmaps.size() > 1) {
subFilePath = "exports/png/" + fileStem + QString("_%1").arg(mipmapIndex) + ".png";
}
// Calculate bytes per line (stride)
int bytesPerPixel = 4; // Assuming RGBA8888 (4 bytes per pixel)
int bytesPerLine = mipmap.width * bytesPerPixel;
QImage image(reinterpret_cast<const uchar*>(mipmap.data.constData()),
mipmap.width, mipmap.height, bytesPerLine, QImage::Format_RGBA8888);
if (image.isNull()) {
qDebug() << "Error: Failed to create QImage for mipmap" << mipmapIndex;
return false;
}
if (!image.save(subFilePath)) {
qDebug() << "Error: Unable to save PNG file" << subFilePath;
return false;
}
mipmapIndex++;
}
return true;
}
bool DDSFile::SaveJPG() {
SetupExportDirs();
int mipmapIndex = 1;
for (const DDSMipmap &mipmap : mipmaps) {
QString subFilePath = "exports/jpg/" + fileStem + ".jpg";
if (mipmaps.size() > 1) {
subFilePath = "exports/jpg/" + fileStem + QString("_%1").arg(mipmapIndex) + ".jpg";
}
// Calculate bytes per line (stride)
int bytesPerPixel = 4; // Assuming RGBA8888 (4 bytes per pixel)
int bytesPerLine = mipmap.width * bytesPerPixel;
QImage image(reinterpret_cast<const uchar*>(mipmap.data.constData()),
mipmap.width, mipmap.height, bytesPerLine, QImage::Format_RGBA8888);
if (image.isNull()) {
qDebug() << "Error: Failed to create QImage for mipmap" << mipmapIndex;
return false;
}
if (!image.save(subFilePath, "JPG")) {
qDebug() << "Error: Unable to save JPG file" << subFilePath;
return false;
}
mipmapIndex++;
}
return true;
}