
gluT是学习OpenGL编程时一个很好的助手。但它缺乏相应的图像加载功能。这使得我们在学习与研究诸如纹理贴图等内容时,不免显得有些尴尬 —— 要么坚持使用gluT而不得不忍受在内存中手工生成简单图像的窘境,要么放弃使用gluT而转向使用Cocoa或MFC等庞大类库。如果选择了后者,看似解决了这个问题,但我们学习研究的方向恐怕会不知不觉地从OpenGL转向Cocoa或MFC了。并且,我们的OpenGL应用也将不得不绑定于特定平台,最终丧失了借助gluT跨越平台的优越性。
OpenGL是使用C语言来制定的编程接口。利用C语言简单、灵活、强大的语言特性,我们可以很轻松地编写图像加载程序。
难点在于图像文件的格式可能很复杂。但TGA类型的图像文件则属例外。它的格式比较简单,但足以胜任类似纹理贴图等任务。
下面是一个名为c1.tga的图像文件。
下面是其格式在Xcode中的十六进制显示。 当然,对于人类来讲,直接看懂这些内容是勉为其难的。所以上图只作感性认识。遇到不懂的地方就去查字典,而网络时代最好的字典就是维基百科了(抱歉,这里仅指英文版的,中文的维基自愿者太少了)。http://en.wikipedia.org/wiki/Truevision_TGA较为详细地介绍了TGA的格式。根据该内容,我们很容易就能识破TGA的天机。TGA的结构分为必备部分与可选部分。本文只讨论必备部分。必备部分又包括头部与实际内容部分。先看头部部分。
| 字段编号 | 长度 | 字段名 | 简介 |
|---|---|---|---|
| 1 | 1字节 | ID长度 | ID字段的长度 |
| 2 | 1字节 | 颜色表类型 | 是否包含颜色表 |
| 3 | 1字节 | 图像类型 | 压缩及颜色类型 |
| 4 | 5字节 | 颜色表规范 | 颜色表的细节 |
| 5 | 10字节 | 图像规范 | 图像大小及格式 |
其中,颜色表规范的5字节又分为下表中的3个子字段。
| 长度 | 字段名 | 简介 |
|---|---|---|
| 2字节 | 第一个条目的索引值 | 在颜色表中的偏移位置 |
| 2字节 | 颜色表长度 | 条目数量 |
| 1字节 | 颜色表条目的大小 | 每像素多少位 |
图像规范的10字节又分为下表中的6个子字段。
| 长度 | 字段名 | 简介 |
|---|---|---|
| 2字节 | X-origin | 左下角(原点)的绝对坐标值 |
| 2字节 | Y-origin | 左下角(原点)的绝对坐标值 |
| 2字节 | 图像宽度 | 以像素为单位 |
| 2字节 | 图像高度 | 以像素为单位 |
| 1字节 | 像素深度 | 每像素多少位 |
| 1字节 | 图像描述符 | 3-0位代表Alpha通道深度,5-4位代表方向 |
结合上述3表,比照TGA内容的十六进制的表示,您能找出规律吗?
十六进制表中,每两个数字为1字节。字段编号1的长度为1字节,因此,十六进制表中的第一个“00”就是ID长度。第二个“00”就是字段编号2的颜色表类型,接下来的“02”即是图像类型,表示它是无压缩的真彩图像。后面的5个“00”,对应于表格2的内容,其值全为0,说明这个TGA图像没有颜色表。接着的4个“00”,表示图像的原点坐标。而“46”“00”,合并后成为“0046”,根据表3,是图像宽度,十进制为70。后面的“002E”,是图像高度,十进制为46。这两组数字表示TGA图像的大小是70 X 46。“18”是像素深度,十进制为24,表示图像是24位真彩。接下来的“00”表示该图像没有Alpha通道。
表格1、2、3解释了TGA图像的头部部分。接下来又该是什么了?我们来看第4个表格。
| 字段编号 | 长度 | 字段名 | 简介 |
|---|---|---|---|
| 6 | 来自于ID长度字段 | 图像的ID | 可选字段,包括一些标识信息 |
| 7 | 来自于颜色表规范字段 | 颜色表数据 | 包含颜色表数据的查询表 |
| 8 | 来自于图像规范字段 | 图像数据 | 根据图像描述符所存储的图像内容 |
字段编号为6、7的字段都是可选的。上面的c1.tga文件就没有这方面的信息。因此,没有这两个字段的存储空间。字段编号8的内容,就是我们最关心的,即图像本身的内容。因此,在十六进制表中,从第2行的“44 58 4C”开始,一直到结束,全部都是图像的内容。当然,我们的肉眼看不出这些数字到底画了什么,但计算机自有识别它们的解码方法。
以上4个表格是实现本文功能的基础。或许,作为程序员,看懂这些表格实属不易。那么,我们来看看用代码如何重现这些表格吧。
//// tga.h//// Created by Sarkuya on 12-4-27.// copyright (c) 2012年 Sarkuya. All rights reserved.//#ifndef _tga_h#define _tga_h#ifndef __glut_h__#if defined(__APPLE__) || defined(MACOSX)# include <gluT/gluT.h>#else# if defined(_WIN32)# include <GL/freeglut.h># endif#endif#endif /* __glut_h__ */typedef struct { glushort firstEntryIndex; glushort length; glubyte entrySize;} TGA_color_MAP_SPEC;typedef struct { glushort xOrigin; glushort yOrigin; glushort imageWIDth; glushort imageHeight; glubyte pixelDepth; glubyte imageDescriptor;} TGA_IMAGE_SPEC;typedef struct { glubyte IDLength; glubyte colorMapType; glubyte imageType; TGA_color_MAP_SPEC colorMapSpec; TGA_IMAGE_SPEC imageSpec;} TGA_header;typedef struct { TGA_header header; glubyte* imageData;} TGA_IMAGE;voID loadTGA(const char* filename,TGA_IMAGE* tgaimage);voID releaseTGA(TGA_IMAGE* tgaimage);#endif /* _tga_h */ TGA_header对应于表格1。IDLength,colorMapType,imageType的长度均为1字节,故用glubyte的数据类型。colorMapSpec及imageSpec均有子字段,故根据这些子字段的长度及名称,另外定义了TGA_color_MAP_SPEC及TGA_IMAGE_SPEC的结构,分别对应于表格2和表格3。4个表格加起来,代表了整个TGA图像的全部内容,所以用TGA_IMAGE来表示。
在这个tga.h的头文件中,仅声明了两个方法。loadTGA用以加载图像,releaseTGA用以释放图像所占空间的资源。
关于tga.h的头文件,还有两点需要说明。首先,由于gluT在其头文件中自动包括了OpenGL应用所需的主要文件,因此,只要将gluT.h包含进来,就可以减轻我们的编程负荷。freeglut由于与目前的Mac OS X存在一些不兼容的地方,因此,在Mac OS X中则使用系统自带的gluT,而windows则可以较方便地安装使用freeglut。
其次,我们在tga.h中使用了OpenGL特有的glubyte,glushort等数据类型,是经过慎重选择的。从上面各个表格可以看出,图像文件的格式信息,都是通过字节,甚至是位的方式来存储的。因此,我们需要以字节作为基本单位与之打交道。但由于C语言的int,short,long等数据类型在各平台的实现中字长可以不一致,因此我们不能断定一个int到底包括了多少个字节。而OpenGL的glubyte,glushort等数据类型则完全以字长为标准来定义,因此非常符合本文中的场合。在计算机中,由于char是最小的存储单位,我们可以通过“多少个char”这种方式来逐步检索图像内容。下面您将看到如何做到这一点。
给定了数据结构,我们可以编写算法了。
#include "tga.h"#include <stdio.h>#include <stdlib.h>#include <limits.h>voID loadTGA(const char* filename,TGA_IMAGE* tgaimage){ file* fp = NulL; fp = fopen(filename,"r"); if (fp == NulL) { fprintf(stderr,"Error opening file!\n"); exit(EXIT_FAILURE); } fscanf(fp,"%c%c%c%2c%2c%c%2c%2c%2c%2c%c%c",&(tgaimage->header.IDLength),&(tgaimage->header.colorMapType),&(tgaimage->header.imageType),&(tgaimage->header.colorMapSpec.firstEntryIndex),&(tgaimage->header.colorMapSpec.length),&(tgaimage->header.colorMapSpec.entrySize),&(tgaimage->header.imageSpec.xOrigin),&(tgaimage->header.imageSpec.yOrigin),&(tgaimage->header.imageSpec.imageWIDth),&(tgaimage->header.imageSpec.imageHeight),&(tgaimage->header.imageSpec.pixelDepth),&(tgaimage->header.imageSpec.imageDescriptor) ); //assume we get the color map of 0,image type of 2 if (tgaimage->header.colorMapType != 0 && tgaimage->header.imageType != 2) { fprintf(stderr,"Unimplemented TGA type Found!"); exit(EXIT_FAILURE); } int imageWIDth = tgaimage->header.imageSpec.imageWIDth; int imageHeight = tgaimage->header.imageSpec.imageHeight; int totalPixels = imageWIDth * imageHeight; int bytesPerPixels = tgaimage->header.imageSpec.pixelDepth / CHAR_BIT; int totalBytes = totalPixels * bytesPerPixels; int imageSize = totalPixels * tgaimage->header.imageSpec.pixelDepth; tgaimage->imageData = (glubyte*) malloc(imageSize); char formatStr[20]; sprintf(formatStr,"%%%dc",totalBytes); fscanf(fp,formatStr,tgaimage->imageData); fclose(fp);}voID releaseTGA(TGA_IMAGE* tgaimage){ printf("releasing tga...\n"); if (tgaimage->imageData != NulL) { free(tgaimage->imageData); tgaimage->imageData = NulL; }} 在loadTGA函数中,我们使用fscanf函数将TGA图像的头部部分的信息一次性地扫进tgaimage的header结构。此功能还可以通过getc(),fgetc(),fgets()等函数来实现,但显而易见,桂冠非fscanf()莫属。在fscanf()函数的格式字符串中,通过“%c”,“%2c”这种方式,严格依照4表的字节数依序进行扫描并赋与相应的变量。程序是以字节(即char,或unsigned char)为基本单位而扫描的,那么在程序中是否需要将这些字节转换为其他数据类型?不需要。计算机内存中实际上只是连续或间断地存储“0”、“1”两种数字,只要它知道从哪个地址开始、偏移多少位的特定空间存储了何种数据类型的数据就足够了。它并不关心这个空间段到底是由多少个char,或是多少个int组成。连取2个char(共16位)的数据赋值于一个glushort的变量,我们回头既可以取出char的数据,又可取出glushort的数据。这是C语言中数据转换的自由。当然,我们甚至可以取出long的数据,但long的数据可能将随后的不明内存空间的内容也捎带出来,因此这种作法非常危险。
由于未有更多时间加以完善,loadTGA函数目前只能处理没有颜色表、且图像类型是非压缩的真彩TGA图像。如果遇到例外,则打印未实现的消息后强行退出程序。有兴趣的读者可以自己试着完善。
在存储图像时,我们先算出图像共有多少个像素,求得其占用内存的总空间并分配内存空间,再求出这些像素共多少个字节,然后以字节为单位,依次存入tgaimage的imageData中。
releaseTGA()负责将imageData所占用的空间释放。
现在,我们可以在实战中检测其功效了。下面的代码是一段功能完整的代码,使用上面的代码加载一个TGA图像(图像来源于OpenGL Super Bible的一个随盘文件)来作为纹理贴图。
//// TGADemo.c// Tgaloader//// Created by Sarkuya on 12-4-27.// copyright (c) 2012年 Sarkuya. All rights reserved.//#include "tga.h"#include <stdio.h>#include <stdlib.h>int winWIDth = 1024;int winHeight = 1024;TGA_IMAGE tgaimage;struct { GLenum code; char* msg;} g_gl_error_table[] = { GL_NO_ERROR,"GL_NO_ERROR",GL_INVALID_ENUM,"GL_INVALID_ENUM",GL_INVALID_VALUE,"GL_INVALID_VALUE",GL_INVALID_OPERATION,"GL_INVALID_OPERATION",GL_STACK_OVERFLOW,"GL_STACK_OVERFLOW",GL_STACK_UNDERFLOW,"GL_STACK_UNDERFLOW",GL_OUT_OF_MEMORY,"GL_OUT_OF_MEMORY"};voID checkGLErrors(voID){ GLenum result = glGetError(); if (result != GL_NO_ERROR) { fprintf(stderr,"Error occurs! The error code is: %d\n",result); int errorcodecounts = sizeof(g_gl_error_table) / sizeof(g_gl_error_table[0]); for (int i = 0; i < errorcodecounts; i++) { if (result == g_gl_error_table[i].code) { fprintf(stderr,"And the error enum is: %s\n",g_gl_error_table[i].msg); } } }}voID init(voID){ glShadeModel(GL_SMOOTH); glClearcolor (0.0,0.0,0.0); glEnable(GL_DEPTH_TEST); char* pathname = ""; char* filename = "brick.tga"; #if defined(__APPLE__) || defined(MACOSX) pathname = "/Volumes/MAC Data/Programming TestFIEld/XcodeProjects/C/TGADemo/TGADemo/";#else# if defined(_WIN32) pathname = "";# endif#endif char pathfilename[255]; sprintf(pathfilename,"%s%s",pathname,filename); loadTGA(pathfilename,&tgaimage); GLsizei imgWIDth,imgHeight; imgWIDth = tgaimage.header.imageSpec.imageWIDth; imgHeight = tgaimage.header.imageSpec.imageHeight; glPixelStorei(GL_UNPACK_AlignmENT,1); glTexImage2D(GL_TEXTURE_2D,GL_RGB,imgWIDth,imgHeight,GL_UNSIGNED_BYTE,tgaimage.imageData); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_TEXTURE_MAG_FILTER,GL_liNEAR); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_liNEAR); glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL); glEnable(GL_TEXTURE_2D);}voID display(voID){ glClear(GL_color_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); gluLookAt( 2.0,4.0,-0.5,1.0,0.0); glBegin(GL_QUADS); glTexCoord2f(0.0f,0.0f); glVertex3f(-2.0f,-1.0f,0.0f); glTexCoord2f(0.0f,1.0f); glVertex3f(-2.0f,1.0f,0.0f); glTexCoord2f(1.0f,1.0f); glVertex3f( 0.0f,0.0f); glVertex3f( 0.0f,0.0f); glTexCoord2f(0.0f,0.0f); glVertex3f(0.0f,1.0f); glVertex3f(0.0f,-2.0f); glTexCoord2f(1.0f,-2.0f); glEnd(); glutSwapBuffers(); checkGLErrors();}voID reshape(int wIDth,int height){ glVIEwport(0,(GLsizei) wIDth,(GLsizei) height); glMatrixMode(GL_PROJECTION); glLoadIDentity(); gluPerspective(35.0,(GLdouble) wIDth / (GLdouble) height,30.0); glMatrixMode(GL_MODELVIEW); glLoadIDentity();}voID keyboard(unsigned char key,int x,int y){ switch (key) { case 27: exit(EXIT_SUCCESS); break; default: break; }}int main(int argc,char** argv){ glutinit(&argc,argv); glutinitdisplayMode(gluT_DOUBLE | gluT_RGB | gluT_DEPTH); glutinitwindowposition((glutGet(gluT_SCREEN_WIDTH) - winWIDth) / 2,(glutGet(gluT_SCREEN_HEIGHT) - winHeight) / 2); glutinitwindowsize(winWIDth,winHeight); glutCreateWindow("TGA DEMO"); glutReshapeFunc (reshape); glutdisplayFunc(display); glutKeyboardFunc(keyboard); init(); glutMainLoop(); releaseTGA(&tgaimage); return EXIT_SUCCESS;} 两点需要注意。一是Xcode项目要读取特定文件,需要对此文件使用绝对路径引用,而Visual Studio则可以直接放在与源文件同一路径下。代码反映了这种情况。二是由于gluT的lgutMainLoop()函数一旦进入就不能再返回,因此程序中的releaseTGA()函数实际上是无法被调用的。freeglut可以解决这个问题。
程序运行后渲染的效果如下图。
您看,即便是gluT,也可以拥有绚丽多彩的真实世界了。[本文调试环境:Xcode V4.3,Mac OS X lion.]
总结以上是内存溢出为你收集整理的为GLUT应用编写TGA图像加载程序全部内容,希望文章能够帮你解决为GLUT应用编写TGA图像加载程序所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)