一、向屏幕畫圖:SDL_BlitSurface
怎么顯示一個(gè)圖像恃逻?顯然,首先要將它從硬盤載入內(nèi)存。載入內(nèi)存之后呢饭宾?
上一節(jié)中我們創(chuàng)建了一個(gè)screen
的屏幕表面,你可以把它理解為對(duì)應(yīng)屏幕顯示區(qū)域的一塊顯存。如果我們把載入內(nèi)存的圖像數(shù)據(jù)復(fù)制到screen
的某個(gè)位置,在屏幕顯示區(qū)域就應(yīng)該能看到我們的圖像辆苔。確實(shí)是這樣。
那么扼劈,怎么把圖像數(shù)據(jù)復(fù)制到screen所指的數(shù)據(jù)結(jié)構(gòu)中驻啤?這就引出了今天的主角SDL_BlitSurface
函數(shù)。函數(shù)原型:
int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);
-
src
是源頁(yè)面荐吵,也就是我們存放圖像數(shù)據(jù)的頁(yè)面骑冗。 -
srcrect
是源頁(yè)面上要復(fù)制的矩形區(qū)域。如果要復(fù)制整個(gè)源頁(yè)面先煎,則傳遞NULL
給srcrect
即可贼涩。 -
dst
是目標(biāo)頁(yè)面,在我們的例子中榨婆,它就是屏幕表面screen
磁携。 -
dstrect
是目標(biāo)頁(yè)面上的矩形區(qū)域,源頁(yè)面的上選定的區(qū)域?qū)@示在這個(gè)矩形區(qū)域中良风。
現(xiàn)在srcrect
確定為NULL
谊迄,dst
確定為screen
,未定的是src
頁(yè)面和dstrect
烟央,dstrect
要視src
頁(yè)面的大小而定统诺。
src
頁(yè)面是要裝載圖像數(shù)據(jù)的頁(yè)面。SDL自身支持BMP格式圖片的載入疑俭,SDL_LoadBMP
會(huì)把bmp圖片載入到一個(gè)SDL_Surface
頁(yè)面粮呢。
我們要使用的bmp圖片是和程序在同一目錄的card.bmp
。SDL_Surface *temp = SDL_LoadBMP("card.bmp");
把card.bmp的數(shù)據(jù)裝載到temp頁(yè)面中钞艇,現(xiàn)在temp
就是即將BlitSurface
的源頁(yè)面啄寡,因此,BlitSurface
的src
參數(shù)也確定了哩照。
dstrect
是一個(gè)指向SDL_Rect
結(jié)構(gòu)的地址挺物。SDL_Rect
有四個(gè)屬性:x, y, w, h,分別是矩形左上角的x飘弧、y坐標(biāo)以及矩形的寬和高识藤。我們想讓圖像顯示在屏幕區(qū)域的左上角,可以把x和y設(shè)為0次伶,把w和h設(shè)為源頁(yè)面的寬和高即可痴昧。假設(shè)源頁(yè)面是temp
,我們可以這樣定義目標(biāo)矩形:SDL_Rect dest_rect = {0, 0, temp->w, temp->h};
冠王,然后將dest_rect
的地址傳遞給dstrect
即可赶撰。
再來回顧一下我們要進(jìn)行的步驟:
- 載入圖像到頁(yè)面(用
SDL_LoadBMP
, 搞定) - 用
SDL_BlitSurface
函數(shù)將圖像頁(yè)面復(fù)制到屏幕表面(四個(gè)參數(shù)都確定了,搞定)
現(xiàn)在我們可以顯示圖像了,代碼如下:
SDL_Surface *temp = SDL_LoadBMP("card.bmp");
SDL_Rect dest_rect = {0, 0, temp->w, temp->h};
SDL_BlitSurface(temp, NULL, screen, &dest_rect);
make
一下扣囊,程序成功生成乎折。運(yùn)行一下,為什么沒有圖像侵歇?
二骂澄、雙緩沖和SDL_Flip
你記得我們第二節(jié)創(chuàng)建頁(yè)面的時(shí)候使用了雙緩沖的標(biāo)志嗎?SDL_DOUBLEBUF
惕虑。 雙緩沖是在顯存中創(chuàng)建了兩塊區(qū)域坟冲,一塊用于屏幕顯示,另一塊是離屏頁(yè)面溃蔫,用于在后臺(tái)作圖健提。當(dāng)后臺(tái)圖像繪制好后,調(diào)用SDL_Flip
對(duì)換離屏頁(yè)面和顯示頁(yè)面伟叛,這樣原來在離屏頁(yè)面上繪制的內(nèi)容就顯示出來了私痹。
雙緩沖可以避免畫面閃爍,想想看统刮,為什么紊遵?當(dāng)游戲的每一幀有大量繪圖工作時(shí),都要在后臺(tái)完成所有繪制侥蒙,再一次性顯示到屏幕表面暗膜。如果直接在屏幕表面繪制,就會(huì)看到各個(gè)圖像的繪制有先有后鞭衩,給人的感覺就是畫面閃爍学搜。
剛才我們只是把圖像畫到了screen
的離屏頁(yè)面,難怪屏幕沒有顯示论衍。趕緊調(diào)用SDL_Flip
翻轉(zhuǎn)screen
吧∪鹋澹現(xiàn)在程序變成這樣:
SDL_Surface *temp = SDL_LoadBMP("card.bmp");
SDL_Rect dest_rect = {0, 0, temp->w, temp->h};
SDL_BlitSurface(card_surface, NULL, screen, &dest_rect);
/* DO NOT FORGET! */
SDL_Flip(screen);
程序運(yùn)行結(jié)果如圖:
三、優(yōu)化性能坯台,圖像載入后轉(zhuǎn)換像素格式
對(duì)上面的程序钉凌,我們要做一點(diǎn)優(yōu)化。當(dāng)bmp圖像裝載入頁(yè)面后捂人,其像素格式和屏幕頁(yè)面的像素格式并不相同,在BlitSurface
時(shí)需要進(jìn)行轉(zhuǎn)換矢沿。多次BlitSurface
就要多次轉(zhuǎn)換滥搭,這樣很低效。
高效的做法是載入后就把頁(yè)面轉(zhuǎn)換成和屏幕頁(yè)面相同的像素格式捣鲸,這樣以后再BlitSurface
時(shí)就不用再轉(zhuǎn)換了瑟匆。可以用SDL_DisplayFormat
來完成這一步栽惶。
SDL_Surface *SDL_DisplayFormat(SDL_Surface *surface);
該函數(shù)把一個(gè)頁(yè)面的數(shù)據(jù)轉(zhuǎn)換像素格式后復(fù)制到新的頁(yè)面愁溜,并返回新的頁(yè)面疾嗅。原來的代碼片段變成這樣。
SDL_Surface *temp = SDL_LoadBMP("card.bmp");
/* 轉(zhuǎn)換生成新的頁(yè)面冕象,像素格式和screen一致 */
SDL_Surface *card_surface = SDL_DisplayFormat(temp);
/* temp已經(jīng)沒用了代承,把它釋放掉,回收內(nèi)存 */
SDL_FreeSurface(temp);
SDL_Rect dest_rect = {0, 0, card_surface->w, card_surface->h};
SDL_BlitSurface(card_surface, NULL, screen, &dest_rect);
/* DO NOT FORGET! */
SDL_Flip(screen);
四渐扮、完整的代碼一覽
完整的程序代碼如下:
/* usage: gcc -o game main.c `sdl-config --cflags --libs` */
#include <stdio.h>
#include <SDL.h>
int main(int argc, char *argv[])
{
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
printf("Unable to initialize SDL: %s\n", SDL_GetError());
exit(1);
}
atexit(SDL_Quit);
SDL_Surface *screen = SDL_SetVideoMode(320, 480, 0, SDL_HWSURFACE|SDL_DOUBLEBUF);
if (screen == NULL) {
printf("Unable to set video mode: %s\n", SDL_GetError());
exit(1);
}
SDL_WM_SetCaption("Hello, Linux Game!", NULL);
SDL_Surface *temp = SDL_LoadBMP("card.bmp");
if(temp == NULL) {
printf("Load bmp image failed!\n");
exit(1);
}
SDL_Surface *card_surface = SDL_DisplayFormat(temp);
SDL_FreeSurface(temp);
SDL_Rect dest_rect = {0, 0, card_surface->w, card_surface->h};
SDL_BlitSurface(card_surface, NULL, screen, &dest_rect);
// DO NOT FORGET!
SDL_Flip(screen);
SDL_FreeSurface(card_surface);
/* To pause the program */
while(1){
SDL_Delay(100);
}
return 0;
}
注意论悴,程序最后用了一個(gè)while
死循環(huán)防止程序一閃就退出,在命令行你可以用C-c結(jié)束程序墓律。之后我們會(huì)介紹更優(yōu)雅的退出程序的方式膀估。
while
循環(huán)中的SDL_Delay(100)
是讓程序阻塞100毫秒,這樣可以避免空循環(huán)把CPU耗盡耻讽。
完整的程序察纯、makefile以及圖片資源點(diǎn)這里查看:https://github.com/jollywing/make-linux-rpg/tree/master/chap03。
五针肥、小結(jié)
到現(xiàn)在為止饼记,我們學(xué)過的SDL函數(shù)包括:
-
SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO)
,初始化視頻和音頻子系統(tǒng)祖驱,需要在使用其它SDL函數(shù)前調(diào)用握恳。 -
SDL_Quit()
,關(guān)閉打開的SDL子系統(tǒng)捺僻,在程序退出前調(diào)用乡洼。 -
SDL_SetVideoMode(320, 480, 0, SDL_HWSURFACE|SDL_DOUBLEBUF)
,在顯存內(nèi)創(chuàng)建一個(gè)寬320像素匕坯,高480像素束昵,顏色深度和當(dāng)前顯示一致,具有雙緩沖的顯示頁(yè)面葛峻。成功返回頁(yè)面地址锹雏,失敗返回NULL
。 -
SDL_LoadBMP(const char *picpath);
裝載一個(gè)BMP圖片术奖,生成一個(gè)裝載圖像數(shù)據(jù)的頁(yè)面礁遵。 -
SDL_DisplayFormat(SDL_Surface *surf)
對(duì)surf
中數(shù)據(jù)轉(zhuǎn)換像素格式后存到一個(gè)新的頁(yè)面,并返回新頁(yè)面采记。 -
SDL_FreeSurface(SDL_Surface *surf)
釋放頁(yè)面佣耐,回收內(nèi)存。 -
SDL_BlitSurface(SDL_Surface *src, SDL_Rect *src_rect, SDL_Surface *dest, SDL_Rect *dest_rect);
唧龄,本節(jié)的核心函數(shù)兼砖。把src
頁(yè)面中src_rect
框中的圖像顯示到dest
頁(yè)面中的dest_rect
區(qū)域。 - SDL_Flip(SDL_Surface *surf),翻轉(zhuǎn)帶有雙緩沖的頁(yè)面讽挟。當(dāng)繪制完成要更新屏幕顯示時(shí)懒叛,一定別忘了調(diào)用這個(gè)函數(shù)。
-
SDL_Delay(size_t n)
耽梅,等待n毫秒薛窥。等待時(shí)間不占用CPU。