当前位置: 移动技术网 > IT编程>开发语言>C/C++ > Windows编程 内存中加载图片并显示 Direct离屏表面的实现

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

2018年04月25日  | 移动技术网IT编程  | 我要评论

深度win7,治理雾霾的上市公司,捕猎响尾蛇全集

版本:VS2015 语言:C++

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

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

链接:https://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上使用该代码,效果并不是很好,所以暂时就不介绍了。


如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网