欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Windows编程 内存中加载图片并显示 Direct离屏表面的实现

程序员文章站 2022-05-25 12:28:28
版本:VS2015 语言:C++ 玩cocos的玩家们应该对Sprite不陌生,Sprite简单的来说就是一张图片嘛,从磁盘中加载到内存中然后显示到屏幕上,十分方便。而这次我就要...

版本:VS2015 语言:C++

玩cocos的玩家们应该对Sprite不陌生,Sprite简单的来说就是一张图片嘛,从磁盘中加载到内存中然后显示到屏幕上,十分方便。而这次我就要介绍的就是在DirectX Windows程序中加载图片。

首先准备一章图片,因为作者使用的是BMP格式的,所以大家一定要注意图片的格式,普通的图片是用不了的,而且现在我写的程序只能使用24位图,所以需要一个史前的工具:

链接:https://pan.baidu.com/s/1qXJsAJi 密码:icca

用这个工具画一张图,并保存成bmp格式(我的代码中尺寸要求是300*300的):

Windows编程 内存中加载图片并显示 Direct离屏表面的实现

嗯,就是一棵树。

好了,图片有了,怎么加载到程序中呢?看代码:

#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了。结果如下:

Windows编程 内存中加载图片并显示 Direct离屏表面的实现

再加朵云:

Windows编程 内存中加载图片并显示 Direct离屏表面的实现

很好,非常的完美(不要问我为什么云是蓝的),除了树被云遮挡住了一部分,这主要是我们的图片是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上使用该代码,效果并不是很好,所以暂时就不介绍了。