版本: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上使用該代碼,效果並不是很好,所以暫時就不介紹了。