为GLUT应用编写TGA图像加载程序

为GLUT应用编写TGA图像加载程序,第1张

概述GLUT是学习OpenGL编程时一个很好的助手。但它缺乏相应的图像加载功能。这使得我们在学习与研究诸如纹理贴图等内容时,不免显得有些尴尬 —— 要么坚持使用GLUT而不得不忍受在内存中手工生成简单图像的窘境,要么放弃使用GLUT而转向使用Cocoa或MFC等庞大类库。如果选择了后者,看似解决了这个问题,但我们学习研究的方向恐怕会不知不觉地从OpenGL转向Cocoa或MFC了。并且,我们的Open

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 1字节 ID长度 ID字段的长度
2 1字节 颜色表类型 是否包含颜色表
3 1字节 图像类型 压缩及颜色类型
4 5字节 颜色表规范 颜色表的细节
5 10字节 图像规范 图像大小及格式

其中,颜色表规范的5字节又分为下表中的3个子字段。

表格2
长度 字段名 简介
2字节 第一个条目的索引值 在颜色表中的偏移位置
2字节 颜色表长度 条目数量
1字节 颜色表条目的大小 每像素多少位

图像规范的10字节又分为下表中的6个子字段。

表格3
长度 字段名 简介
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个表格。

表格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图像加载程序所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1059920.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-25
下一篇2022-05-25

发表评论

登录后才能评论

评论列表(0条)

    保存