加载BMP格式的图片
到现在为止我们只创建了一个窗口,其他什么都没干,这次我们将在窗口里显示图片,这是视频子系统最常用的功能,显示图片。SDL视频子系统只能加载bmp格式的位图。调用函数是SDL_Surface *SDL_LoadBMP(const char *file);这个函数的参数是c语言的字符串,返回值是一个绘图表面。在SDL中有两种绘图表面,第一种是使用SDL_SetVideoMode函数生成的显示表面(或窗口),显示表面是唯一的,只能使用SDL_SetVideoMod生成,显示表面可以直接显示在屏幕上;除了显示表面,加载图片生成的表面,加载字体生成的表面,这些表面不能直接显示在屏幕上,这是这两种表面的差别。
在开始本例前要准备一张bmp图片,然后我们可以自己写一个加载bmp图片的函数:
/*-------------------------------------------------------------------- 函数名: loadImage 参 数: char *filename 图像文件的名字 返回值: SDL_Surface * 成功返回指向图像表面的指针,否则返回NULL 功 能: 载入图像 备 注: ----------------------------------------------------------------------*/ SDL_Surface *loadImage(char *aFilename) { SDL_Surface* loadedImage = NULL; SDL_Surface* optimizedImage = NULL; //载入图像 loadedImage = SDL_LoadBMP( aFilename); if( NULL != loadedImage )//如果加载成功,loadedImage不为空 { //创建优化图像 optimizedImage = SDL_DisplayFormat( loadedImage ); //释放loadedImage SDL_FreeSurface( loadedImage ); } return optimizedImage; }
在函数里首先载入bmp图片,然后判断是否加载成功,如果加载成功,则要对生成的表面进行优化,为什么要进行优化?因为加载图片的生成的表面的格式可能和显示表面的格式不一样,以后显示这张图片的时候每次都要把图片的表面格式转换成显示表面的格式然后在显示,当然这个工作是系统进行的,但这会降低系统的效率,所以我们在这要将图片的表面按照显示表面的格式进行转换,然后将转换后表面指针返回。
SDL_DisplayFormat函数的作用就是将loadedImage所指的表面按照显示表面的格式生成一个副本,所以在生成副本以后我们会把loadedImage所指的原来的表面释放,然后将新生成的表面指针optimizedImage返回。
本例中我们将显示一副钟表作为背景图,钟表大小580*580,图片名字为clock.bmp。首先在vs2008里创建一个空的控制台应用,然后设置工程属性,这个在第一个安装教程里有详细说明。例子的运行效果:
工程源代码为:
1 /* 2 功能:演示加载位图 3 作者:csl 4 日期:2012-5-5 5 */ 6 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include "sdl\SDL.h" 10 11 //定义窗口的宽度、高度、位深 12 #define WIDTH 800 13 #define HEIGH 600 14 #define BPP 32 15 16 //定义表面指针 17 SDL_Surface * gpScreen = NULL;//显示窗口的指针 18 SDL_Surface * gpClock = NULL;//bmp图片表面的指针 19 20 SDL_Surface *loadImage(char *aFilename);//加载bmp图片 21 22 int main(int arc,char* agv[]) 23 { 24 if((SDL_Init(SDL_INIT_EVERYTHING)==-1)) //初始化SDL子系统 25 { 26 printf("Unable to init SDL: %s\n", SDL_GetError()); 27 exit(-1); 28 } 29 atexit(SDL_Quit);// 注册SDL_Quit,当退出时调用,使得退出时程序自动清理 30 31 //创建窗口 32 gpScreen = SDL_SetVideoMode(WIDTH,HEIGH, BPP, SDL_HWSURFACE | SDL_HWPALETTE | SDL_DOUBLEBUF ); 33 if(!gpScreen) 34 { 35 printf("Unable to create window: %s\n", SDL_GetError()); 36 exit(1); 37 } 38 39 //加载图片 40 gpClock = loadImage("clock.bmp"); 41 42 //位块传输 43 SDL_BlitSurface(gpClock,NULL,gpScreen,NULL); 44 45 //显示图片 46 SDL_Flip(gpScreen); 47 48 SDL_Delay(30000); //暂停3秒 49 50 //退出程序前必须释放表面指针 51 SDL_FreeSurface(gpScreen); 52 SDL_FreeSurface(gpClock); 53 54 system("pause"); 55 return 0; 56 } 57 58 59 /*-------------------------------------------------------------------- 60 函数名: loadImage 61 参 数: 62 char *filename 图像文件的名字 63 返回值: 64 SDL_Surface * 返回指向图像表面的指针 65 功 能: 载入图像 66 备 注: 67 ----------------------------------------------------------------------*/ 68 SDL_Surface *loadImage(char *aFilename) 69 { 70 SDL_Surface* loadedImage = NULL; 71 SDL_Surface* optimizedImage = NULL; 72 73 //载入图像 74 loadedImage = SDL_LoadBMP( aFilename); 75 76 if( NULL != loadedImage )//If the image loaded 77 { 78 //创建优化图像 79 optimizedImage = SDL_DisplayFormat( loadedImage ); 80 81 //释放loadImage 82 SDL_FreeSurface( loadedImage ); 83 } 84 return optimizedImage; 85 }
在主函数中第40行我们调用自己定义的loadImage函数加载了时钟的bmp图片,加载成功后我们要将该表面“贴到”显示表面上,然后调用刷新函数刷新就可以显示图片了。所以显示一个图片需要三步: 1.加载图片;2.将图片表面贴到显示表面上;3 刷新屏幕。
其中,第1步我们已经做完了,第2步如何将图片表面贴到显示表面上呢?首先要了解如何在屏幕上显示东西,在屏幕上显示的任何东西都存储在一种称之为帧缓存中,帧缓存类似一个二维数组,是一块内存区域,存储了屏幕上每一个像素显示数据,显示图像的时候,就会按帧缓存数据在屏幕上显示出图像来,而显示表面就是帧缓存,所以其他表面要显示的话,必须把自己的显示数据传输到显示表面,覆盖相应的显示表面的数据,则显示的时候就会显示出来,这就类似把一副画(图片表面)贴到白板(显示表面)上。在SDL中这称为Blit(bit block transfer,位块传输),可以把源表面的数据传输到目标表面上,覆盖相应的数据,这种传输很快,并且会自动把源表面格式转换成目标表格的格式。在SDL完成这个功能的函数:
int SDL_BlitSurface
(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);
参数:src 源表面
srcrect 源表面需要显示的地方,用一个矩形表示,SDL_Rect是一种结构体(描述的是一个矩形)
dst 目标表面
dstrect 在目标表面显示的位置,也是用一个矩形表示
用法,调用这个函数的时候需要把源表面的指针传给第一个参数,在SDL中任意一个图片都是用一个矩形(SDL_Rect)表示,你需要显示源表面的那一块就用矩形来指出,需要把这个矩形的指针传给第srcrect,如果为NULL则显示源表面的全部内容;将目标表面的指针传给第3个参数dst,如果要指定在目标表面上的显示位置,同样需要用一个矩形来表示这个位置,如果为NULL,则从目标表面左上角开始显示源表面。这样就理解了第43行的意思,把钟表全部显示到显示表面上,从目标表面的左上角开始显示。
位块传输完成以后呢,还不能在屏幕上看到,因为你要告诉显卡刷新屏幕,这就要调用SDL_Flip函数,函数的参数是显示表面的指针。刷新屏幕后就可以看到图片了。
显示任意一副图片都要做这三步,最后记着,退出程序时要释放相应的表面,所以第50,51行是释放显示表面和图片表面。
在这个例子中有一个新的数据类型SDL_Rect,这是SDL中的结构体
typedef struct{ Sint16 x, y; // 矩形左上角座标 Uint16 w, h; // 宽度与高度,以像素为单位 } SDL_Rect; |
如果你希望将clock显示到窗口的中间,则可以这样写
SDL_Rect dstRect;
dstRect.x = (WIDTH-gpClock->w)/2;
dstRect.y = (HEIGH-gpClock->h)/2;
dstRect.w = gpClock->w;
dstRect.h = gpClock->h;
SDL_BlitSurface(gpClock,NULL,gpScreen,&dstRect);//记着一定是dstRect的地址
用这段代码代替第43行则可以在窗口中间显示钟表,注意SDL_Rect dstRect;这句要放到函数的开始部分,因为C语言不允许在中间定义变量。这个例子的源代码可以点这儿下载。注意本例是在vs2008下调试通过。
使用SDL_LoadBmp不能加载其他格式图片的函数,如何加载其他格式图片呢?
SDL有一个扩展库SDL_image,这个扩展库支持BMP, PPM, XPM, PCX, GIF, JPEG, PNG和TGA格式的图片,这个扩展库在安装教程里已经介绍过如何安装了,如果安装了这个扩展库,那么在你的源文件里引入它的头文件:#include "SDL_image.h",然后在工程设置里添加静态库的引用SDL_image.lib。然后我们修改一下loadImage函数就可以了。
在loadImage函数里我们用IMG_Load函数代替SDLLoadBmp函数就可以加载其他格式图像了。
/*-------------------------------------------------------------------- 函数名: loadImage 参 数: char *filename 图像文件的名字 返回值: SDL_Surface * 返回指向图像表面的指针 功 能: 载入图像 备 注: ----------------------------------------------------------------------*/ SDL_Surface *loadImage(char *aFilename) { SDL_Surface* loadedImage = NULL; SDL_Surface* optimizedImage = NULL; //载入图像 loadedImage = IMG_Load( aFilename); if( NULL != loadedImage )//If the image loaded { //创建优化图像 optimizedImage = SDL_DisplayFormat( loadedImage ); //释放loadImage SDL_FreeSurface( loadedImage ); } return optimizedImage; }
提示,无论是IMG_Load函数还是SDL_LoadBmp函数,加载的图像文件一般和源文件在一个目录,文件名字可以使用绝对路径或相对路径,相对路径是相对于源文件所在目录而言的,比如要加载和源文件同目录下的图片clock.bmp,可以这样加载IMG_Load("clock.bmp");,如果把图像文件都放到源文件目录的子目录image下,则可以IMG_Load("image\\clock.bmp");记住一定是两个\\,\是转义字符。必须用两个\表示一个\。如果是绝对目录,比如说加载d:\bmp下的clock.bmp文件,则可以这样调用IMG_Load("d:\\bmp\\clock.bmp");。