TGA文件分析
TGA简介
TGA是由美国Truevision公司为其显示卡开发的一种图像文件格式,已被国际上的图形、图像工业所接受。现已成为数字化图像,以及运用光线跟踪算法所产生的高质量图像的常用格式。TGA文件的扩展名为.tga,该格式支持压缩,使用不失真的压缩算法,可以带通道图,另外还支持行程编码压缩。
TGA在兼顾了BMP的图象质量的同时又兼顾了JPEG的体积优势。并且还有自身的特点:通道效果、方向性。在CG领域常作为影视动画的序列输出格式,因为兼具体积小和效果清晰的特点。
文件格式分析
TGA原始文件结构(v1.0)由两个部分组成:
- 文件头(Tga File Header):由图像描述信息字段长度、颜色表类型、图像类型、颜色表说明和图像说明五个字段组成,总计18字节,描述了图像存储的基本信息,应用程序可依据该部分字段值读写图像数据。
- 图像/颜色表数据(Image/Color Map Data):由图像描述信息(可选)、颜色表数据和图像数据三部分组成,用于存储图片的图像信息。
TGA扩展文件结构(v2.0)在此基础上增加了三个部分:
- 开发者自定义区域(Developer Area):包含开发者定义字段列表和开发者字典(用于存储开发者定义字段的值),该区域为开发者扩展该文件格式提供接口,以便存储额外的信息。
- 扩展区域(Extension Area):由扩展区域大小、作者姓名、作者注释、日期/时间、工作名称/ID、工作累计耗时、编辑软件的名称、编辑软件的版本、关键颜色、像素宽高比、灰度值、颜色校正表偏移量、缩略图偏移量、扫描线表偏移量、alpha通道类型、扫描线表、缩略图图像数据和颜色校正表组成,为Truevision公司定义的标准扩展功能,以提供更多的图像附加信息。
- 文件尾(TGA File Footer):由扩展区域偏移量、开发者目录偏移量和TGA文件扩展格式签名三部分组成,用于验证TGA文件扩展格式,并可以确定扩展区域和开发者字典的位置。
代码实现
main.cpp
#include <iostream>
#include <cstdio>
#include <fstream>
#include <windows.h>
#include "rgb2yuv.h"
#pragma pack(1)
using namespace std;
typedef struct _TgaHeader
{
BYTE IDLength; /* 00h Size of Image ID field */
BYTE ColorMapType; /* 01h Color map type */
BYTE ImageType; /* 02h Image type code */
WORD CMapStart; /* 03h Color map origin */
WORD CMapLength; /* 05h Color map length */
BYTE CMapDepth; /* 07h Depth of color map entries */
WORD XOffset; /* 08h X origin of image */
WORD YOffset; /* 0Ah Y origin of image */
WORD Width; /* 0Ch Width of image */
WORD Height; /* 0Eh Height of image */
BYTE PixelDepth; /* 10h Image pixel size */
BYTE ImageDescriptor; /* 11h Image descriptor byte */
} TGAHEAD;
void Read_rgb(ifstream& file, unsigned char* r_buff, unsigned char* g_buff, unsigned char* b_buff, int height, int width) {
for (int i = height - 1; i >= 0; i--) {
for (int j = 0; j < width; j++) {
file.read((char*)(b_buff + (i * width + j)), 1);
file.read((char*)(g_buff + (i * width + j)), 1);
file.read((char*)(r_buff + (i * width + j)), 1);
}
}
}
int main()
{
ifstream TgaFile("D:\\数据压缩实验\\4.tga", ios::binary); /*以二进制输入方式打开文件*/
ofstream YuvFile("test4.yuv", ios::binary);
if (!TgaFile) { cout << "打开tga文件失败!" << endl; }
else cout << "打开tga文件成功" << endl;
if (!YuvFile) { cout << "打开yuv文件失败!" << endl; }
else cout << "打开yuv文件成功" << endl;
TGAHEAD my_tgaheader;
TgaFile.read((char*)(&my_tgaheader), 18);
unsigned char* ImageData = NULL;
unsigned char* b = NULL;
unsigned char* g = NULL;
unsigned char* r = NULL;
unsigned char* Y = NULL;
unsigned char* U = NULL;
unsigned char* V = NULL;
unsigned char* u = NULL;
unsigned char* v = NULL;
Init_table();
int offset = 18;
int width = my_tgaheader.Width;
int height = my_tgaheader.Height;
cout << "test图像的分辨率:" << width << "*" << height << endl;
r = new unsigned char[width * height];
g = new unsigned char[width * height];
b = new unsigned char[width * height];
Y = new unsigned char[width * height];
U = new unsigned char[width * height];
V = new unsigned char[width * height];
if (my_tgaheader.IDLength == 0) { cout << "无图像信息字段" << endl; }
else {
cout << "存在图像信息字段" << endl;
offset = offset + my_tgaheader.IDLength;
}
if (my_tgaheader.ColorMapType == 0) { cout << "不使用颜色板" << endl; }
else if (my_tgaheader.ColorMapType == 1) { cout << "使用颜色板" << endl; }
else { cout << "其他图像类型" << endl; }
if (my_tgaheader.ImageType == 1)
{
cout << "未压缩的颜色表图像" << endl;
cout << "元素表颜色总数:" << int(my_tgaheader.CMapLength) << endl;
cout << "每个元素大小:" << int(my_tgaheader.CMapDepth) << endl;
cout << "像素深度:" << int(my_tgaheader.PixelDepth) << endl;
offset = offset + (my_tgaheader.CMapLength * my_tgaheader.CMapDepth);
TgaFile.seekg(offset, ios::beg);
}
else if (my_tgaheader.ImageType == 2) {
cout << "未压缩的真彩色图像" << endl;
TgaFile.seekg(offset, ios::beg);
Read_rgb(TgaFile, r, g, b, height, width);
}
else {
cout << "其他图像类型" << endl;
return 0;
}
u = new unsigned char[int(width * height / 4)];
v = new unsigned char[int(width * height / 4)];
CaculateYUV(height, width, r, g, b, Y, U, V);
SampleYUV(height, width, U, V, u, v);
WriteYUV(height, width, Y, u, v, YuvFile);
}
rgb2yuv
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <malloc.h>
using namespace std;
float RGB2YUV02990[256],RGB2YUV05870[256],RGB2YUV01140[256],
RGB2YUV01684[256],RGB2YUV03316[256],RGB2YUV05000[256],
RGB2YUV04187[256],RGB2YUV00813[256];
void Init_table() {
for(int i = 0; i < 256; i++) {
RGB2YUV02990[i]= (float)0.2990 * i;
RGB2YUV05870[i]= (float)0.5870 * i;
RGB2YUV01140[i]= (float)0.1140 * i;
RGB2YUV01684[i]= (float)0.1684 * i;
RGB2YUV03316[i]= (float)0.3316 * i;
RGB2YUV05000[i]= (float)0.5000 * i;
RGB2YUV04187[i]= (float)0.4187 * i;
RGB2YUV00813[i]= (float)0.0813 * i;
}
}
// 计算YUV
void CaculateYUV(int height, int width,
unsigned char* Red, unsigned char* Green, unsigned char* Blue, unsigned char*
Y, unsigned char* U, unsigned char* V) {
float temp;
for(int i = 0; i < height * width; i++) {
//处理Y分量
temp= RGB2YUV02990[*(Red + i)] + RGB2YUV05870[*(Green +i)] + RGB2YUV01140[*(Blue +i)];
temp= temp > 236 ? 236 : temp;
temp= temp < 16 ? 16 : temp;
*(Y+ i) = (unsigned char)temp;
//处理U分量
temp= -RGB2YUV01684[*(Red + i)] - RGB2YUV03316[*(Green+i)] + RGB2YUV05000[*(Blue
+ i)] + 128;
temp= temp > 236 ? 236 : temp;
temp= temp < 16 ? 16 : temp;
*(U+ i) = (unsigned char)temp;
//处理V分量
temp= RGB2YUV05000[*(Red + i)] - RGB2YUV04187[*(Green+i)] - RGB2YUV00813[*(Blue +i)] + 128;
temp= temp > 236 ? 236 : temp;
temp= temp < 16 ? 16 : temp;
*(V+ i) = (unsigned char)temp;
}
}
// 写入YUV文件422格式
void WriteYUV(int Height, int Width,
unsigned char* Y, unsigned char* U, unsigned char* V, ofstream& YuvFile) {
YuvFile.write((char*)Y,Height * Width);
YuvFile.write((char*)U,Height * Width / 4);
YuvFile.write((char*)V,Height * Width / 4);
YuvFile.close();
}
// 对UV进行下采样
void SampleYUV(int Height, int Width,
unsigned char* U, unsigned char* V, unsigned char* u, unsigned char* v)
{
int k = 0;
float average_u, average_v;
for(int i = 0; i < Height * Width / 4; i++) {
average_u= (float)(*(U + k) + *(U + k + 1) + *(U + k +Width) + *(U + k + 1 + Width)) /4.0;
average_v= (float)(*(V + k) + *(V + k + 1) + *(V + k +Width) + *(V + k + 1 + Width)) /4.0;
*(u + i) = (unsigned char)average_u;
*(v + i) = (unsigned char)average_v;
if((i + 1) % (Width / 2) == 0) {
k= k + 2 + Width;
}
else{
k= k + 2;
}
}
}
实验结果及分析
通过此程序,我对tga文件格式有了进一步了解,包括如何用代码读取TGA图片文件偶信息和每个像素的RGB信息。
在编程过程中,值得注意的两点是:
- 结构体是按字节对齐的方式存储的,即以结构体成员中占内存最多的数据类型所占的字节数为标准,所有的成员在分配内存时都要与这个长度对齐。需要通过 #pragma pack(1) 取消字节对齐。
- 文件流类提供了许多不同的成员函数,可以用来在文件中移动。其中的一个方法如下: seekg(offset, place); 新位置将从由 place 给出的起始位置开始,偏移 offset 个字节。通过读取 Image ID 和 Color Map Data 的长度,可以确定offset值使文件从Image Data开始读。
参考文献:
http://www.fileformat.info/format/tga/egff.htm
http://blog.sina.com.cn/s/blog_814e83d801014t3m.html
https://baike.baidu.com/item/tga/27071#viewPageContent