本节将介绍如何加载其他格式的图片,同时,介绍如何显示文字。效果图如下
其中背景图片是我从网上下载的一张JPG图片,已经将它上传在了附件中。
显示其它格式图片:
要显示png、jpg、gif等格式的图片,我们需要下载SDL_image库
http://www.libsdl.org/projects/SDL_image/
下载上图中标记出来的文件,然后解压。
1、将解压后include目录下的SDL_image.h文件拷贝到C:\MinGW\include\SDL目录下
2、将解压后include目录下的SDL_image.lib文件拷贝到C:\MinGW\lib目录下
3、将解压后include目录下的所有dll文件拷贝到C:\MinGW\bin目录下。
注意:我在这样做的时候出现了一个问题,当程序写好之后,在Eclipse下面运行报找不到驱动程序的错误,但自己手动进入到生成的EXE目录中双击运行程序没有问题。后来我将include目录下的所有dll文件拷贝到C:\WINDOWS\system32就好了,如果你也遇到和我一样的问题,可以参考这种解决方法。至于造成这个问题的原因,暂时还不清楚。
经过上面的准备,我们可以开始写代码了,其实加载图片的代码很简单。
SDLSurfacePtr SDLVideo::LoadImage(std::string fileName) { SDL_Surface *surface = IMG_Load(fileName.c_str()); if(NULL == surface) { throw SDLException(std::string("IMG_Load加载图片时发生错误:") + SDL_GetError()); } return SDLSurfacePtr(new SDLSurface(surface)); }
通过比较原来加载BMP格式图片的代码
SDLSurfacePtr SDLVideo::LoadBMP(std::string file) { SDL_Surface *surface = SDL_LoadBMP(file.c_str()); if(NULL == surface) { throw SDLException(std::string("SDL_LoadBMP加载BMP图片时发生错误:") + SDL_GetError()); } return SDLSurfacePtr(new SDLSurface(surface)); }
其实就一个地方不同,一个用的是IMG_load函数,一个是SDL_LoadBMP函数。这里将加载图片的函数放到了SDLVideo类中。如果需要加载其他格式的图片调用LoadImage函数就可以了,同时LoadImage也可以加载BMP格式的图片,所以在这里LoadBMP函数基本上没什么用处了。
我们下一步要做的就是封装一个专门加载图片的类SDLImageManager,用来对加载的图片进行管理,这里主要是对图片进行缓冲,避免频繁的访问硬盘,提高程序效率。头文件:
#ifndef RESOURCEMANAGER_H_ #define RESOURCEMANAGER_H_ #include "SDLSurface.h" #include <map> class SDLImageManager { friend class SDL; private: SDLImageManager(); public: virtual ~SDLImageManager(); public: //获取图片 SDLSurfacePtr loadImage(std::string fileName); //获取图片,并将图片中的某种颜色去掉,使去掉的那部分颜色为透明,这样就不会覆盖掉底层图片的颜色 //详细介绍见http://lazyfoo.net/SDL_tutorials/lesson05/index.php SDLSurfacePtr loadImageWithoutColor(std::string fileName, Uint8 r, Uint8 g, Uint8 b ); private: std::map<std::string, SDLSurfacePtr> buffer; }; #endif /* RESOURCEMANAGER_H_ */
其中loadImage用来获取一张图片,成员变量buffer用来缓冲已经加载的图片。loadImageWithoutColor的功能主要是去除图片的底色,避免底色覆盖掉底层图片的颜色,这样解释可能不好懂,我也没有什么更好的办法,大家可以去http://lazyfoo.net/SDL_tutorials/lesson05/index.php,上面有详细的介绍。
源文件:
#include "SDLImageManager.h" #include "SDLCore.h" #include <boost/lexical_cast.hpp> SDLImageManager::SDLImageManager() { } SDLImageManager::~SDLImageManager() { } SDLSurfacePtr SDLImageManager::loadImage(std::string fileName) { //先到缓冲区去找 std::map<std::string, SDLSurfacePtr>::iterator it = buffer.find(fileName); //如果找到就返回 if(it != buffer.end()) { return it->second; } //否则从硬盘加载图片 SDLSurfacePtr loadedImage = SDL::video()->LoadImage( fileName.c_str() ); //将文件格式调整成程序需要的格式 SDLSurfacePtr image = SDL::video()->DisplayFormat( loadedImage ); //放入缓冲区 buffer.insert(std::make_pair(fileName, image)); return image; } SDLSurfacePtr SDLImageManager::loadImageWithoutColor(std::string fileName, Uint8 r, Uint8 g, Uint8 b ) { /** * 根据文件名和要去除的颜色生成一个唯一的键, * 在缓冲区类同文件名,但一个是原图,一个是去除某种颜色的图可能共存, * 所以这里用fileName作为键值有可能覆盖掉缓冲区中的原图。 * 存储每种颜色分量的空间是一个字节,所以最大数据不会超过255,这里简单的乘上1000就可以满足要求了。 * 比如这里传入的参数是aaa.jpg, 255, 255, 255;那么得到的结果就是aaa.jpg255255255。 * 这里boost::lexical_cast是boost里面一个常用的函数,用来做数据之间的转换。 */ std::string key = fileName + boost::lexical_cast<std::string>(r*1000*1000) + boost::lexical_cast<std::string>(g*1000) + boost::lexical_cast<std::string>(b); std::map<std::string, SDLSurfacePtr>::iterator it = buffer.find(key); if(it != buffer.end()) { return it->second; } SDLSurfacePtr loadedImage = SDL::video()->LoadImage( fileName.c_str() ); SDLSurfacePtr image = SDL::video()->DisplayFormat( loadedImage ); Uint32 colorKey = SDL::video()->MapRGB(image->value()->format, r, g, b); SDL::video()->SetColorKey(image, SDL_SRCCOLORKEY, colorKey); buffer.insert(std::make_pair(key, image)); return image; }
加载图片的相关准备工作已经完成。
显示文字:
要显示true type字体,我们需要下载SDL_ttf库。http://www.libsdl.org/projects/SDL_ttf/
然后按照上面介绍SDL_image的方法,将下载下来的文件放到相应的目录下。这边不再重述。
为了管理字体,我们同上面一样也新建了一个SDLFontManager类
#ifndef SDLFONTMANAGER_H_ #define SDLFONTMANAGER_H_ #include "SDLFont.h" class SDLFontManager { friend class SDL; private: SDLFontManager(); private: /** * 初始化TTF环境 */ void Init(); public: virtual ~SDLFontManager(); public: /** * 打开字体 * name 字体文件路径 * size 待打开字体的大小 */ SDLFontPtr OpenFont(std::string name, int size); }; #endif /* SDLFONTMANAGER_H_ */
#include "SDLFontManager.h" SDLFontManager::SDLFontManager() { // TODO Auto-generated constructor stub } SDLFontManager::~SDLFontManager() { TTF_Quit();//退出TTF环境 } void SDLFontManager::Init() { if(TTF_WasInit()) { return; } if(-1 == TTF_Init()) { throw SDLException(std::string("TTF_Init初始化TTF库时发生错误:") + SDL_GetError()); } } SDLFontPtr SDLFontManager::OpenFont(std::string name, int size) { TTF_Font *font = TTF_OpenFont(name.c_str(), size); if(NULL == font) { throw SDLException(std::string("TTF_OpenFont打开字体时发生错误:") + SDL_GetError()); } return SDLFontPtr(new SDLFont(font)); }
上面的代码很简单,初始化TTF环境 -> 打开字体 ->退出TTF环境
其中SDLFontPtr的定义为
typedef boost::shared_ptr<SDLFont> SDLFontPtr;
由于字体打开后需要关闭,所以这里用了智能指针,在SDLFontPtr不再被使用的时候,它会调用SDLFont的delete函数。SDLFont的定义
#ifndef SDLFONT_H_ #define SDLFONT_H_ #include <SDL/SDL_ttf.h> #include "boost/shared_ptr.hpp" #include "SDLSurface.h" #include <string> #include "SDLException.h" class SDLFont { friend class SDLFontManager; private: SDLFont(TTF_Font *font); public: virtual ~SDLFont(); public: /** * 渲染字体 * message 文字内容 * color 文字颜色 */ SDLSurfacePtr RenderTextSolid(std::string message, SDL_Color color); SDLSurfacePtr RenderUNICODESolid(std::string message, SDL_Color color); SDLSurfacePtr RenderUNICODEBlended(std::string message, SDL_Color color); SDLSurfacePtr RenderUNICODEShaded(std::string message, SDL_Color fg, SDL_Color bg); /** * 获取和设置字体的样式 */ int GetFontStyle(); void SetFontStyle(int style);//参见SDL/SDL_ttf.h中的宏定义 private: /* * 获取message中包含的Unicode字符个数 * 如果message="你好abc", 则返回5 * 如果message="你好啊", 则返回3 */ int getUnicodeCharCountByGb2312(std::string message); /** * 将GB2312编码转换成UNICODE编码 */ Uint16 * getUnicodeByGb2312(std::string message); private: TTF_Font *font; }; typedef boost::shared_ptr<SDLFont> SDLFontPtr; #endif /* SDLFONT_H_ */
#include "SDLFont.h" #include <iostream> SDLFont::SDLFont(TTF_Font *font) { this->font = font; } SDLFont::~SDLFont() { if (font != NULL) { TTF_CloseFont(font); } } SDLSurfacePtr SDLFont::RenderTextSolid(std::string message, SDL_Color color) { SDL_Surface* image = TTF_RenderText_Solid(font, message.c_str(), color); if (image == NULL) { throw SDLException(std::string("TTF_RenderText_Solid渲染字体时发生错误:") + SDL_GetError()); } return SDLSurfacePtr(new SDLSurface(image)); } SDLSurfacePtr SDLFont::RenderUNICODESolid(std::string message, SDL_Color color) { Uint16 * text = getUnicodeByGb2312(message); SDL_Surface* image = TTF_RenderUNICODE_Solid(font, text, color); delete[] text; if (image == NULL) { throw SDLException(std::string("TTF_RenderUNICODE_Solid渲染字体时发生错误:") + SDL_GetError()); } return SDLSurfacePtr(new SDLSurface(image)); } int SDLFont::getUnicodeCharCountByGb2312(std::string message) { int number = 0; for (unsigned int i = 0; i < message.length(); i++) { //判断最高位是不是0,如果是0表示是一个ASCII码,否则是一个汉字 //一个汉字占用两个字符,所以变量i需要++ if ((message.at(i) & 0x80) != 0) { i++; } number++; } return number; } Uint16 * SDLFont::getUnicodeByGb2312(std::string message) { /** * 获取字符串中包含的UNICODE字符数量,主要是为了后面转换时分配内存 * 这里其实不用精确具体的个数,只要保证chNum大于实际的个数就行了 * 因为一个汉字占用两个字节,所以这里用int chNum = message.length()也可以 * 如果message中不含汉字,则message.length()==getUnicodeCharCountByGb2312(message) * 如果message含有汉字,则message.length()>getUnicodeCharCountByGb2312(message) */ int chNum = getUnicodeCharCountByGb2312(message); //int chNum = message.length(); wchar_t * wmessage=new wchar_t[chNum]; char *chset = setlocale(LC_CTYPE,"chs");//设置当前字符集,返回字符集的描述 if(chset == NULL)//如果返回NULL,表示该环境中没有这种字符集 { throw SDLException("setlocale设置当前环境为简体中文时发生错误,可能是系统不支持简体中文:"); } //将GB2312转换成UNICODE字符集 //返回实际转换的UNICODE字符个数 int wchNum = mbstowcs(wmessage,message.c_str(),chNum); if(wchNum == -1)//如果里面包含了非法的GB2312字符编码 { throw SDLException(std::string("mbstowcs中文字符转UNICODE时发生错误:")); } setlocale(LC_CTYPE,"");//设置回原来的字符集 Uint16 *text = new Uint16[wchNum+1]; text[wchNum] = 0; for (int i = 0; i < wchNum; i++) { text[i] = wmessage[i]; } delete[] wmessage; return text; } SDLSurfacePtr SDLFont::RenderUNICODEBlended(std::string message, SDL_Color color) { Uint16 * text = getUnicodeByGb2312(message); SDL_Surface* image = TTF_RenderUNICODE_Blended(font, text, color); delete[] text; if (image == NULL) { throw SDLException(std::string("TTF_RenderUNICODE_Blended渲染字体时发生错误:") + SDL_GetError()); } return SDLSurfacePtr(new SDLSurface(image)); } SDLSurfacePtr SDLFont::RenderUNICODEShaded(std::string message, SDL_Color fg, SDL_Color bg) { Uint16 * text = getUnicodeByGb2312(message); SDL_Surface* image = TTF_RenderUNICODE_Shaded(font, text, fg, bg); delete[] text; if (image == NULL) { throw SDLException(std::string("TTF_RenderUNICODE_Blended渲染字体时发生错误:") + SDL_GetError()); } return SDLSurfacePtr(new SDLSurface(image)); } int SDLFont::GetFontStyle() { return TTF_GetFontStyle(font); } void SDLFont::SetFontStyle(int style) { TTF_SetFontStyle(font, style); }
SDLFont对TTF库中的函数进行了封装,并且增加了对gb2312格式编码的处理。这里使用的规范是封装SDL库的成员函数开头字母大写,其余的成员函数开头字母小写 ,后面的章节中将继续采用该规范。
在程序中,像std::string a="你好"这样的代码,编译器会根据系统的默认编码方式对"你好"这个字符串进行存储,我们现在用的系统都是简体中文,所以"你好"在内存中的编码方式一般为简体中文的方式,简体中文的编码方式有多种,这里给函数取的名字和写的注释都是GB2312,但实际的编码方式可能不是这个。只是GB2312比较常见,不管是GB2312还是GB其它的编码格式,mbstowcs都会将它转换成UNICODE编码格式。
这里用mbstowcs进行编码转换,并不代表只有这一种方式,也不是因为它是最好的,而是由于它使用起来比较简单。网上有很过关于编码转换的讨论,也有一些开源的项目,其中比较出名的是libiconv。我们在这里进行编码转换单纯是为了显示中文,并且是以系统中有简体中文环境为前提,怎样使我们的程序能在其他语言环境中运行,需要对程序做国际化支持的考虑。这里推荐Gettext项目。
上面怎么加载图片和显示文字的准备工作都已经做好,下一步是怎么使用这些类进行显示。代码如下
#include "Lesson02.h" Lesson02::Lesson02() { // TODO Auto-generated constructor stub } Lesson02::~Lesson02() { // TODO Auto-generated destructor stub } void Lesson02::onRender() { //先绘制背景图片 SDL::video()->BlitSurface(image, NULL, screen, NULL); //将文字显示在screen的中央 SDL_Rect rect; rect.x = screen->value()->w/2 - message->value()->w/2; rect.y = screen->value()->h/2 - message->value()->h/2; SDL::video()->BlitSurface(message, NULL, screen, &rect); } void Lesson02::onInit() { //加载背景图片 image = SDL::imageManager()->loadImage("E:\\code_picture\\464d64dd7512752d5982dd84.jpg"); //创建字体 SDLFontPtr font = SDL::fontManager()->OpenFont("E:\\code_picture\\wqy-zenhei.ttc", 20); //设置字体样式 font->SetFontStyle(TTF_STYLE_UNDERLINE | TTF_STYLE_ITALIC); //渲染文字,这里的两种颜色一个是前景色,一个是背景色 //渲染文字后返回的其实就是一张图片,和loadImage加载的图片没什么两样 message = font->RenderUNICODEShaded("大家好(Hello, Everybody)" , SDL::assistant()->makeColor(0, 0, 0) , SDL::assistant()->makeColor(255, 0, 0)); //大家可以试试下面注释掉的这两行代码,看看他们的不同效果 /*message = font->RenderUNICODESolid("大家好(Hello, Everybody)", SDL::assistant()->makeColor(255, 0, 0)); message = font->RenderUNICODEBlended("大家好(Hello, Everybody)", SDL::assistant()->makeColor(255, 0, 0)); */ }
由于我们前期的准备工作比较充分,所以到具体使用的时候代码就很简单,这里不再做解释。其中wqy-zenhei.ttc是文泉驿字体,网上有相关介绍,你也可以使用C:\WINDOWS\Fonts目录下随便一个可以支持中文的字体,比如simhei.ttf。
最后,需要添加连接选项,增加SDL_image和SDL_ttf,如下图:
最后重新编译整个工程即可。这里贴出来并不是全部代码,而是一些比较重要的代码,完整的代码见附件。