TGA转换为YUV的C++实现方法

TGA转换为YUV的C++实现方法

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

本文通过C++,实现了将TGA格式图像转换为YUV格式。暂时只讨论Image Type Code为2(无压缩、无调色板的RGB图像)和10(游程编码的RGB图像)的情况,且均支持Targa 16、Targa 24和Targa 32图像的转换。

一. TGA文件结构简介

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

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

0:无图像数据

1:无压缩、有调色板的图像
2:无压缩、无调色板的RGB图像
3:无压缩黑白图像
9:有调色板的游程编码图像
10:游程编码的RGB图像
11:压缩的黑白图像
32:使用Huffman、Delta、游程编码压缩编码的有调色板的图像
33:使用Huffman、Delta、游程编码压缩编码的有调色板的图像,4趟四叉树类型处理
Colour Map Offset32颜色表入口索引
Colour Map Length52颜色表总entry数
Colour Map Depth71每个颜色表entry的位数:
16:Targa 16
24:Targa 24
32:Targa 32
X Origin82图像左下角的x坐标
Y Origin102图像左下角的y坐标
Width122图像宽
Height142图像高
Bits Per Pixel161图像每像素占用的位数
Image Descriptor171图像描述符字节:

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 Field18可变该字段长度由ID Length字段指定。一般被忽略。如果需要存储更多信息,可以放在图像数据之后
Colour Map Data可变可变若Colour Map Type为 0,则该字段不存在。每个调色板entry的长度可为4(RGBA)、3(RGB)或2字节(第1字节为GGBBBBB,第2字节为ARRRRRGG)
Image Data Field可变可变注意,TGA文件采用小端存储方式,RGB分量以B、G、R的顺序存储,图像数据是从左下角开始存储的

二. 基本原理

1. RLE(Run Length Encoding,游程编码)算法的简单介绍

游程编码算法的基本原理其实非常简单,例如我们在微信聊天时,经常会发送这样一句话:

emmmmmmmmmmmmmm

你可能会很自然地想到,后面14个“m”是完全重复的,那么将这一字符串编为“1e14m”就可以很大程度上压缩数据量,这就是RLE的基本原理。

这种压缩编码算法显然是无损的,但是一般来说,它的的压缩比并不高(例如后面将要用到的测试图像,进行了RLE的图像相比同深度的未压缩图像,压缩比也仅有1.1—1.3倍左右),且压缩比随图形复杂程度的上升而下降。

如前所述,在Image Type Code为10的TGA图像中,Image Data Field中的图像数据就采用了RLE算法。其中,数据被分为若干个Packet(数据包),其中共有两类:

  1. Run-Length Packet:存储了连续的不重复RGB(A)值的像素信息;
  2. Raw Packet:存储了具有连续的重复RGB(A)值的像素信息。

数据包的结构为:

名称长度说明
Packet Header1字节第1位为Packet Header ID:0表示为Raw Packet,1表示为RL Packet;
后7位:对于Raw Packet,表示该包中的像素数-1;对于RL Packet,表示游程(Run Size)-1
Packet Data可变对于Raw Packet,该字段中依次存储了包中不同的1—128个像素的RGB(A)值;
对于RL Packet,该字段中存储了1—128个相同的像素的RGB(A)值

以两个Targa 24的数据包为例,我们只要将0x01 B0 G0 R0 B1 G1 R1的数据包还原为G0 R0 B1 G1 R10x83 B G R的数据包还原为B G R B G R B G R即可(其中,“B G R”不表示十六进制数)。

2. RGB与YUV色彩空间转换的基本原理

参见RGB与YUV色彩空间相互转换

三. 思路

  1. 准备工作:打开所需要的文件;
  2. 为TGA文件建立文件头的结构体,将文件头的内容读入;
  3. 根据文件头信息,计算偏移量,找到图像数据的起始点;
  4. 读入图像数据,对RLE压缩的图像进行解码,并根据Bits Per Pixel的不同,分离R、G、B分量;
  5. 将图像上下颠倒,即改为RGB文件的存储方式
  6. 使用rgb2yuv函数,即完成了向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)
{
    switch (bytesPerPixel)
    {
    case 4:
        pixel->r = rgba[2];
        pixel->g = rgba[1];
        pixel->b = rgba[0];
        pixel->a = rgba[3];
        break;

    case 3:
        pixel->r = rgba[2];
        pixel->g = rgba[1];
        pixel->b = rgba[0];
        pixel->a = 255;
        break;

    case 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);
        break;

    default:
        break;
    }
}

void ReadColourData(HEADER* tgaHdPtr, PIXEL* colourData, FILE* tgaPtr)
{
    int bytesPerPx = tgaHdPtr->bitsPerPixel / 8;    // Bytes per pixel
    unsigned char tempRGBA[4];  // Temporary buffer for the RGBA data of 1 pixel

    int n = 0;  // nth pixel
    while (n < tgaHdPtr->width * tgaHdPtr->height)
    {
        switch (tgaHdPtr->imageTypeCode)
        {
        /* Uncompressed, unmapped RGB image */
        case 2:
        {
            /* Read the colour data of 1 pixel */
            if (fread(tempRGBA, 1, bytesPerPx, tgaPtr) != bytesPerPx)
            {
                printf("ERROR!!! Unexpected end of file at pixel %d.\n", n);
                exit(-1);
            }

            SeparateRGBA(&(colourData[n]), tempRGBA, bytesPerPx);
            //printf("%-4x%-4x%-4x\n", colourData[n].b, colourData[n].g, colourData[n].r);   // Check
            n++;
            break;
        }


        /* Run length encoded, unmapped RGB image */
        case 10:
        {
            unsigned char tempPktHeader;    // Temporary buffer for the packet header
            int pktHdID = 0;    // Determines if it's an RL packet or a raw packet 
            int pktRunSize = 0;    // Run size (the number of pixels in this packet)

            /* Read the packet header */
            if (fread(&tempPktHeader, 1, 1, tgaPtr) != 1)
            {
                printf("ERROR!!! Unexpected end of file at pixel %d.\n", n);
                exit(-1);
            }
            pktHdID = (tempPktHeader & 0x80) >> 7;
            pktRunSize = (tempPktHeader & 0x7F) + 1;

            /* Raw packet */
            if (pktHdID == 0)
            {
                for (int i = 0; i < pktRunSize; i++)
                {
                    if (fread(tempRGBA, 1, bytesPerPx, tgaPtr) != bytesPerPx)
                    {
                        printf("ERROR!!! Unexpected end of file at pixel %d.\n", n);
                        exit(-1);
                    }
                    SeparateRGBA(&(colourData[n]), tempRGBA, bytesPerPx);
                    n++;
                }
            }
            /* RL packet */
            else if (pktHdID == 1)
            {
                if (fread(tempRGBA, 1, bytesPerPx, tgaPtr) != bytesPerPx)
                {
                    printf("ERROR!!! Unexpected end of file at pixel %d.\n", n);
                    exit(-1);
                }
                for (int i = 0; i < pktRunSize; i++)
                {
                    SeparateRGBA(&(colourData[n]), tempRGBA, bytesPerPx);
                    n++;
                }
            }
            else
            {
                printf("ERROR!!! Unexpected invalid value of packet header ID.\n");
                exit(-1);
            }

            break;
        }


        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 <iostream>
#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 = "snow32RLE.tga";
    const char* yuvFileName = "snow32RLE.yuv";
    //const char* rgbFileName = "snow32RLE.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 && hd.imageTypeCode != 10)
    {
        cout << "Can only handle image type 2 (uncompressed, unmapped RGB) & image type 10 (run length encoded, unmapped RGB).\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 << " byte(s) 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
以RLE压缩的Targa 32图像为例,输出结果为:

Targa 32 (Image Type Code = 10) 调试控制台的输出结果

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


转换后的YUV图像(Image Type Code = 2)

转换后的YUV图像(Image Type Code = 10)

转换基本成功。

六. 参考文献

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值