# 前言
实现软光栅渲染第一步,先要个画布,目前是直接画在 TGA 图像上,之后会考虑使用图形界面呈现。
实际关于 TGA 文件解析的文章有很多,写的也很详细,写这个主要是为了记录我实现的 TGA 图像读取和生成的大致过程,避免自己之后看不懂自己写的代码。
完整的代码地址在此。
# TGA 文件格式
# TGA 文件头
TGA 文件头一共分为五个部分,如下图
位域 | 长度 | 类型 | 描述 |
---|---|---|---|
1 | 1byte | Id length | 取值 0~255. 0 表示无 id 段 |
2 | 1byte | Color map type | 0: 不使用颜色表; 1: 使用颜色表 |
3 | 1byte | Image type | 0~127 为 Truevision 公司用于一般应用,128~255 供开发者使用. 0: 没有图像数据; 1: 未压缩的颜色表图像; 2: 未压缩的 TRUE-COLOR 图像; 3: 未压缩的黑白图像; 9: RLE 压缩的颜色表图像; 10: RLE 压缩的 TRUE-COLOR 图像; 11: 压缩的黑白图像. |
4 | 5byte | Color map specification | 若 Header.Color map type = 0, 则没有颜色表存在,这 5byte 应全为 0. |
2byte | First Entry Index | 颜色表起点,即颜色表第一项的索引 | |
2byte | Color map Length | 颜色表长度,即颜色表共有多少项 | |
1byte | Color map Entry Size | 颜色表项大小,即每个项占用 bit 数。典型值 15, 16, 24, 32. 即使是 15, 每次也要用 16bit 解析,然后忽略第 16bit | |
5 | 10byte | Image specification | 描述图像维度和格式 |
2byte | X origin of Image | 图像起点 (左下角) x 坐标。基于屏幕原点在左下角 | |
2byte | Y origin of Image | 图像起点 (左下角) y 坐标。基于屏幕原点在左下角 | |
2byte | Image Width | 图像宽度 (单位: pixel) | |
2byte | Image Height | 图像高度 (单位: pixel) | |
1byte | Pixel Depth | 像素深度,即每个像素的 bit 数,包含属性或 α 通道。常用值 8, 16, 24, 32 | |
1byte | Image Descriptor | 0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero) 1: undefined data in the Alpha field, can be ignored 2: undefined data in the Alpha field, but should be retained 3: useful Alpha channel data is present 4: pre-multiplied Alpha (see description below) 5 -127: RESERVED 128-255: Un-assigned |
# RLE 压缩算法
行程编码压缩 (RLE) 算法利用了这样一个事实,即许多类型的图像都有很大的部分里的像素值都是相同的。因此可以把相同的连续的像素进行压缩存储。
RLE 图像包括两种类型的数据元素:行程数据包 (Run-length Packets) 和原始数据包 (Raw Packets), 这两个 Packets 的数据结构是一样的,如下表格。
Field Name | Packet Type | Pixel Count | Pixel Data |
---|---|---|---|
Field Size | 1bit | 7bits | Variable |
注: pixel Count 为 7bits + 1, 假设 7bits 表示 12,则实际的 piexl Count 为 13
# 行程数据包 (Run-length Packets)
当 Packet Type 为 1 时,该数据为行程数据包,是已压缩的图像数据。假设有个行程数据包如下
Packet Type | Pixel Count | Pixel Data |
---|---|---|
1 | 0000011 | 0x12 |
该行程数据包是单通道的像素数据,且数量为 4 个,则最终解析出的图像数据为 0x12 0x12 0x12 0x12
# 原始数据包 (Raw Packets)
当 Packet Type 为 0 时,该数据为原始数据包,是未压缩图像数据。假设有个原始数据包如下
Packet Type | Pixel Count | Pixel Data |
---|---|---|
0 | 0000011 | 0x12 0x11 0x10 0x13 |
该原始数据包是单通道的像素数据,且数量为 4 个,则最终解析出的图像数据为 0x12 0x11 0x10 0x13
# TGA 图像生成
对于渲染生成的 TGA 图像,支持存储为不进行 RLE 压缩的真彩色图像或黑白图像 (包括 α 通道),像素深度支持 8、16、24、32 位,不使用颜色表查询。
typedef struct { | |
unsigned char* data; | |
int width, height, channels; | |
float* depthBuffer; // z-buffer | |
}image_t; | |
static void tga_save(FILE* file, image_t *image) { | |
// write tga header to file | |
unsigned char tgaHeader[18]; | |
memset(tgaHeader, 0, 18); | |
tgaHeader[2] = image->channels != 1 ? 2 : 3; // image type | |
tgaHeader[12] = (unsigned char)(image->width & 0xFF); // image width lsb | |
tgaHeader[13] = (unsigned char)((image->width >> 8) & 0xFF); // image width msb | |
tgaHeader[14] = (unsigned char)(image->height & 0xFF); // image height lsb | |
tgaHeader[15] = (unsigned char)((image->height >> 8) & 0xFF); // image height msb | |
tgaHeader[16] = (unsigned char)((image->channels * 8) & 0xFF); // pixel depth | |
tgaHeader[17] = (image->channels == 2 || image->channels == 4) * 8; // image descriptor | |
fwrite(tgaHeader, 1, 18, file); | |
// write image data to file | |
fwrite(image->data, 1, image->width * image->height * image->channels, file); | |
} |
# TGA 图像加载
目前个人对 RLE 图像加载的使用场景很简单,图像没有使用颜色表,因此只是读取 TGA 文件头的图像大小和通道以及是否使用了 RLE 压缩算法这些信息,若未使用 RLE 算法,则把图像信息一次性加载出来,若使用了 RLE 算法,则进行解码获取图像数据。
static image_t *tga_load(FILE *file) { | |
// read tga file header | |
int width, height, channels; | |
bool isRLE; | |
read_tga_header(file, &width, &height, &channels, &isRLE); | |
image_t *image = image_new(width, height, channels); | |
if (isRLE) { | |
load_rle_image(file, image); | |
} else { | |
fread(image->data, 1, image->width * image->height * image->channels, file); | |
} | |
return image; | |
} | |
static void load_rle_image(FILE* file, image_t* image) { | |
int totalPixels = image->width * image->height * image->channels; | |
int currentPixels = 0; | |
while (currentPixels < totalPixels) { | |
unsigned char firstFiled = fgetc(file); | |
bool rlePacket = firstFiled & 0x80; | |
int pixelCount = (firstFiled & 0x7F) + 1; | |
if (rlePacket) { /* Run-Length Packet */ | |
unsigned char pixels[4]; | |
fread(pixels, 1, image->channels, file); | |
for (int i = 0; i < pixelCount; i++) { | |
for (int j = 0; j < image->channels; j++) { | |
image->data[currentPixels++] = pixels[j]; | |
} | |
} | |
} else { /* Raw Packet */ | |
for (int i = 0; i < pixelCount; i++) { | |
for (int j = 0; j < image->channels; j++) { | |
image->data[currentPixels++] = fgetc(file); | |
} | |
} | |
} | |
} | |
} |
# renference
【数据压缩】TGA 文件格式分析
为 GLUT 应用编写 TGA 图像加载程序
Truevision TGAª FILE FORMAT SPECIFICATION Version 2.0
自制光栅渲染器(1):绘制一个点
TGA 文件格式