1. 程式人生 > >Windows程式設計 記憶體中載入圖片並顯示 Direct離屏表面的實現

Windows程式設計 記憶體中載入圖片並顯示 Direct離屏表面的實現

版本:VS2015 語言:C++

前段時間去白空軌了,感覺快燃盡了。沒有看Windows的書,所以部落格也沒更,不過請組織放心,從現在開始,即使是節假日,我也會仔細鑽研DirectX的。

今天是第七章的完結,當時作者寫書比較老了,還一直用的8點陣圖,而8點陣圖牽扯到調色盤,所以他就一直在那邊糾結,我就簡單的看了一下,給大家介紹的也都是VS中能夠調試出來的程式。

好了,進入正式的學習。

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

嗯,接下來是第八章了,我準備中秋節的時候看到第十章,如果有有趣的內容的話,會把程式碼發給大家看看的,請期待一下咯。