第三章 載入並顯示BMP影像

吳吉慶發表於2014-12-26

一、向螢幕畫圖:SDL_BlitSurface

怎麼顯示一個影像?顯然,首先要將它從硬碟載入記憶體。載入記憶體之後呢?

上一節中我們建立了一個screen的螢幕表面,你可以把它理解為對應螢幕顯示區域的一塊視訊記憶體。如果我們把載入記憶體的影像資料複製到screen的某個位置,在螢幕顯示區域就應該能看到我們的影像。確實是這樣。

那麼,怎麼把影像資料複製到screen所指的資料結構中?這就引出了今天的主角SDL_BlitSurface函式。函式原型:

int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);
  • src是源頁面,也就是我們存放影像資料的頁面。
  • srcrect是源頁面上要複製的矩形區域。如果要複製整個源頁面,則傳遞NULLsrcrect即可。
  • dst是目標頁面,在我們的例子中,它就是螢幕表面screen
  • dstrect是目標頁面上的矩形區域,源頁面的上選定的區域將顯示在這個矩形區域中。

現在srcrect確定為NULLdst確定為screen,未定的是src頁面和dstrectdstrect要視src頁面的大小而定。

src頁面是要裝載影像資料的頁面。SDL自身支援BMP格式圖片的載入,SDL_LoadBMP會把bmp圖片載入到一個SDL_Surface頁面。

我們要使用的bmp圖片是和程式在同一目錄的card.bmpSDL_Surface *temp = SDL_LoadBMP("card.bmp");把card.bmp的資料裝載到temp頁面中,現在temp就是即將BlitSurface的源頁面,因此,BlitSurfacesrc引數也確定了。

dstrect是一個指向SDL_Rect結構的地址。SDL_Rect有四個屬性:x, y, w, h,分別是矩形左上角的x、y座標以及矩形的寬和高。我們想讓影像顯示在螢幕區域的左上角,可以把x和y設為0,把w和h設為源頁面的寬和高即可。假設源頁面是temp,我們可以這樣定義目標矩形:SDL_Rect dest_rect = {0, 0, temp->w, temp->h};,然後將dest_rect的地址傳遞給dstrect即可。

再來回顧一下我們要進行的步驟:

  1. 載入影像到頁面(用SDL_LoadBMP, 搞定)
  2. SDL_BlitSurface函式將影像頁面複製到螢幕表面(四個引數都確定了,搞定)

現在我們可以顯示影像了,程式碼如下:

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一下,程式成功生成。執行一下,為什麼沒有影像?

二、雙緩衝和SDL_Flip

你記得我們第二節建立頁面的時候使用了雙緩衝的標誌嗎?SDL_DOUBLEBUF。 雙緩衝是在視訊記憶體中建立了兩塊區域,一塊用於螢幕顯示,另一塊是離屏頁面,用於在後臺作圖。當後臺影像繪製好後,呼叫SDL_Flip對換離屏頁面和顯示頁面,這樣原來在離屏頁面上繪製的內容就顯示出來了。

雙緩衝可以避免畫面閃爍,想想看,為什麼?當遊戲的每一幀有大量繪圖工作時,都要在後臺完成所有繪製,再一次性顯示到螢幕表面。如果直接在螢幕表面繪製,就會看到各個影像的繪製有先有後,給人的感覺就是畫面閃爍。

剛才我們只是把影像畫到了screen的離屏頁面,難怪螢幕沒有顯示。趕緊呼叫SDL_Flip翻轉screen吧。現在程式變成這樣:

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);

程式執行結果如圖:

裝載並顯示bmp

三、優化效能,影像載入後轉換畫素格式

對上面的程式,我們要做一點優化。當bmp影像裝載入頁面後,其畫素格式和螢幕頁面的畫素格式並不相同,在BlitSurface時需要進行轉換。多次BlitSurface就要多次轉換,這樣很低效。

高效的做法是載入後就把頁面轉換成和螢幕頁面相同的畫素格式,這樣以後再BlitSurface時就不用再轉換了。可以用SDL_DisplayFormat來完成這一步。

SDL_Surface *SDL_DisplayFormat(SDL_Surface *surface);

該函式把一個頁面的資料轉換畫素格式後複製到新的頁面,並返回新的頁面。原來的程式碼片段變成這樣。

SDL_Surface *temp = SDL_LoadBMP("card.bmp");
/* 轉換生成新的頁面,畫素格式和screen一致 */
SDL_Surface *card_surface = SDL_DisplayFormat(temp);
/* 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);

四、完整的程式碼一覽

完整的程式程式碼如下:

/* 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;
}

注意,程式最後用了一個while死迴圈防止程式一閃就退出,在命令列你可以用C-c結束程式。之後我們會介紹更優雅的退出程式的方式。

while迴圈中的SDL_Delay(100)是讓程式阻塞100毫秒,這樣可以避免空迴圈把CPU耗盡。

完整的程式、makefile以及圖片資源點這裡檢視:https://github.com/jollywing/make-linux-rpg/tree/master/chap03

五、小結

到現在為止,我們學過的SDL函式包括:

  1. SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO),初始化視訊和音訊子系統,需要在使用其它SDL函式前呼叫。
  2. SDL_Quit(),關閉開啟的SDL子系統,在程式退出前呼叫。
  3. SDL_SetVideoMode(320, 480, 0, SDL_HWSURFACE|SDL_DOUBLEBUF),在視訊記憶體內建立一個寬320畫素,高480畫素,顏色深度和當前顯示一致,具有雙緩衝的顯示頁面。成功返回頁面地址,失敗返回NULL
  4. SDL_LoadBMP(const char *picpath);裝載一個BMP圖片,生成一個裝載影像資料的頁面。
  5. SDL_DisplayFormat(SDL_Surface *surf)surf中資料轉換畫素格式後存到一個新的頁面,並返回新頁面。
  6. SDL_FreeSurface(SDL_Surface *surf)釋放頁面,回收記憶體。
  7. SDL_BlitSurface(SDL_Surface *src, SDL_Rect *src_rect, SDL_Surface *dest, SDL_Rect *dest_rect);,本節的核心函式。把src頁面中src_rect框中的影像顯示到dest頁面中的dest_rect區域。
  8. SDL_Flip(SDL_Surface *surf),翻轉帶有雙緩衝的頁面。當繪製完成要更新螢幕顯示時,一定別忘了呼叫這個函式。
  9. SDL_Delay(size_t n),等待n毫秒。等待時間不佔用CPU。

相關文章