Windows程式設計 記憶體中載入圖片並顯示 Direct離屏表面的實現
版本:VS2015 語言:C++
前段時間去白空軌了,感覺快燃盡了。沒有看Windows的書,所以部落格也沒更,不過請組織放心,從現在開始,即使是節假日,我也會仔細鑽研DirectX的。
今天是第七章的完結,當時作者寫書比較老了,還一直用的8點陣圖,而8點陣圖牽扯到調色盤,所以他就一直在那邊糾結,我就簡單的看了一下,給大家介紹的也都是VS中能夠調試出來的程式。
好了,進入正式的學習。
玩cocos的玩家們應該對Sprite不陌生,Sprite簡單的來說就是一張圖片嘛,從磁碟中載入到記憶體中然後顯示到螢幕上,十分方便。而這次我就要介紹的就是在DirectX Windows程式中載入圖片。
首先準備一章圖片,因為作者使用的是
連結:http://pan.baidu.com/s/1qXJsAJi 密碼:icca
用這個工具畫一張圖,並儲存成bmp格式(我的程式碼中尺寸要求是300*300的):
嗯,就是一棵樹。
好了,圖片有了,怎麼載入到程式中呢?看程式碼:
#define BITMAP_ID 0x4D42 // 定義BMP資料結構 typedef struct BITMAP_FILE_TAG { BITMAPFILEHEADER bitmapfileheader; //BMP檔案頭部 BITMAPINFOHEADER bitmapinfoheader; //BMP資訊頭部 PALETTEENTRY palette[256]; //調色盤(但是在我們的程式中沒有作用) UCHAR *buffer; //資料 }BITMAP_FILE, *BITMAP_FILE_PTR; BITMAP_FILE_PTR picture1; //我們的圖片 // 翻轉bmp圖片 int Flip_Bitmap(UCHAR *image, int bytes_per_line, int height) { UCHAR* buffer; int index; if (!(buffer = (UCHAR*)malloc(bytes_per_line * height))) { popMessage(TEXT("malloc ERROR")); return 0; } memcpy(buffer, image, bytes_per_line * height); for (index = 0; index < height; ++index) { memcpy(&image[((height - 1) - index)*bytes_per_line], &buffer[index * bytes_per_line], bytes_per_line); } free(buffer); return 1; } // 讀取bmp型別的圖片 int Load_Bitmap_File(BITMAP_FILE_PTR bitmap, char* filename) { int file_handle; //檔案開啟處理的結果標誌 OFSTRUCT file_data; //OF結構,即OpenFile函式開啟後存入的資料結構 // 開啟需要的圖片 if (-1 == (file_handle = OpenFile(filename, &file_data, OF_READ))) { // 打開出錯 popMessage(TEXT("OpenFile ERROR")); return 0; } // 讀取檔案頭部 _lread(file_handle, &bitmap->bitmapfileheader, sizeof(BITMAPFILEHEADER)); if (bitmap->bitmapfileheader.bfType != BITMAP_ID) { _lclose(file_handle); popMessage(TEXT("THIS FILE IS NOT BMP")); return 0; } // 讀取檔案資訊頭部 _lread(file_handle, &bitmap->bitmapinfoheader, sizeof(BITMAPINFOHEADER)); _llseek(file_handle, -(int)(bitmap->bitmapinfoheader.biSizeImage), SEEK_END); if (bitmap->bitmapinfoheader.biBitCount == 24) { // 分配好記憶體 if (bitmap->buffer) free(bitmap->buffer); if (!(bitmap->buffer = (UCHAR*)malloc(bitmap->bitmapinfoheader.biSizeImage))) { _lclose(file_handle); popMessage(TEXT("malloc ERROR")); return 0; } // 新增進來 _lread(file_handle, bitmap->buffer, bitmap->bitmapinfoheader.biSizeImage); } else { // 其他情況報錯 _lclose(file_handle); popMessage(TEXT("COLOR DEPTH IS ERROR")); return 0; } _lclose(file_handle); // 最後記得把圖片翻轉回來 Flip_Bitmap(bitmap->buffer, bitmap->bitmapinfoheader.biWidth*(bitmap->bitmapinfoheader.biBitCount / 8), bitmap->bitmapinfoheader.biHeight); return 1; } // 解除安裝對應的圖片 int UnLoad_Bitmap_File(BITMAP_FILE_PTR bitmap) { if (bitmap->buffer) { free(bitmap->buffer); bitmap->buffer = NULL; } return 1; } // 遊戲初始化 int Game_Init(void* params = NULL) { // 基礎設定,略,不清楚的玩家請參見之前的部落格 // 載入24點陣圖 picture1 = new BITMAP_FILE(); if (!Load_Bitmap_File(picture1, "tree.bmp")) { popMessage(TEXT("LOAD PICTURE ERROR")); return 0; } return 1; } // 遊戲結束 int Game_Shutdown(void* params = NULL) { // 釋放初始化時建立的物件 UnLoad_Bitmap_File(picture1); delete picture1; //其他物件的釋放,略 return 1; } // 遊戲主迴圈 int Game_Main(void* params = NULL) { // 判斷是否要退出 if (KEYDOWN(VK_ESCAPE)) PostMessage(main_window_handle, WM_CLOSE, 0, 0); // 初始化主介面描述 DDRAW_INIT_STRUCT(ddsd); if (FAILED(lpddsback->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL))) //有備用表面時用備用表面加鎖 { wsprintf(msg, TEXT("LOCK 出錯了")); popMessage(msg); } //畫顏色 UINT *video_buffer = (UINT*)ddsd.lpSurface; for (int x = 0; x < 640; ++x) for (int y = 0; y < 480; ++y) Plot_Pixel_Fast32_2(x, y, 255, 255, 255, 128, video_buffer, ddsd.lPitch); // 載入圖片 int pos_x = 170; int pos_y = 180; for (int x = pos_x; x < 300+ pos_x; ++x) for (int y = pos_y; y < 300+pos_y; ++y) Plot_Pixel_Fast32_2(x, y, picture1->buffer[(y-pos_y) * 300 * 3 + (x-pos_x)*3 + 2], picture1->buffer[(y - pos_y) * 300 * 3+ (x - pos_x) * 3 + 1], picture1->buffer[(y - pos_y) * 300 * 3 + (x - pos_x) * 3 + 0], 0, video_buffer, ddsd.lPitch); if (FAILED(lpddsback->Unlock(NULL))) //解鎖 { wsprintf(msg, TEXT("UNLOCK 出錯了")); popMessage(msg); } while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT))); //切換介面,這邊的while不是很懂,應該每次只會呼叫一次 return 1; }
程式碼稍微有點長,而且是我只擷取的相關部分,暫略的部分請看之前的博文,我就不再多貼程式碼了。
在這邊需要注意的是_lread等方法,千萬注意,不要呼叫成C語言io.h中的方法了,這邊的_lread是Windows API的方法,看了書的同學要注意_lseek在現在版本中的方法名是_llseek,不要用錯了,不然找不到方法。
在Load_Bitmap_File中完成檔案的讀取,讀取到picture1這一個自定的結構的快取中,然後在遊戲迴圈中渲染該快取,就OK了。結果如下:
再加朵雲:
很好,非常的完美(不要問我為什麼雲是藍的),除了樹被雲遮擋住了一部分,這主要是我們的圖片是24位的,沒有透明度,書上的做法是設定一個顏色為透明顏色,當遇到該顏色,Direct會自動將其設定為透明度0,但我不想給這一塊的例項,這樣的程式即使自己改改也是可以的。關鍵是如何讀取32點陣圖片,這才是大家關心的吧?
哈哈,暫時先不做實驗,要弄的話,我想看看png是怎麼載入的。
現在是不是感覺整個人都昇華了?我們居然在這麼幾行程式碼下弄出了類似Sprite的效果,實在是太棒了,感覺離一個遊戲就差一步之遙了。
先等一等,在這一章中還有一個重要的概念,那就是離屏表面。大家可能要問了,離屏表面是什麼鬼?我們之前學習主表面、備用表面,它們是快取在哪的呢,沒錯就是視訊記憶體中。
離屏表面其實也是一段快取,但不用作顯示的表面,只用作快取。簡單的來說,我們之前的程式是在記憶體中快取了一張圖片,而現在我們要把它移動到視訊記憶體中,讓它顯示的效率更加高!
好了,讓我們看看程式:
LPDIRECTDRAWSURFACE7 lpdds_off = NULL; //離屏表面
// 遊戲初始化
int Game_Init(void* params = NULL)
{
// 基礎設定和載入24點陣圖略
// 建立離屏介面
DDRAW_INIT_STRUCT(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT ;
ddsd.dwWidth = 300;
ddsd.dwHeight = 300;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; //如果第二個引數設定為DDSCAPS_SYSTEMMEMORY,那麼離屏表面會快取到記憶體中
if (FAILED(lpdd->CreateSurface(&ddsd, &lpdds_off, NULL)))
{
popMessage(TEXT("建立離屏表面出錯了"));
return 0;
}
DDRAW_INIT_STRUCT(ddsd); //將載入的圖片載入到離屏表面
lpdds_off->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL);
UINT *buffer = (UINT*)ddsd.lpSurface;
for (int x = 0; x < 300; ++x)
for (int y = 0; y < 300; ++y)
Plot_Pixel_Fast32_2(x, y, picture2->buffer[y * 300 * 3 + x * 3 + 2], picture2->buffer[y * 300 * 3 + x * 3 + 1], picture2->buffer[y * 300 * 3 + x * 3 + 0], 0, buffer, ddsd.lPitch);
lpdds_off->Unlock(NULL);
return 1;
}
// 遊戲主迴圈
int Game_Main(void* params = NULL)
{
// 上面的程式碼略
// 使用離屏表面載入圖片
RECT dest_rest, source_rect;
dest_rest.left = pos_x; //目標矩形,即你的備用表面
dest_rest.top = pos_x;
dest_rest.right = pos_x + 300 - 1;
dest_rest.bottom = pos_x + 300 - 1;
source_rect.left = 0; //源矩形,即你的離屏表面
source_rect.top = 0;
source_rect.right = 300 - 1;
source_rect.bottom = 300 - 1;
if (FAILED(lpddsback->Unlock(NULL))) //解鎖
{
wsprintf(msg, TEXT("UNLOCK 出錯了"));
popMessage(msg);
}
if (FAILED(lpddsback->Blt(&dest_rest, lpdds_off, &source_rect, DDBLT_WAIT, NULL))) //載入!
{
popMessage(TEXT("離屏表面使用出錯了"));
return 0;
}
while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT))); //切換介面,這邊的while不是很懂,應該每次只會呼叫一次
return 1;
}
需要注意的是使用Blt方法把離屏的內容切到備用表面,不能在這邊加鎖,因為方法內部就主動實現的加鎖解鎖功能。
效果是一樣的,但是我們已經能主動使用視訊記憶體了!
後面其實還有個挺重要的內容,實現視窗化,但是我在win10上使用該程式碼,效果並不是很好,所以暫時就不介紹了。
嗯,接下來是第八章了,我準備中秋節的時候看到第十章,如果有有趣的內容的話,會把程式碼發給大家看看的,請期待一下咯。