在进行游戏编程的过程中经常会遇到图像文件的读取需求,但是如果直接使用其它的
图像库,要么没有源代码,要么非常难以使用,要么就会出现版权纠纷。实际上经常的情
况就是仅仅只是需要一种纹理数据,至于具体的文件格式根本就没有需要,完全可以自定
义一种文件格式,使得读取尽可能的简洁明了。听起来似乎很难,其实不然,本人在多年
的游戏开发过程中总结出来了一种简洁明了的方法,因为主要是用于OpenGL程序的,所以
我将其命名为glt格式,也就是OpenGL的Texture格式。本文给出了XPM文件转GLT文件的转
换程序。
这个转换程序采用了Lex来书写扫描器,最主要的原因就是GNU的lex和bison产生的代
码可以直接进行商用而不必付出任何的费用。另外一个重要原因就是用lex书写的代码,本
身就是一种文档。因此本文也直接采用GNU的flex了。
实际上我写这个文档的目的如下:
(1)推荐lex的使用
(2)解释自定义的纹理文件格式和相关读取和写入程序
(3)用代码来直接表达设计信息,实际上就是用尽量少的文字来表达尽可能多的信息
可以从网络上直接下载UnixCmd包在Windows下面安装,实际上安装过程就是简单的解
压缩过程,只不过需要设置路径指向该解压缩目录,如果要使用bison那么还需要设置两个
环境变量BISON_HAIRY和BISON_SIMPLE分别指向bison.hairy和bison.simple文件。
xpm2glt.l
%{ // 这个程序主要实现了XPM格式的图像文件转换成OpenGL可以直接使用的数组格式的功能 #include <iostream> #include <fstream> #include <string> #include <vector> #include <iterator> #include <map> #include "glt.hpp" #include "glt.cpp" std::string g_InputFileName;// 输入的文件名 std::string g_InputFileNameHeader;// 输入的文件名(不包括扩展名) std::string g_Buffer;// 用来缓存读取的字符串信息 std::string g_ArrayName;//xpm文件采用的数组名称 unsigned int g_Info[4];//xpm文件的信息:图像宽、高、索引宽度以及索引表长度 typedef std::map<std::string,std::string> COLORDICT; typedef std::vector<std::string> IMAGETABLE; COLORDICT g_ColorDict;// 颜色词典 IMAGETABLE g_ImageTable;// 图像表 texture_t g_texture;// OpenGL使用的纹理文件类,用来读取和写入图像数据 %} %x STRING CNOTE D [0-9] L [A-Za-z] X {D}|[A-Fa-f] S " " WS [ /t/n] array {L}({L}|{D})+"_xpm" info {D}+{S}{D}+{S}{D}+{S}{D}+ palettecolor .+/tc{S}({L}|{D})+ rgbcolor .+/tc{S}#{X}{X}{X}{X}{X}{X} %%
/" {BEGIN STRING;g_Buffer.clear();} <STRING>{S} {g_Buffer.append(" ");} <STRING>//n {g_Buffer.append("/n");} <STRING>//t {g_Buffer.append("/t");} <STRING>///" {g_Buffer.append("/"");} <STRING>/" { BEGIN INITIAL; if(!g_Buffer.empty())//必须是图像索引表才进行处理 { for(size_t i=0;i<g_Info[0];++i) { std::string idx = g_Buffer.substr(i*g_Info[3],g_Info[3]); g_ImageTable.push_back(idx); } } } <STRING>/n std::cerr<<"字符串错误"<<std::endl; <STRING>{info}/" { unput('/"'); sscanf(yytext,"%d %d %d %d",&g_Info[0],&g_Info[1],&g_Info[2],&g_Info[3]); } <STRING>{palettecolor} { //std::string idx(&yytext[0],g_Info[3]); std::string clr(&yytext[g_Info[3]+3],yyleng-g_Info[3]-3); //g_ColorDict.insert(std::make_pair(idx,clr)); std::cerr<< "不允许使用调色版模式的颜色:["+clr+"]" << std::endl; exit(1); } <STRING>{rgbcolor} { std::string idx(&yytext[0],g_Info[3]); std::string clr(&yytext[g_Info[3]+3],yyleng-g_Info[3]-3); // 需要将#FFFFFF颜色换成三个字节表示的16进制格式0xFF,0xFF,0xFF std::string R("0x"+clr.substr(1,2)); std::string G("0x"+clr.substr(3,2)); std::string B("0x"+clr.substr(5,2)); g_ColorDict.insert(std::make_pair(idx,R+","+G+","+B)); } <STRING>. {g_Buffer.append(yytext);}
///* {BEGIN CNOTE;} <CNOTE>.|/n ; <CNOTE>/*// {BEGIN INITIAL;}
{array} {g_ArrayName.append(yytext,yyleng);} .|/n ;
%% int yywrap() { return 1; }
int main(int argc,const char*argv[]) { if(argc==1){//从管道获取数据 g_InputFileName = "Texture"; }else if(argc==2){//从输入文件获取数据 g_InputFileNameHeader = g_InputFileName = argv[1]; g_InputFileNameHeader.erase(g_InputFileNameHeader.rfind('.')); extern FILE*yyin; yyin = fopen(argv[1],"r"); }else{ std::cerr<<"该程序必须从XPM文件或者管道输入数据!"<<std::endl; std::cerr<<"eg:"<<std::endl; std::cerr<<"/txpm2glt < File.xpm"<<std::endl; std::cerr<<"/txpm2glt File.xpm"<<std::endl; exit(0); } yylex();// lex产生的词法扫描程序 std::clog <<"xpm文件的宽:["<<g_Info[0]<<"]"<<std::endl; std::clog <<"xpm文件的高:["<<g_Info[1]<<"]"<<std::endl; std::clog <<"xpm颜色数量:["<<g_Info[2]<<"]"<<std::endl; std::clog <<"xpm索引宽度:["<<g_Info[3]<<"]"<<std::endl; std::clog<<g_ColorDict.size()<<","<<g_ImageTable.size()<<std::endl;
g_texture.width = g_Info[0]; g_texture.height = g_Info[1]; g_texture.depth = 3 ;// 目前只处理RGB格式的图像数据 for(size_t i=0;i<g_Info[1];++i) { for(size_t j=0;j<g_Info[0];++j) { std::string tmp = g_ColorDict[g_ImageTable[i*g_Info[0]+j]]; unsigned int R,G,B; sscanf(tmp.c_str(),"%x,%x,%x",&R,&G,&B); g_texture.data.push_back(R); g_texture.data.push_back(G); g_texture.data.push_back(B); } } std::ofstream binary((g_InputFileNameHeader+".glt.bin").c_str(),std::ios_base::binary); g_texture.write(binary);// 写成二进制格式的纹理文件 std::ofstream text((g_InputFileNameHeader+".glt").c_str()); text << g_texture ;// 写成文本格式的纹理文件 std::clog<<"文件["<<argv[1]<<"]转换成功!"<<std::endl; return 0; }
|
glt.hpp
#ifndef GLT_HPP #define GLT_HPP #include <vector> #include <iostream> struct texture_t { unsigned int width;// 图像宽度 unsigned int height;// 图像高度 unsigned int depth;// 图像深度 std::vector<unsigned char> data;// 图像数据 void read (std::istream&s);// 读取二进制格式的纹理文件 void write(std::ostream&s);// 写出二进制格式的纹理文件 void clear();// 清理内存中所有的图象数据 void horizontal_flip(); // 垂直翻转 // 读取文本格式的纹理文件 friend std::istream&operator>>(std::istream&s,texture_t&o); // 写出文本格式的纹理文件 friend std::ostream&operator<<(std::ostream&s,const texture_t&o); }; std::istream&operator>>(std::istream&s,texture_t&o); std::ostream&operator<<(std::ostream&s,const texture_t&o); #endif//GLT_HPP
|
glt.cpp
#include "glt.hpp" #include <algorithm> void texture_t::read (std::istream&s) { s.read(reinterpret_cast<char*>(&width),sizeof(unsigned int)); s.read(reinterpret_cast<char*>(&height),sizeof(unsigned int)); s.read(reinterpret_cast<char*>(&depth),sizeof(unsigned int)); unsigned int size = 0; s.read(reinterpret_cast<char*>(&size),sizeof(unsigned int)); data.resize(size); s.read(reinterpret_cast<char*>(&data[0]),size*sizeof(unsigned char)); }
void texture_t::write(std::ostream&s) { s.write(reinterpret_cast<const char*>(&width),sizeof(unsigned int)); s.write(reinterpret_cast<const char*>(&height),sizeof(unsigned int)); s.write(reinterpret_cast<const char*>(&depth),sizeof(unsigned int)); unsigned int size = data.size(); s.write(reinterpret_cast<const char*>(&size),sizeof(unsigned int)); s.write(reinterpret_cast<const char*>(&data[0]),size*sizeof(unsigned char)); }
void texture_t::clear() { data.clear(); }
void texture_t::horizontal_flip() { if(width*height == 0) return; for(unsigned int i=0;i<height/2;++i) { std::swap_ranges(&data[(i*width)*3+0],&data[(i*width+width)*3-1],&data[(height-i-1)*width*3+0]); } }
std::istream&operator>>(std::istream&s,texture_t&o) { s >> o.width >> o.height >> o.depth ; std::copy(std::istream_iterator<unsigned char>(s),std::istream_iterator<unsigned char>(),std::back_inserter(o.data)); return s; }
std::ostream&operator<<(std::ostream&s,const texture_t&o) { s << o.width << "/t" << o.height << "/t" << o.depth << std::endl; std::copy(o.data.begin(),o.data.end(),std::ostream_iterator<unsigned int>(s,"/t")); return s; }
|
Makefile
LEX=flex YACC=bison CXX=g++ CXXFLAGS=
xpm2glt.exe:xpm2glt.l $(LEX) xpm2glt.l $(CXX) $(CXXFLAGS) lex.yy.c -o $@
clean: rm *.c *.h xpm2glt.exe
|
sample.xpm
/* XPM */ static char * sample1_xpm[] = { "32 32 6 1", " c #000000", ". c #000000", "+ c #000080", "@ c #FFFFFF", "# c #FFFF00", "$ c #FF0000", " ", " ", " ", " ", " ", " .............. ", " .++++++++++++. ", " .++++++++++++. ", " .+@@+++++++++. ", " .+@@+++++++++. ", " .+@@+++++++++. ", " .+@@++++++..............", " .+@@++++++.############.", ".........+@@++++++.############.", ".$.+@@++++++.#@@#########.", ".$.+@@++++++.#@@#########.", ".$@@$.+++++++++.#@@#########.", ".$@@$.+++++++++.#@@#########.", ".$@@$...........#@@#########.", ".$@@$. .#@@#########.", ".$@@$. .#@@#########.", ".$@@$. .#@@#########.", ".$@@$. .############.", ".$@@$. .############.", ".$. ..............", ".$. ", ".............. ", " ", " ", " ", " ", " "};
|
将上面的转换程序xpm2glt.exe编译成功之后,就可以直接使用了,下面给出使用方法
xpm2glt.exe sample.xpm
在OpenGL程序中使用glt的texture_t类的方法如下:
std::ifstream in("sample.glt.bin",std::ios_base::binary);
texture_t t; t.read(in); t.horizontal_flip();
glBindTexture(GL_TEXTURE_2D,TEXTURE1);
gluBuild2DMipmaps(GL_TEXTURE_2D,t.depth,t.width,t.height,GL_RGB,GL_UNSIGNED_BYTE,&t.data[0]);
从上面的xpm2glt.l代码中可以看见用lex来书写词法扫描程序确实非常容易,在代码
中就可以记录设计信息,因此代码就是文档,现在还不是很成熟的方案,但是已经向这个
方面迈出了一大步了:)
关于xpm文件的来源,可以使用GIMP图像处理软件直接生成,这样就将格式转换的过程
交给了GIMP而不是游戏设计者自己来实现繁琐的图像文件管理,这样就可以尽可能的利用
已有的工具来实现自己的目标。