在进行游戏编程的过程中经常会遇到图像文件的读取需求,但是如果直接使用其它的
图像库,要么没有源代码,要么非常难以使用,要么就会出现版权纠纷。实际上经常的情
况就是仅仅只是需要一种纹理数据,至于具体的文件格式根本就没有需要,完全可以自定
义一种文件格式,使得读取尽可能的简洁明了。听起来似乎很难,其实不然,本人在多年
的游戏开发过程中总结出来了一种简洁明了的方法,因为主要是用于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而不是游戏设计者自己来实现繁琐的图像文件管理,这样就可以尽可能的利用
已有的工具来实现自己的目标。