第3章 加載并顯示BMP圖像

一、向屏幕畫圖: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è)面先煎,則傳遞NULLsrcrect即可贼涩。
  • 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.bmpSDL_Surface *temp = SDL_LoadBMP("card.bmp");把card.bmp的數(shù)據(jù)裝載到temp頁(yè)面中钞艇,現(xiàn)在temp就是即將BlitSurface的源頁(yè)面啄寡,因此,BlitSurfacesrc參數(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)行的步驟:

  1. 載入圖像到頁(yè)面(用SDL_LoadBMP, 搞定)
  2. 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é)果如圖:


裝載并顯示bmp

三、優(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ù)包括:

  1. SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO),初始化視頻和音頻子系統(tǒng)祖驱,需要在使用其它SDL函數(shù)前調(diào)用握恳。
  2. SDL_Quit(),關(guān)閉打開的SDL子系統(tǒng)捺僻,在程序退出前調(diào)用乡洼。
  3. SDL_SetVideoMode(320, 480, 0, SDL_HWSURFACE|SDL_DOUBLEBUF),在顯存內(nèi)創(chuàng)建一個(gè)寬320像素匕坯,高480像素束昵,顏色深度和當(dāng)前顯示一致,具有雙緩沖的顯示頁(yè)面葛峻。成功返回頁(yè)面地址锹雏,失敗返回NULL
  4. SDL_LoadBMP(const char *picpath);裝載一個(gè)BMP圖片术奖,生成一個(gè)裝載圖像數(shù)據(jù)的頁(yè)面礁遵。
  5. SDL_DisplayFormat(SDL_Surface *surf)對(duì)surf中數(shù)據(jù)轉(zhuǎn)換像素格式后存到一個(gè)新的頁(yè)面,并返回新頁(yè)面采记。
  6. SDL_FreeSurface(SDL_Surface *surf)釋放頁(yè)面佣耐,回收內(nèi)存。
  7. 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ū)域。
  8. SDL_Flip(SDL_Surface *surf),翻轉(zhuǎn)帶有雙緩沖的頁(yè)面讽挟。當(dāng)繪制完成要更新屏幕顯示時(shí)懒叛,一定別忘了調(diào)用這個(gè)函數(shù)。
  9. SDL_Delay(size_t n)耽梅,等待n毫秒薛窥。等待時(shí)間不占用CPU。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末褐墅,一起剝皮案震驚了整個(gè)濱河市拆檬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妥凳,老刑警劉巖竟贯,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異逝钥,居然都是意外死亡屑那,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門艘款,熙熙樓的掌柜王于貴愁眉苦臉地迎上來持际,“玉大人,你說我怎么就攤上這事哗咆≈┯” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵晌柬,是天一觀的道長(zhǎng)姥份。 經(jīng)常有香客問我,道長(zhǎng)年碘,這世上最難降的妖魔是什么澈歉? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮屿衅,結(jié)果婚禮上埃难,老公的妹妹穿的比我還像新娘。我一直安慰自己涤久,他們只是感情好涡尘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著响迂,像睡著了一般悟衩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栓拜,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼幕与。 笑死挑势,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的啦鸣。 我是一名探鬼主播潮饱,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼诫给!你這毒婦竟也來了香拉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤中狂,失蹤者是張志新(化名)和其女友劉穎凫碌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胃榕,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盛险,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勋又。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苦掘。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖楔壤,靈堂內(nèi)的尸體忽然破棺而出鹤啡,到底是詐尸還是另有隱情,我是刑警寧澤蹲嚣,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布递瑰,位于F島的核電站,受9級(jí)特大地震影響端铛,放射性物質(zhì)發(fā)生泄漏泣矛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一禾蚕、第九天 我趴在偏房一處隱蔽的房頂上張望您朽。 院中可真熱鬧,春花似錦换淆、人聲如沸哗总。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)讯屈。三九已至,卻和暖如春县习,著一層夾襖步出監(jiān)牢的瞬間涮母,已是汗流浹背谆趾。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叛本,地道東北人沪蓬。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像来候,于是被迫代替她去往敵國(guó)和親跷叉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容