TGA转换为YUV的实现方法

TGA转换为YUV的实现方法

TGA(或TARGA)格式,是Truevision公司开发的一种用于描述位图图像的格式,它能够表示从黑白、索引颜色到RGB颜色的位图,支持Α通道,并支持无损压缩方法,兼具了体积小和效果清晰的特点,成为了CG领域影视动画的常用序列输出格式。

本文通过C++,实现了将TGA格式图像转换为YUV格式。目前仅对无压缩、无调色板的TGA图像编写了代码(支持Targa 16、Targa 24和Targa 32),其余情况会在随后的更新中予以补充。

一. TGA文件结构简介

下面先介绍无压缩TGA文件的文件结构:

名称 偏移量(Byte) 长度(Byte) 说明
ID Length 0 1 图像信息字段的长度:为0时表示不存在图像信息字段
Colour Map Type 1 1 调色板类型:
0:无调色板
1:有调色板
Image Type Code 2 1 图像类型码:

0:无图像数据

1:无压缩、有调色板的图像
2:无压缩、无调色板的RGB图像
3:无压缩黑白图像
9:有调色板的游程编码图像
10:游程编码的RGB图像
11:压缩的黑白图像
32:使用Huffman、Delta、游程编码压缩编码的有调色板的图像
33:使用Huffman、Delta、游程编码压缩编码的有调色板的图像,4-pass quadtree-type process
Colour Map Offset 3 1 颜色表入口索引
Colour Map Length 5 2 颜色表总entry数
Colour Map Depth 7 2 每个颜色表entry的位数:
16:Targa 16
24:Targa 24
32:Targa 32
X Origin 8 1 图像左下角的x坐标
Y Origin 10 2 图像左下角的y坐标
Width 12 2 图像宽
Height 14 2 图像高
Bits Per Pixel 16 2 图像每像素占用的位数
Image Descriptor 17 1 图像描述符字节:

bits 3-0——每像素对应的属性位的位数:
Targa 16, 该值为 0 或 1;Targa 24,该值为 0;Targa 32,该值为 8

bit 4——保留,必须为 0

bit 5——屏幕起始位置标志:
0:原点在左下角1;1:原点在左上角;对于truevision图像必须为 0

bits 7-6——交叉数据存储标志:
00:无交叉
01:两路奇/偶交叉
10:四路交叉
11:保留
Image Identification Field 18 可变 该字段长度由ID Length字段指定。一般被忽略。如果需要存储更多信息,可以放在图像数据之后
Color Map Data 可变 可变 若Colour Map Type为 0,则该字段不存在。每个调色板entry的长度可为4(RGBA)、3(RGB)或2字节(第1字节为GGBBBBB,第2字节为ARRRRRGG)
Image Date Field 可变 可变 注意,TGA文件采用小端存储方式,RGB分量以B、G、R的顺序存储,图像数据是从左下角开始存储的

二. 思路

  1. 准备工作:打开所需要的文件;
  2. 为TGA文件建立文件头的结构体,将文件头的内容读入;
  3. 根据文件头信息,计算偏移量,找到图像数据的起始点;
  4. 读入图像数据,并根据Bits Per Pixel的不同,分离R、G、B分量;
  5. 将图像上下颠倒,即改为RGB文件的存储方式
  6. 使用rgb2yuv函数(原理与具体思路参见RGB与YUV色彩空间相互转换),即完成了向YUV文件的转换

三. 源代码

declarations.h

#pragma once
#include <iostream>

typedef struct
{
    char  idLength = 0;         // Length of identification field (length = 1B, offset = 0B)
    char  colourMapType = 0;    // Colour map type (length = 1B, offset = 1B): 0 for no colour map included, 1 for colour map included
    char  imageTypeCode = 0;    // Image type code (length = 1B, offset = 2B): 2 for uncompressed & unmapped RGB image, 10 for run length encoded & unmapped RGB image

    /* Colour map specification (all set to 0 if colour map type is 0) */
    short colourMapOffset = 0;  // Colour map origin (length = 2B, offset = 3B): index of first colour map entry
    short colourMapLength = 0;  // Colour map length (length = 2B, offset = 5B): number of colour map entries
    char  colourMapDepth = 0;   // Colour map depth (length = 1B, offfset = 7B): number of bits in each entry. 16 for the Targa 16, 24 for the Targa 24, 32 for the Targa 32

    /* Image specification */
    short x_origin = 0;         // X coordinate of the lower left corner (length = 2B, offset = 8B)
    short y_origin = 0;         // Y coordinate of the lower left corner (length = 2B, offset = 10B)
    short width = 0;            // Width of image (length = 2B, offset = 12B)
    short height = 0;           // Height of image (length = 2B, offset = 14B)
    char  bitsPerPixel = 0;     // Number of bits in each pixel (length = 1B, offset = 16B)
    char  imageDescriptor = 0;  // Image descripter byte (length = 1B, offset = 17B)
} HEADER;

typedef struct
{
    unsigned char r, g, b, a;
} PIXEL;

void ReadTgaHeader(HEADER* tgaHdPtr, FILE* tgaPtr);
void ReadColourData(HEADER* tgaHdPtr, PIXEL* colourData, FILE* tgaPtr);
void Trans2RgbFormat(PIXEL* colourData, unsigned char* rgbBuff, HEADER* tgaHdPtr);
void rgb2yuv(FILE* yuvPtr, int width, int height, unsigned char* rgbBuff);

ReadTgaHeader.cpp

#include <iostream>
#include "declarations.h"
using namespace std;

void ReadTgaHeader(HEADER* tgaHdPtr, FILE* tgaPtr)
{
    /* Read data in TGA file header fields */
    tgaHdPtr->idLength = fgetc(tgaPtr);
    tgaHdPtr->colourMapType = fgetc(tgaPtr);
    tgaHdPtr->imageTypeCode = fgetc(tgaPtr);
    fread(&tgaHdPtr->colourMapOffset, 2, 1, tgaPtr);
    fread(&tgaHdPtr->colourMapLength, 2, 1, tgaPtr);
    tgaHdPtr->colourMapDepth = fgetc(tgaPtr);
    fread(&tgaHdPtr->x_origin, 2, 1, tgaPtr);
    fread(&tgaHdPtr->y_origin, 2, 1, tgaPtr);
    fread(&tgaHdPtr->width, 2, 1, tgaPtr);
    fread(&tgaHdPtr->height, 2, 1, tgaPtr);
    tgaHdPtr->bitsPerPixel = fgetc(tgaPtr);
    tgaHdPtr->imageDescriptor = fgetc(tgaPtr);

    /* Display header info on screen */
    cout << "\nTGA file header information:\n";
    printf("ID length:         %d\n", tgaHdPtr->idLength);
    printf("Colour Map type:   %d\n", tgaHdPtr->colourMapType);
    printf("Image type code:   %d\n", tgaHdPtr->imageTypeCode);
    printf("Colour map offset: %d\n", tgaHdPtr->colourMapOffset);
    printf("Colour map length: %d\n", tgaHdPtr->colourMapLength);
    printf("Colour map depth:  %d\n", tgaHdPtr->colourMapDepth);
    printf("X origin:          %d\n", tgaHdPtr->x_origin);
    printf("Y origin:          %d\n", tgaHdPtr->y_origin);
    printf("Width:             %d\n", tgaHdPtr->width);
    printf("Height:            %d\n", tgaHdPtr->height);
    printf("Image pixel size:  %d\n", tgaHdPtr->bitsPerPixel);
    printf("Descriptor:        %d\n\n", tgaHdPtr->imageDescriptor);
}

ReadColourData.cpp

#include "declarations.h"

void SeparateRGBA(PIXEL* pixel, unsigned char* rgba, int bytesPerPixel)
{
    if (bytesPerPixel == 4)
    {
        pixel->r = rgba[2];
        pixel->g = rgba[1];
        pixel->b = rgba[0];
        pixel->a = rgba[3];
    }
    else if (bytesPerPixel == 3)
    {
        pixel->r = rgba[2];
        pixel->g = rgba[1];
        pixel->b = rgba[0];
        pixel->a = 255;
    }
    else if (bytesPerPixel == 2)
    {
        pixel->r = (rgba[1] & 0b01111100) << 1;
        pixel->g = ((rgba[1] & 0b00000011) << 6) | ((rgba[0] & 0b11100000) >> 2);
        pixel->b = (rgba[0] & 0b00011111) << 3;
        pixel->a = (rgba[1] & 0b10000000);
    }
}

void ReadColourData(HEADER* tgaHdPtr, PIXEL* colourData, FILE* tgaPtr)
{
    unsigned char rgbaTemp[5];  // Temporary RGBA buffer for a single pixel
    int bytesPerPx = tgaHdPtr->bitsPerPixel / 8;    // Bytes per pixel
    for (int n = 0; n < tgaHdPtr->width * tgaHdPtr->height; n++)
    {
        switch (tgaHdPtr->imageTypeCode)
        {
        case 2: // Uncompressed, unmapped RGB image
            if (fread(rgbaTemp, 1, bytesPerPx, tgaPtr) != bytesPerPx)
            {
                printf("Unexpected end of file at pixel %d.\n", n);
                exit(-1);
            }
            SeparateRGBA(&(colourData[n]), rgbaTemp, bytesPerPx);

            //printf("%-4x%-4x%-4x\n", colourData[n].b, colourData[n].g, colourData[n].r);   // Check
        default:
            break;
        }
    }
}

Trans2RgbFormat

#include "declarations.h"

void Trans2RgbFormat(PIXEL* colourData, unsigned char* rgbBuff, HEADER* tgaHdPtr)
{
    /* Write RGB data in .rgb format into rgbBuff */
    int w = tgaHdPtr->width;
    int h = tgaHdPtr->height;

    for (int i = 0; i < h; i++)   // i for row of image
    {
        for (int j = 0; j < w; j++) // j for column of image
        {
            int rgbPxNum = (h - 1 - i) * w + j; // Pixel number in RGB file
            int tgaPxNum = i * w + j;   // Pixel number in TGA file

            rgbBuff[3 * rgbPxNum + 2] = colourData[tgaPxNum].r;
            rgbBuff[3 * rgbPxNum + 1] = colourData[tgaPxNum].g;
            rgbBuff[3 * rgbPxNum] = colourData[tgaPxNum].b;
        }
    }
}

rgb2yuv.cpp

#include <iostream>
#include "declarations.h"

int rgb66[256], rgb129[256], rgb25[256];
int rgb38[256], rgb74[256], rgb112[256];
int rgb94[256], rgb18[256];

void rgbLookupTable()
{
	for (int i = 0; i < 256; i++)
	{
		rgb66[i] = 66 * i;
		rgb129[i] = 129 * i;
		rgb25[i] = 25 * i;
		rgb38[i] = 38 * i;
		rgb74[i] = 74 * i;
		rgb112[i] = 112 * i;
		rgb94[i] = 94 * i;
		rgb18[i] = 18 * i;
	}
}

void rgb2yuv(FILE* yuvPtr, int width, int height, unsigned char* rgbBuff)
{
	int pxCount = width * height;
	unsigned char* yBuff = new unsigned char[pxCount];		// Buffer for Y component
	unsigned char* uBuff = new unsigned char[pxCount / 4];	// Buffer for U component
	unsigned char* vBuff = new unsigned char[pxCount / 4];	// Buffer for V component
	unsigned char* uBuff444 = new unsigned char[pxCount];	// Buffer for U component in 4:4:4 format
	unsigned char* vBuff444 = new unsigned char[pxCount];	// Buffer for V component in 4:4:4 format

	// RGB to YUV (4:4:4)
	for (int i = 0; i < pxCount; i++)	// i for pixel number
	{
		unsigned char r = rgbBuff[3 * i + 2];	// R component of the ith pixel
		unsigned char g = rgbBuff[3 * i + 1];	// G component of the ith pixel
		unsigned char b = rgbBuff[3 * i];		// B component of the ith pixel
		rgbLookupTable();
		yBuff[i] = ((rgb66[r] + rgb129[g] + rgb25[b]) >> 8) + 16;
		uBuff444[i] = ((-rgb38[r] - rgb74[g] + rgb112[b]) >> 8) + 128;
		vBuff444[i] = ((rgb112[r] - rgb94[g] - rgb18[b]) >> 8) + 128;
	}

	// 4:4:4 to 4:2:0
	for (int i = 0; i < height; i += 2)
	{
		for (int j = 0; j < width; j += 2)
		{
			uBuff[i / 2 * width / 2 + j / 2] = uBuff444[i * width + j];
			vBuff[i / 2 * width / 2 + j / 2] = vBuff444[i * width + j];
		}
	}
	delete[]uBuff444;
	delete[]vBuff444;

	fwrite(yBuff, sizeof(unsigned char), pxCount, yuvPtr);
	fwrite(uBuff, sizeof(unsigned char), pxCount / 4, yuvPtr);
	fwrite(vBuff, sizeof(unsigned char), pxCount / 4, yuvPtr);
}

main.cpp

#include "stdio.h"
#include <iostream>
#include "stdlib.h"
#include "math.h"
#include "declarations.h"
using namespace std;

int main(int argc, char* argv[])
{
    /* Declarations */
    FILE* tgaFilePtr, * yuvFilePtr;
    //FILE* rgbFilePtr;
    HEADER hd;  // Structure variable for TGA file header
    int w, h, pxCount;
    const char* tgaFileName = "snow24.tga";
    const char* yuvFileName = "snow24.yuv";
    //const char* rgbFileName = "snow24.rgb";
    PIXEL* rgbaData = NULL;  // Entire RGBA data of TGA file; used for future funtions
    unsigned char* rgbBuffer = NULL; // RGB data of TGA file (in .rgb format); extracted from rgbaData; used for tga2yuv
    int offset = 0;

    /* Open the files */
    if (fopen_s(&tgaFilePtr, tgaFileName, "rb") == 0)
    {
        cout << "Successfully opened \"" << tgaFileName << "\".\n";
    }
    else
    {
        cout << "Failed to open \"" << tgaFileName << "\".\n";
        exit(-1);
    }
    if (fopen_s(&yuvFilePtr, yuvFileName, "wb") == 0)
    {
        cout << "Successfully opened \"" << yuvFileName << "\".\n";
    }
    else
    {
        cout << "Failed to open \"" << yuvFileName << "\".\n";
        exit(-1);
    }
    //if (fopen_s(&rgbFilePtr, rgbFileName, "wb") == 0)
    //{
    //    cout << "Successfully opened \"" << rgbFileName << "\".\n";
    //}
    //else
    //{
    //    cout << "Failed to open \"" << rgbFileName << "\".\n";
    //    exit(-1);
    //}



    /* Read and display the header fields */
    ReadTgaHeader(&hd, tgaFilePtr);
    w = hd.height;
    h = hd.width;
    pxCount = w * h;

    /* Space allocation */
    rgbaData = new PIXEL[hd.width * hd.height];
    memset(rgbaData, 0, hd.height * hd.width);   // Initialisation
    rgbBuffer = new unsigned char[hd.width * hd.height * 3];
    memset(rgbBuffer, 0, hd.height * hd.width * 3);   // Initialisation

    /* Developed function check & invalidation check */
    if (hd.imageTypeCode != 2)
    {
        cout << "Can only handle image type 2 (uncompressed, unmapped RGB image).\nOther options being developed.\n";
        exit(-1);
    }
    if (hd.bitsPerPixel != 16 && hd.bitsPerPixel != 24 && hd.bitsPerPixel != 32)
    {
        cout << "Invalid value of image pixel size!\nCan only handle pixel depths of 16, 24, and 32.\n";
        exit(-1);
    }
    if (hd.colourMapType != 0 && hd.colourMapType != 1)
    {
        cout << "Invalid value of colour map type!\nCan only handle colour map types of 0 and 1.\n";
        exit(-1);
    }

    /* Skip over unnecessary chunks */
    offset += hd.idLength;
    offset += hd.colourMapType * hd.colourMapLength;  // ????应该还要乘hd.colourMapDepth吧?
    cout << offset << " bytes skipped over.\n\n";
    fseek(tgaFilePtr, offset, SEEK_CUR);  // Skip 'offset' bytes from the end of header

    /* Read the image RGB (A, if exists) data */
    ReadColourData(&hd, rgbaData, tgaFilePtr);

    /* Transform .tga formatted RGB data into .rgb format */
    Trans2RgbFormat(rgbaData, rgbBuffer, &hd);
    //fwrite(rgbBuffer, 3, pxCount, rgbFilePtr);

    /* Transform RGB into YUV */
    rgb2yuv(yuvFilePtr, w, h, rgbBuffer);

    delete[]rgbaData;
    delete[]rgbBuffer;
    //fclose(rgbFilePtr);
    fclose(tgaFilePtr);
    fclose(yuvFilePtr);
}

四. 运行结果

实验所用的测试图像为“snow.jpeg”通过Adobe Photoshop转换而来的“snow16.tga”“snow24.tga”和“snow32.tga”。原始图像如下:


snow.jpeg

将转换的图像用YUV Viewer Plus打开,结果如下:


snow32.yuv、snow24.yuv和snow16.yuv
## 五. 参考文献
发布了14 篇原创文章 · 获赞 3 · 访问量 3238

猜你喜欢

转载自blog.csdn.net/szzheng/article/details/105283603