在游戏开发和图像处理领域,DDS(DirectDraw Surface)文件格式被广泛用于存储纹理图像。DDS格式支持压缩纹理和未压缩纹理,使其成为高效存储和传输大尺寸纹理数据的理想选择。与传统的图像格式(如PNG、JPEG)相比,DDS能够更好地处理3D图像和纹理,尤其是在游戏引擎中常见的实时渲染场景中。

       虽然大多数应用程序和图像处理库已经为DDS文件格式提供了解析支持,但在一些特定的情况下(如需要避免第三方库或进行自定义图像处理时),你可能希望手动解析DDS文件。本文将展示如何在C++中实现一个不依赖任何第三方库的DDS文件解析器。通过手动读取DDS文件头部信息,并提取图像的像素数据,我们可以获得图像的基本信息,并将其保存为文本格式进行进一步处理或分析。

1. 什么是DDS文件格式?

       DDS(DirectDraw Surface)文件格式最初由微软为DirectX图形API设计,用于存储纹理图像和环境映射图。它不仅支持未压缩的像素格式,还可以存储多种压缩纹理格式(如DXT系列)。这些压缩纹理格式能够有效减小图像数据的大小,减少内存占用,尤其是在处理复杂的3D图像时。

1.1 DDS文件结构

       一个标准的DDS文件由两大主要部分组成:

  1. 文件头(DDS Header):文件头用于描述文件的基本信息,包含图像的尺寸、格式以及其它与图像内容相关的元数据。DDS文件的头部结构是固定的,包含一个“魔数”(magic number),通过该魔数可以验证文件是否为有效的DDS文件。DDS文件头结构对于解析文件数据至关重要。

  2. 像素数据:紧接着文件头部分的是实际的图像数据。图像数据的形式可以是未压缩的RGB格式,也可以是通过DXT压缩技术压缩的纹理数据。DDS文件支持多级渐远纹理(Mipmaps),用于提升图像在不同缩放级别下的渲染效果。

1.2 DDS文件头结构

       DDS文件头由一个固定长度的结构体表示,通常为128字节。下面是文件头的详细结构,包括每个字段的解释:

字段名 类型 描述
magic uint32_t 文件的魔数,通常为0x20534444('DDS '),用于标识文件格式是否正确
size uint32_t 文件头的大小,通常为124字节
flags uint32_t 文件标志,包含了一些描述图像的标志,例如图像是否包含Alpha通道、是否使用Mipmap等
height uint32_t 图像的高度
width uint32_t 图像的宽度
pitchOrLinearSize uint32_t 每行字节数或整个图像的字节数。对于非压缩图像,这通常是每行图像数据的字节数
depth uint32_t 图像的深度,如果是3D纹理或体积纹理,深度代表纹理的Z维度,2D纹理通常为0
mipMapCount uint32_t Mipmap层数,用于存储多层渐远纹理
reserved uint32_t[11] 保留字段,未使用
pfFlags uint32_t 像素格式标志,指示纹理的像素格式,例如是否为RGB格式或压缩格式(如DXT1、DXT5等)
pfFourCC uint32_t 像素格式标识符,通常为FourCC编码,例如D3DFMT_X8R8G8B8表示RGB格式,DXT1表示压缩格式
pfRGBBitCount uint32_t 每个像素的位数,通常为24位(RGB格式)或32位(RGBA格式)
pfRBitMask uint32_t 红色分量掩码
pfGBitMask uint32_t 绿色分量掩码
pfBBitMask uint32_t 蓝色分量掩码
pfABitMask uint32_t Alpha分量掩码,若图像有Alpha通道,则该字段有效

       根据文件头的信息,我们可以了解到图像的基本特性,包括尺寸、颜色格式、压缩方式等。理解这些字段对于进一步处理图像数据至关重要。

2. C++代码实现:手动解析DDS文件

       在本节中,我们将使用C++实现一个简单的DDS文件解析器,手动读取DDS文件头并提取图像的像素数据。为了简化起见,我们假设DDS文件中的图像为RGB格式,即每个像素占用3个字节,且图像未进行压缩。

2.1 完整代码实现

#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <string>

// DDS文件头(第一部分)
#pragma pack(push, 1)
struct DDSHeader {
    uint32_t magic;         // 文件魔数(0x20534444,'DDS ')
    uint32_t size;          // 文件头大小(通常为124)
    uint32_t flags;         // 文件标志
    uint32_t height;        // 图像高度
    uint32_t width;         // 图像宽度
    uint32_t pitchOrLinearSize;  // 每行字节数或图像总字节数
    uint32_t depth;         // 深度(如果存在)
    uint32_t mipMapCount;   // Mipmap数量
    uint32_t reserved[11];  // 保留字段
    uint32_t pfFlags;       // 格式标志(如 DDPF_RGB)
    uint32_t pfFourCC;      // 格式标识符(例如 D3DFMT_X8R8G8B8)
    uint32_t pfRGBBitCount; // 每个像素的位数
    uint32_t pfRBitMask;    // 红色分量掩码
    uint32_t pfGBitMask;    // 绿色分量掤面
    uint32_t pfBBitMask;    // 蓝色分量掩码
    uint32_t pfABitMask;    // Alpha分量掩码(如果有的话)
};
#pragma pack(pop)

void saveDDSDataToTxt(const std::string& ddsFilePath, const std::string& txtFilePath) {
    std::ifstream ddsFile(ddsFilePath, std::ios::binary);
    if (!ddsFile.is_open()) {
        std::cerr << "Error opening DDS file: " << ddsFilePath << std::endl;
        return;
    }

    // 读取DDS头部
    DDSHeader header;
    ddsFile.read(reinterpret_cast<char*>(&header), sizeof(DDSHeader));
    
    if (header.magic != 0x20534444) {  // 'DDS '的魔数
        std::cerr << "Invalid DDS file format." << std::endl;
        ddsFile.close();
        return;
    }

    // 打开输出txt文件
    std::ofstream txtFile(txtFilePath);
    if (!txtFile.is_open()) {
        std::cerr << "Error opening txt file: " << txtFilePath << std::endl;
        ddsFile.close();
        return;
    }

    // 写入图像的基本信息
    txtFile << "Width: " << header.width << "\n";
    txtFile << "Height: " << header.height << "\n";
    txtFile << "Mipmap Count: " << header.mipMapCount << "\n";
    txtFile << "Pixel Format: " << header.pfFourCC << "\n\n";

    // 读取并保存每个像素的数据
    std::vector<uint8_t> pixelData(header.width * header.height * 3);  // 假设RGB格式,每个像素3字节
    ddsFile.read(reinterpret_cast<char*>(pixelData.data()), pixelData.size());

    // 写入每个像素的RGB数据
    int index = 0;
    for (uint32_t y = 0; y < header.height; ++y) {
        for (uint32_t x = 0; x < header.width; ++x) {
            uint8_t r = pixelData[index++];
            uint8_t g = pixelData[index++];
            uint8_t b = pixelData[index++];
            
            // 将每个像素的数据写入txt文件
            txtFile << "Pixel (" << x << ", " << y << "): ";
            txtFile << "R: " << (int)r << " G: " << (int)g << " B: " << (int)b << "\n";
        }
    }

    // 关闭文件
    txtFile.close();
    ddsFile.close();

    std::cout << "DDS data has been saved to " << txtFilePath << std::endl;
}

int main() {
    std::string ddsFilePath = "input.dds";
    std::string txtFilePath = "output.txt";

    saveDDSDataToTxt(ddsFilePath, txtFilePath);

    return 0;
}

2.2 代码解析

文件头解析

       DDSHeader结构体描述了DDS文件头的内容。通过读取该结构体,我们能够获取图像的大小、像素格式、以及是否使用了Mipmap等信息。解析这些信息后,我们就可以根据图像尺寸来正确地读取像素数据。

图像数据提取

       假设DDS文件使用的是未压缩的RGB格式,我们直接读取文件中存储的像素数据。每个像素的RGB值占用3个字节,分别代表红色、绿色和蓝色分量。

输出结果

       像素数据以文本格式保存到output.txt文件中,方便进一步分析或查看。文件中的每行数据以Pixel (x, y): R: xxx G: xxx B: xxx的格式存储,表示每个像素的RGB值。

2.3 输出结果

       假设输入DDS文件为一个2x2的RGB图像,输出的output.txt文件内容如下:

Width: 2
Height: 2
Mipmap Count: 1
Pixel Format: 827611204

Pixel (0, 0): R: 255 G: 0 B: 0
Pixel (1, 0): R: 0 G: 255 B: 0
Pixel (0, 1): R: 0 G: 0 B: 255
Pixel (1, 1): R: 255 G: 255 B: 0

2.4 总结

        通过手动解析DDS文件,我们能够更灵活地读取图像数据,而不依赖于第三方图像库。这为一些特殊场景提供了方便,尤其是当需要自定义图像处理或提高性能时。尽管我们目前只处理了未压缩的RGB图像,对于其他压缩格式(如DXT1、DXT5等),代码需要进一步扩展以支持解压缩功能。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐