小工具:用C++读取TGA并输出数据到文本

需求

图像中存储的数据既有可能是 0.0 ~ 1.0 代表颜色强度的值,也有可能是代表 0 ~ 255 的序号。如果是序号,则用普通的观察方式会比较困难,尤其是当序号差别较小时,眼睛是无法区分的,比如:
小工具:用C++读取TGA并输出数据到文本
(不过我并不确定上图中数据的含义是否真的是序号)

因此,我想做一段C++小程序来读取TGA图片中的数据,然后将值输出到文本中。

我最开始参考的是C++读写Tga文件_猫哥-CSDN博客,这里的程序很简洁,但是对于文件头并没有解释,而且似乎只针对于“32位色的无压缩”。我之后又看到了https://github.com/aseprite/tga,这个比较全,也定义了文件头结构体,我的代码中的TGA文件头的定义也拷贝自它。

另外,TGA的wiki上有对这个格式包括文件头更详细的描述。我在附录中贴出wiki上的文件头解释。

代码


#include<iostream>
#include<fstream>
#include <iomanip>
using namespace std;

//TGA文件头
struct TGAHeader
{
    uint8_t  idLength;
    uint8_t  colormapType;
    uint8_t  imageType;         //图像类型
    uint16_t colormapOrigin;
    uint16_t colormapLength;
    uint8_t  colormapDepth;
    uint16_t xOrigin;
    uint16_t yOrigin;
    uint16_t width;             //宽度
    uint16_t height;            //高度
    uint8_t  bitsPerPixel;      //每像素多少位
    uint8_t  imageDescriptor;
};

//ImageType对应的含义,来源于Wiki:https://en.wikipedia.org/wiki/Truevision_TGA
string GetImageTypeString(uint8_t imageType)
{
    if (imageType == 0) return "no image data is present";
    else if (imageType == 1) return "uncompressed color-mapped image";
    else if (imageType == 2) return "uncompressed true-color image";
    else if (imageType == 3) return "uncompressed black-and-white(grayscale) image";
    else if (imageType == 9) return "run-length encoded color-mapped image";
    else if (imageType == 10) return "run-length encoded true-color image";
    else if (imageType == 11) return "run-length encoded black-and-white(grayscale) image";
}

int main()
{
    //TGA文件名:
    const string TGAFileName = "PP2_rgb_XVect_a_XExtent.TGA";

    //打开TGA文件
    FILE* file_tga;
    fopen_s(&file_tga, TGAFileName.c_str(), "rb");

    //读取文件头
    TGAHeader data_header;
    {
        //fread(&data_header, sizeof(TGAHeader), 1, file_tga);//直接读取会有结构体的对齐问题
        fread(&data_header.idLength, sizeof(uint8_t), 1, file_tga);
        fread(&data_header.colormapType, sizeof(uint8_t), 1, file_tga);
        fread(&data_header.imageType, sizeof(uint8_t), 1, file_tga);
        fread(&data_header.colormapOrigin, sizeof(uint16_t), 1, file_tga);
        fread(&data_header.colormapLength, sizeof(uint16_t), 1, file_tga);
        fread(&data_header.colormapDepth, sizeof(uint8_t), 1, file_tga);
        fread(&data_header.xOrigin, sizeof(uint16_t), 1, file_tga);
        fread(&data_header.yOrigin, sizeof(uint16_t), 1, file_tga);
        fread(&data_header.width, sizeof(uint16_t), 1, file_tga);
        fread(&data_header.height, sizeof(uint16_t), 1, file_tga);
        fread(&data_header.bitsPerPixel, sizeof(uint8_t), 1, file_tga);
        fread(&data_header.imageDescriptor, sizeof(uint8_t), 1, file_tga);
    }

    //输出文件头的一些信息
    {
        cout << "Image Type : " << GetImageTypeString(data_header.imageType) << endl;
        cout << "每像素位数 : " << (int)data_header.bitsPerPixel << endl;
        cout << "宽度 : " << data_header.width << endl;
        cout << "高度 : " << data_header.height << endl;
    }

    if (data_header.bitsPerPixel != 32)//只考虑32位的数据
        return 0;
    
    //像素数据从尺寸:
    int dataSize = data_header.width * data_header.height * 4;
    //读取颜色数据
    uint8_t* data_color = new uint8_t[dataSize];
    fread(data_color, sizeof(uint8_t), dataSize, file_tga);

    //存储到TXT中:
    {
        //TXT名字:
        string TXTFileName = TGAFileName;
        TXTFileName.replace(TXTFileName.length() - 3, 3, "txt");

        //打开写入的txt文件
        ofstream file_txt;  
        file_txt.open(TXTFileName, ios::out);

        for (int h = 0; h < data_header.height; h++)
        {
            for (int w = 0; w < data_header.width; w++)
            {
                int index = data_header.width * h + w;

                file_txt << "(";
                file_txt << setw(3) << (int)data_color[index * 4 + 0];//R
                file_txt << ",";
                file_txt << setw(3) << (int)data_color[index * 4 + 1];//G
                file_txt << ",";
                file_txt << setw(3) << (int)data_color[index * 4 + 2];//B
                file_txt << ",";
                file_txt << setw(3) << (int)data_color[index * 4 + 3];//A
                file_txt << ")    ";
            }
            file_txt << endl;
        }

        file_txt.close();
    }
}

最后将输出txt文本:
小工具:用C++读取TGA并输出数据到文本
这样就可以看清具体的值了。

问题记录:结构体对齐问题

一开始出了问题,因为没有考虑到结构体的对齐问题。这个问题在这里【原创&交流】使用标准C库读文件时需要注意的一个问题也有讨论。

比如对于我这里的TGA文件头,其实只有18Byte的尺寸,但是如果以sizeof输出则有20:
小工具:用C++读取TGA并输出数据到文本
因此最后我选择了单独为每段数据读取。

附录:wiki上的TGA文件头描述

小工具:用C++读取TGA并输出数据到文本

上一篇:day-12(副)代码


下一篇:2021-10-07