数值的存储都是小端模式:little endian,网络的传输是大端模式:big endian。
1.整数用补码表示
正数和0的补码是自身;负数的补码是符号位不变,其余各位取反,最后加1(加1是因为二进制最大只能表示到模减1大小)。
正数 负数
原码 就是其自身 符号位置1,数值部分不变
反码 就是其自身 符号位置1,数值部分取反
补码 就是其自身 符号位置1,数值部分取反加1
移码 对应补码的符号位直接变反即可
//计算机是以补码的形式存放数据的
补码(two's complement) 在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值位统一处理(符号位参与,都为正数处理);同时,加法和减法也可以统一处理。
补码加法
[X+Y]补 = [X]补 + [Y]补
补码减法
[X-Y]补 = [X]补 - [Y]补 = [X]补 + [-Y]补【1】
【X*Y】补==【X】补×Y
实例:
[+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补
于是补码的出现, 解决了0的符号以及两个编码的问题:
1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原
这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:
(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补
整数的溢出及异常:
整数是不会出现溢出异常的,整数的溢出被认为是正常的舍弃(其实只要很合理)。整数只有被0除才会异常,而浮点数,即使被0除也不会抛出异常。
计算机离散数特殊现象之绝对值:
绝对值等于自己的数有两个,0 和最小的负数。
计算机中绝对值表示:
abs(x) := (x >= 0) ? x : -x
负数在计算机中以补码形式存储,计算补码的方式和取反操作类似。
符号位不变,其它位取反,最后加一。
现在我们回到最小的负数问题,最小的负数在计算机中表示为 1000,000,下面我们对这个数操作
补码: 1000,0000
各位取反: 0111,1111
加一: 1000,0000
整数内存中十六进制表示(二进制)和十进制转换:
int TestNum = -1;
char *pNum = (char*)&TestNum;
char szNumBuffer[256];
// szNumBuffer的结果只是存储表示,并不是数字的正序十六进制表示
sprintf_s(szNumBuffer, "%02x%02x%02x%02x",(unsigned char)*pNum, unsigned char(*(pNum + 1)), unsigned char(*(pNum + 2)), unsigned char(*(pNum + 3)));
union uTagNum
{
int a;
char b[4];
}uTagData;
// 十六进制字符拷贝到联合体中,因为浮点数不能左移运算
string strNumBuffer = szNumBuffer;
for(int i = 0; i < 4; i++)
{
int hex = 0;
string hexStr = strNumBuffer.substr(2 *i, 2);
sscanf(hexStr.c_str(), "%02x", &hex);
uTagData.b[i] = hex; // 3-i需要小端模式存放,也就是低字节在低位置
}
printf("整数本来的值:%d\n", uTagData.a);
2.浮点数用IEEE 754标准浮点数格式表示
公式:将浮点数化为:Sx(2^E)xF 后转换为:(-1)Sx(2^(E + 127))x(F - 1),得到S+(E+127)+(F-1)的二进制数,也就是十六进制数。
浮点数表示格式:float:符号位(1) 指数位(8) 尾数位(23); double:也是类似的方式,只是位数不同。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字F。
S表示正负,0正,1负;E用移码表示(+127);F用原码表示(无论是IEEE 754标准还是IBM 370格式)。
浮点数的精度问题,溢出异常:
1)比较精度问题:浮点数的比较,精度定位到七位或者六位,或者自定义精度,这样可以得到自己想要的结果。
2)运算精度问题:浮点数的运算也存在有效位,差异大的数相加减会舍掉小的数的精度,高精度尽量用double表示。
3)转换精度问题:直接float,double和 int long long之间转换,因为内存中的表示差异会得到不一样的值,转换会发生错误。
4)溢出问题:除0操作,0与无穷大运算,无穷大与无穷大运算
若E位为255并且F位为0时表示无穷大的数,此时浮点数受S位影响,例如0x7F800000表示正无穷大,0xFF800000表示负无穷大。当我们使用1个数除以0时,结果将被记作0x7F800000。无穷大的调试表示为:1.#INF,当除以0的时候会发生。INF是infinity 无穷大的意思。
若E位为255并且F位不为0时表示非数值,也就是说是非法数,例如0x7F800001。非法数的调试表示为:1.#IND, 对负数开平方,对负数取对数,0.0/0.0,0.0*∞, ∞/∞ 等会产生。或者表示为NAN。IND是indeterminate不确定/模糊的意思,NAN是not a number的意思。
int nTest = -1;// 0x1000 0001,补码为0xFFFF FFFF
char *pNPointer = (char*)&nTest;
char szBuffer[256];
sprintf_s(szBuffer, "内存字节:%0x:%0x:%0x:%0x", *pNPointer, *(pNPointer + 1), *(pNPointer + 2), *(pNPointer + 3));
// 浮点数在内存中的表示,没有使用补码,而是IEEE 754的浮点数表示形式,S是符号位,E是指数,F是原码
浮点数内存中十六进制表示(二进制)和十进制相互转换:
string DigitalData::TestFloat(float fData)
{
unsigned char *pData = (unsigned char*)&fData;
char szBuffer[32];
//再转化为16进制为:47 F1 20 00,最后把它翻过来,就成了:00 20 F1 47。
unsigned char Tail = *(pData);
unsigned char Tail2 = *(pData + 1);
unsigned char Exp = *(pData + 2);
unsigned char Head = *(pData + 3);
sprintf_s(szBuffer, "%02x%02x%02x%02x", Head, Exp,Tail2, Tail);
//printf(szBuffer);
return string(szBuffer);
}
float DigitalData::GetFloatFromString( string &strFloatValue)
{
union
{
float fValue;
char szBuffer[4];
}tempUnion;
// 十六进制字符拷贝到联合体中,因为浮点数不能左移运算
for(int i = 0; i < 4; i++)
{
int hex = 0;
string hexStr = strFloatValue.substr(2 *i, 2);
sscanf(hexStr.c_str(), "%02x", &hex);
tempUnion.szBuffer[ 3 - i] = hex; // 3-i需要小端模式存放,也就是低字节在低位置
}
return tempUnion.fValue;
}
3.非数值类型char/singed char/unsigned char的区别-数值直接转换需要看原类型 但unsigned char的范围更广和十六进制正确/ 指针间接解析字节都正确
总结:
1. 直接数值转换:
在表示数值类型时候,char和unsigned char表示范围不同,转换时候需要注意原来的类型,否则数值直接转换都会出现问题。但当转换为大数据类型时候,char会根据首位来对十六进制前面拓展编码为0或者1,改变了原来编码;unsigned char转换为大数据类型时候,都是拓展编码了0,没有改变原来十六进制编码的含义。
2.非数值表示含义:
但在表示非数值类型时候,unsigned char可以支持Ascii以外的拓展编码,可以表示更多的非数值含义。
3.指针遍历间接转换:
指针遍历时候,没有对字节上面的值进行截取转换,所以char *和unsigned char*间接转换为大数据类型是没有问题的;但是在char和unsigned char之间转换,会出现上面的1,2情况。
void TestFunc( unsigned char v)
{
char c = v; // 记录的是有符号的数,若是负数那么转换正确,十六进制字面表达填充错误
unsigned char uc = v; // 记录的是无符号的数,十六进制字面表达正确
unsigned int a = c, b = uc;
int i = c, j = uc;
printf("----------------\n");
printf("%%c: %c, %c\n", c, uc);// 都是Ascii拓展码表的拉丁字符
printf("%%X: %X, %X\n", c, uc); // FFFFFF80, 80 ;unsigned char正确高位拓展为0
printf("%%u: %u, %u\n", a, b); // 4294967168, 128
char szBuffer1[256];
unsigned char *pJ = (unsigned char*)&b;
sprintf_s(szBuffer1, "%02x%02x%02x%02x",unsigned char(*(pJ + 3)),unsigned char(*(pJ + 2)),
unsigned char(*(pJ + 1)), unsigned char(*(pJ + 0)));
printf("unsigned int的十六进制值:%s\n", szBuffer1);// 输出0x00000080
printf("%%d: %d, %d\n", i, j); // -128, 128
//1.数值转换:因为一般编译器char是signed char表示范围[-128,127]最高位是符号位,
// 高位为1时转换为大数前面都填充1,导致十六进制不准确;但是本身为负数,转换为大数负数是正确的。
// unsigned char表示范围是[0,255],最高位不是符号位,转换为大数时,不管最高位是1还是0都是填充0
// 十六进制是正确的;但是本身为负数转换到的却不是负数。
//
总之数值转换要看原来的值来确定用char还是unsigned char,否则char/unsigned char转换整数值都会有问题;
// 非数值类型例如颜色等却要用unsigned char额外保证转换为大数值类型时正确, 真正的操作用指针解析将会更加准确。
int nTest = -255/*0x80*/;
printf("Origin num: %d\n", nTest);
//
2.指针解析:对数值十六进制(二进制)解析时候(文件和网络内存中),unsigned char*,char*都是OK的,因为指针只是遍历并没有转换截取
/*unsigned*/ char *pNum = (/*unsigned*/ char*)&nTest;
int nDecodeNum = 0;
nDecodeNum |= int(*(pNum+3));// 需要转换为int,所以用int
nDecodeNum<<=8; // 需要<<=
nDecodeNum |= int(*(pNum+2));
nDecodeNum<<=8;
nDecodeNum |= int(*(pNum+1));
nDecodeNum<<=8;
nDecodeNum |= int(*(pNum + 0));
printf("rellay negativeData:%d\n", nDecodeNum);
}
int _tmain(int argc, _TCHAR* argv[])
{
TestFunc(0x80);// 前面是1,转换为char后,char转换为十六进制,转换为int/unsigned int会首位拓展导致数值拓展不正确;转换为unsigned char却不会出现这样的问题
TestFunc(0x7E);// 前面符号位是0,无论是转换为char还是unsigned char都是没有问题的
char nTestNum = 0xFF;
unsigned char nResult = (unsigned char)nTestNum;
printf("测试char和unsignec char之间的转换1:%d\n", (char)nResult);
unsigned char nTestNum2 = 0xFF;
char nResult2 = (char)nTestNum2;
printf("测试char和unsignec char之间的转换2:%u\n", (unsigned char)nResult2);
//输出结果:
/*----------------
----------------
%c: €, €
%X: FFFFFF80, 80
%u: 4294967168, 128
%d: -128, 128
Origin num: -255
rellay negativeData:-255
----------------
%c: ~, ~
%X: 7E, 7E
%u: 126, 126
%d: 126, 126
Origin num: -255
rellay negativeData:-255*/
while(1);
return 0;
}
4.汉字字符编码
汉字是字模库中的点阵信息(图形学上也就是图像贴图,获取后需要转换为纹理渲染到显示器上)。
汉字的显示原理:
1).从键盘输入的汉字经过键盘管理模块,变换成机内码。
机内码
也就是内码。汉字区位码的区码和位码的范围在1~94内,如果直接作为机内码必将与基本的ASCII码冲突。为避免与基本ASCII码中的控制码与字符码的冲突,分别在区码、位码上增加A0H(即10100000)。所以,内码同样占两个字节,分别称为高位内码与低位内码,按如下规则确定:
高位内码=区码+A0H
低位内码=位码+A0H
例如,汉字“啊”的内码是“B0A1H”(即1011000010100001)。
注:高位内码与低位内码的取值范围均是A1H~FEH。
2).然后经字模检索程序,查到机内码对应的点阵信息在字模库的地址。
以16×16的点阵汉字库文件为例。一个汉字用了256个点共32个字节表示。汉字共分94区,每个区有94位汉字。机内码用两个字节表示,第一个字节存储区号(qh),为了和ASCII码相区别,范围从十六进制的A1H开始(小于80H地为ASCII码字符),对应区码的第一区;第二个字节是位号(wh),范围也从A1H开始,对应某区中的第一个位码。这样,将汉字机内码减去A0A0H就得到该汉字的区位码。从而可以得到汉字在字库中的具体位置:
位置=(94*(qh-1) + wh-1) * 一个汉字字模占用的字节数
对于16×16的点阵汉字库,汉字在字库中的具体位置的计算公式就是:(94*(qh-1)+wh-1)*32。例如,“房”的机内码为十六进制的B7BF,则其区位码是B7BFH-A0A0H=171FH,转化为十进制就是2331,在汉字库中的位置就是32*[94*(23-1)+(31-1)]=67136字节以后的32个字节为“房”的显示点阵。
3).从字库中检索出该汉字点阵信息。
4).利用显示驱动程序将这些信息送到显示卡的显示缓冲存储器中。
5).显示器的控制器把点阵信息整屏顺次读出,并使每一个二进制位与屏幕的一个点位相对应,就可以将汉字字形在屏幕上显示出来。
大陆用得最多最广泛的是GB2312-80 汉字编码。
汉字用两个字节(共16位二进制数编码)表示, 两字节首位都是“1” ,这种汉
字编码最多可以表示 27×27=128×128=16384个汉字。
见:http://www.wendangdaquan.com/Wdshow.asp?id=2744a7ee6294dd88d0d26b99
汉字的存储也是小端顺序的,但是这个时候汉字的最小单位不是一个字节而是两个字节。
汉字内存中十六进制表示(二进制)和汉字字符之间的相互转换:
char *pCh = "啊爱";
char szCh[256];
char szCh0[256];
sprintf_s(szCh0, "%d", char(*(pCh)));// szCh0的值为-80,因为汉字是双字节的,汉字的最高位都是1,将单个字节转成整数当然就是负数了
sprintf_s(szCh, "%02x%02x%02x%02x", unsigned char(*(pCh)), unsigned char(*(pCh+1)),unsigned char(*(pCh+2)), unsigned char(*(pCh+3)));
// 汉字的GB2312编码,或者UTF-16,UTF-8编码,是用2字节是作为一个存储顺序单元,所以pCh+0是高位8字节,pCh+1是低位8字节。
int nHighByte;
int nLowByte;
sscanf(&szCh[0], "%02x%02x", &nHighByte, &nLowByte);
char szDecodeBuffer[256];
szDecodeBuffer[0] = nHighByte;
szDecodeBuffer[1] = nLowByte;
sscanf(&szCh[4], "%2x%2x", &nHighByte, &nLowByte);
szDecodeBuffer[2] = nHighByte;
szDecodeBuffer[3] = nLowByte;
szDecodeBuffer[4] = 0;
printf("%s\n", szDecodeBuffer);
////asc转十六进制 ,十进制数值上的转换而不是字符编码的转换,属于加密解密范畴
int ascToHex(const char *Asc, char *Hex, int AscLen)
{
char *HexPtr = Hex;
short i;
for(i = 0; i < AscLen; i++)
{
*HexPtr = Asc[i] << 4;
if (!(Asc[i]>='0' && Asc[i]<='9'))
*HexPtr += 0x90;
i++;
*HexPtr |= (Asc[i] & 0x0F);
if (!(Asc[i]>='0' && Asc[i]<='9'))
*HexPtr += 0x09;
HexPtr++;
}
return 0;
}
5.图形图像颜色的表示
1)颜色数据的表示和操作
颜色一般用unsigned int来表示,ARGB每个通道一个字节的格式,为了避免符号位所以需要unsigned char来存取操作。
显卡的监视器会解析这些unsigned char的颜色值,将其渲染出来。
颜色操作:
uint32_t Color::to_ARGB32() const {
uint32_t c=(uint8_t)(a*255); // 例如0x0000007F
c<<=8; //0x00007F00
c|=(uint8_t)(r*255); //0x00007F10
c<<=8; //0x007F1000
c|=(uint8_t)(g*255); //0x007F1011
c<<=8; //0x7F101100
c|=(uint8_t)(b*255); //0x7F101112
unsigned char red = (unsigned char)(color >> 16);
int nRed = (int)red; // 输出16
return c; //0x7F101112
}
// 总结:
// 1. C/C++语言的位移运算不需要考虑内存存放的大小端,因为这个是对逻辑上十六进制的位移, 直接16进制左右移动即可。
// 2.小转大,大左移<<填充:类型的转换,将小类型转换为大类型,只需要声明一块大数据类型的空间,然后左移填充各个小的数据类型。
// 3.大转小,大右移>>截取:大的转换为小的先右移,然后直接强制类型转换截取,或者格式截取。
左右移动只是数值上的操作,赋值应该在寄存器上不用考虑小端模式(逻辑上左移动即可); 读取在小端存储模式上,读取后也用到了寄存器。
左右移动只是改变大数据的值,地址并不改变。输出是<<,输入是>。
2)数值图形数据的表示和操作
图形数据存放形式:
结论1:unsigned char编码转换安全和范围更大:
unsigned char可以安全的表示十六进制数据,浮点数,结构体和非数值数据;unsigned char可以表示更多的编码范围。
结论2:图形图像数据的存取和内存表示:
1)传入二维指针其实里面是用了一维,传入指针时候用unsigned char就可以了。
2)unsigned char可以安全的表示十六进制,浮点数和结构体,非数值数据,也就是图形图像数据表示。
例子1:
void TestFunc( unsigned char v)
{
char c = v;
unsigned char uc = v;
unsigned int a = c, b = uc;
int i = c, j = uc;
printf("----------------\n");
printf("%%c: %c, %c\n", c, uc);
printf("%%X: %X, %X\n", c, uc);
printf("%%u: %u, %u\n", a, b);
printf("%%d: %d, %d\n", i, j);
}
void TestFunc( /*unsigned*/ char v)
{
char c = v;
unsigned char uc = v;
unsigned int a = c, b = uc;
int i = c, j = uc;
printf("----------------\n");
printf("%%c: %c, %c\n", c, uc);
printf("%%X: %X, %X\n", c, uc);
printf("%%u: %u, %u\n", a, b);
printf("%%d: %d, %d\n", i, j);
}
void main()
{
TestFunc(0x80);//都是这样的测试代码,结果表明unsigned char作为数值转化为高位时候填充是安全的,非数值时候可以表示更多。
}
例子2:
struct MyVertex
{
float x;
float y;
float z;
MyVertex(float a,float b,float c)
{
x = a; y = b; z = c;
}
};
void printVertex(const MyVertex &b)
{
printf("value:(%1.2f,%1.2f,%1.2f)\n",b.x,b.y,b.z);
};
void func(unsigned char **c)
{
*c= (unsigned char *)malloc(sizeof(unsigned char) * 100);
}
MyVertex *vertex = NULL;;
func((unsigned char**)&vertex); // Direct3D很多函数都是,传入二维指针,函数内部对一维进行分配内存(连续的内存空间).
if(vertex == NULL)
return;
vertex[0] = MyVertex( 2.0f,2.0f,3.0f); // 对传出的一维连续内存空间进行赋值
vertex[1] = MyVertex( 2.0f,2.0f,3.0f);
vertex[2] = MyVertex( 2.0f,2.0f,3.0f);
vertex[3] = MyVertex( 2.0f,2.0f,3.0f);
vertex[4] = MyVertex( 4.0f,4.0f,9.0f);
printVertex(vertex[0]);//1.传入二维指针其实里面是用了一维,传入指针时候用unsigned char就可以了。2.unsigned char可以安全的表示十六进制,浮点数和结构体,非数值数据。
printVertex(vertex[3]);
while(1);
}
3)图形图像文件的表示和操作
图形数据格式:
GA文件
TGA(Tagged Graphics)文件 TGA是由美国Truevision公司为其显示卡开发的一种图像文件格式,已被国际上的图形、图像工业所接受。现在已成为数字化图像,以及运用光线跟踪算法所产生的高质量图像的常用格式。TGA文件的扩展名为.tga。 TGA的结构比较简单,属于一种图形、图像数据的通用格式,目前大部分文件为24位或32位真彩色,在多媒体领域有着很大影响。由于Truevision 公司推出TGA的目的是为了采集、输出电视图像,所以TGA文件总是按行存储、按行进行压缩的,这使得它同时也成为计算机生成图像向电视转换的一种首选格 式。使用photoshop软件可以打开此类文件。
结构
TGA的结构比较简单,属于一种图形、图像数据的通用格式,在多媒体领域有很大影响,是计算机生成图像向电视转换的一种首选格式。
特点
TGA图像格式最大的特点是可以做出不规则形状的图形、图像文件,一般图形、图像文件都为四方形,若需要有圆形、菱形甚至是缕空的图像文件时,TGA可就派上用场了!
优点
TGA格式支持压缩,使用不失真的压缩算法。
在工业设计领域,使用三维软件制作出来的图像可以利用TGA格式的优势,在图像内部生成一个Alpha(通道),这个功能方便了在平面软件中的工作。
Tga常见的格式有非压缩RGB和压缩RGB两种格式,文件的第三个Byte位作为标记:2为非压缩RGB格式,10为压缩RGB格式。这里的类只实现读取非压缩格式的tga文件。
先给出tga文件的文件格式:
名称 |
偏移 |
长度 |
说明 |
图像信息字段长度 |
0 |
1 |
本字段是 1 字节无符号整型,指出图像信息字段( 见本子表的后面 )长度,其取值范围是 0 到 255 ,当它为 0 时表示没有图像的信息字段。 |
颜色表类型 |
1 |
1 |
0 表示没有颜色表,1 表示颜色表存在。由于本格式是无颜色表的,因此此项通常被忽略。 |
图像类型码 |
2 |
1 |
该字段总为 2 , 这也是此类型为格式 2 的原因。 |
颜色表规格字段 |
颜色表首址 |
3 |
2 |
颜色表首的入口索引,整型(低位-高位) |
如果颜色表字段为0,则忽略该字段 |
颜色表的长度 |
5 |
2 |
颜色表的表项总数,整型(低位-高位) |
颜色表项位数 |
7 |
1 |
位数(bit),16 代表 16 位 TGA ,24 代表 24 位 TGA ,32 代表 32 位 TGA |
图像规格字段 |
图像 X 坐标起始位置 |
8 |
2 |
图像左下角 X坐标的整型(低位-高位)值 |
图像 Y 坐标起始位置 |
10 |
2 |
图像左下角 Y坐标的整型(低位-高位)值 |
图像宽度 |
12 |
2 |
以像素为单位,图像宽度的整型(低位-高位) |
图像高度 |
14 |
2 |
以像素为单位,图像宽度的整型(低位-高位) |
图像每像素存储占用位数 |
16 |
2 |
它的值为16,24 或 32 等等。决定了该图像是 TGA 16,TGA24,TGA 32 等等。 |
图像描述符字节 |
17 |
1 |
bits 3-0 - 每像素对应的属性位的位数; 对于TGA 16, 该值为 0 或 1,对于 TGA 24,该值为 0,对于 TGA 32,该值为 8。 bit 4 - 保留,必须为 0 bit 5 - 屏幕起始位置标志 0 = 原点在左下角 1 = 原点在左上角 对于 truevision 图像必须为 0 bits 7-6 - 交叉数据存储标志 00 = 无交叉 01 = 两路奇/偶交叉 10 = 四路交叉 11 = 保留 |
图像信息字段 |
18 |
可变 |
包含一个自由格式的,长度是图像由“图像信息字段”指定。它常常被忽略(即偏移 0 处值为 0 ),注意其最大可以含有 255 个字符。如果需要存储更多信息,可以放在图像数据之后。 |
颜色表数据 |
可变 |
可变 |
如果颜色表类型为 0,则该域不存在,否则越过该域直接读取图像颜色表规格中描述了每项的字节数,为 2,3,4 之一。 |
图像数据 |
可变 |
可变 |
RGB颜色数据,存放顺序为:BBB GGG RRR (AAA) |
代码如下:
1. //========================================================
2. /**
3. * @file TGALoader.h
4. *
5. * 项目描述: TGA文件载入类
6. * 文件描述: 纹理映射
7. * 适用平台: Windows98/2000/NT/XP
8. *
9. *
10. */
11. //========================================================
12. #ifndef __TGALOADER_H__
13. #define __TGALOADER_H__
14. #include "stdafx.h"
15. /** TGA文件载入类 */
16. class CTGALoader
17. {
18. public:
19.
20. CTGALoader(); /**< 构造函数 */
21. ~CTGALoader();
22. bool LoadTGA(const char *file); /**< 载入TGA文件 */
23. void FreeImage(); /**< 释放内存 */
24. bool Load(const char* fileName); /**< 载入TGA文件为纹理 */
25. unsigned int ID; /**< 生成纹理的ID号 */
26. int imageWidth; /**< 图像宽度 */
27. int imageHeight; /**< 图像高度 */
28. unsigned char *image; /**< 指向图像数据的指针 */
29. unsigned int type; /**< 图象类型GL_RGB 或GL_RGBA */
30. };
31. #endif
1. //========================================================
2. /**
3. * @file TGALoader.cpp
4. *
5. * 项目描述: 纹理映射
6. * 文件描述: TGA文件载入
7. * 适用平台: Windows98/2000/NT/XP
8. *
9. *
10. */
11. //========================================================
12. #include "TGALoader.h"
13. /** 构造函数 */
14. CTGALoader::CTGALoader()
15. {
16. /** 设置为默认值 */
17. image = 0;
18. type = 0;
19. ID = -1;
20. imageWidth = 0;
21. imageHeight = 0;
22. }
23. /** 析构函数 */
24. CTGALoader::~CTGALoader()
25. {
26. FreeImage(); /**< 释放内存 */
27. }
28. /** 载入TGA文件 */
29. bool CTGALoader::LoadTGA(const char* file)
30. {
31. FILE *pfile;
32. unsigned char tempColor; /**< 用于交换颜色分量 */
33. unsigned char bitCount; /**< 每象素的bit位数 */
34. int colorMode; /**< 颜色模式 */
35. long tgaSize; /**< TGA文件大小 */
36. unsigned char unCompressHeader[12] = {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /**< 未压缩TGA文件头 */
37. unsigned char tgaHeader[12]; /**< 文件头 */
38. unsigned char header[6]; /**< 文件头前6个字节 */
39. /** 检查文件名是否为空 */
40. if(!file)
41. return false;
42.
43. /** 打开文件 */
44. pfile = fopen(file, "rb");
45. if(!pfile)
46. return false;
47. /** 读取文件头前12个字节 */
48. fread(tgaHeader, 1, sizeof(tgaHeader), pfile);
49. /** 比较文件是否为未压缩文件 */
50. if(memcmp(unCompressHeader, tgaHeader, sizeof(unCompressHeader)) != 0)
51. {
52. MessageBox(NULL,"文件类型错误!","错误",MB_OK);
53. fclose(pfile);
54. return false;
55. }
56. /** 读取6个字节(从上面读取的12个字节后再取6个字节) */
57. fread(header, 1, sizeof(header), pfile);
58. /** 计算图像的宽度和高度 */
59. imageWidth = header[1] * 256 + header[0];
60. imageHeight = header[3] * 256 + header[2];
61. /** 获取每象素的bit位数 */
62. bitCount = header[4];
63. /** 计算颜色模式和图像大小 */
64. colorMode = bitCount / 8;
65. tgaSize = imageWidth * imageHeight * colorMode;
66. /** 分配内存 */
67. image = new unsigned char[sizeof(unsigned char) * tgaSize];
68. /** 读取数据 */
69. fread(image, sizeof(unsigned char), tgaSize, pfile);
70. /** 将BGA格式转化为RGA格式 */
71. for(long index = 0; index < tgaSize; index += colorMode)
72. {
73. tempColor = image[index];
74. image[index] = image[index + 2];
75. image[index + 2] = tempColor;
76. }
77. /** 关闭文件 */
78. fclose(pfile);
79. /** 设置图象类型 */
80. if(colorMode == 3)
81. type = GL_RGB;
82. else
83. type = GL_RGBA;
84. return true;
85. }
86. /** 载入TGA文件并创建纹理 */
87. bool CTGALoader::Load(const char* fileName)
88. {
89. if(!LoadTGA(fileName))
90. {
91. MessageBox(NULL,"载入TGA文件失败!","错误",MB_OK);
92. exit(0);
93. }
94. /** 生成纹理对象名称 */
95. glGenTextures(1, &ID);
96.
97. /** 创建纹理对象 */
98. glBindTexture(GL_TEXTURE_2D, ID);
99.
100. /** 控制滤波 */
101. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
102. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
103. glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
104. glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
105.
106. /** 创建纹理 */
107. gluBuild2DMipmaps(GL_TEXTURE_2D, type, imageWidth,
108. imageHeight, type, GL_UNSIGNED_BYTE,
109. image);
110. return true;
111. }
112. void CTGALoader::FreeImage()
113. {
114. /** 释放内存 */
115. if(image)
116. {
117. delete[] image;
118. image = 0;
119. }
120. }