#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() const { 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(&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(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(&ddsHeader), sizeof(DDSHeader)); for (auto mipmap : aIWIFile.mipmaps) { if (!mipmap.data.size()) { continue; } // Write mipmap data out.writeRawData(reinterpret_cast(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() const { 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(&header), sizeof(DDSHeader)); file.close(); qDebug() << "Successfully wrote DDS file: " << fileStem + ".dds"; return true; } bool DDSFile::SaveIWI() const { SetupExportDirs(); IWIFile iwiFile(*this); if (!iwiFile.SaveIWI()) { qDebug() << "Error: Unable to write IWI file" << fileStem + ".iwi"; return false; } return true; } bool DDSFile::SavePNG() const { 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(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() const { 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(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; }