版本:VS2015 語言:C++
玩cocos的玩家們應該對Sprite不陌生,Sprite簡單的來說就是一張圖片嘛,從磁盤中加載到內存中然後顯示到屏幕上,十分方便。而這次我就要介紹的就是在DirectX Windows程序中加載圖片。
首先准備一章圖片,因為作者使用的是BMP格式的,所以大家一定要注意圖片的格式,普通的圖片是用不了的,而且現在我寫的程序只能使用24位圖,所以需要一個史前的工具:
鏈接: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上使用該代碼,效果並不是很好,所以暫時就不介紹了。