程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> Windows編程 內存中加載圖片並顯示 Direct離屏表面的實現

Windows編程 內存中加載圖片並顯示 Direct離屏表面的實現

編輯:關於C++

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


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved