Windows编程 内存中加载图片并显示 Direct离屏表面的实现
版本: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上使用该代码,效果并不是很好,所以暂时就不介绍了。
上一篇: 嵌入式系统组成的总结