位图的创建和使用:
经过长途跋涉,我们终于看到了位图,在游戏编程中我们大多数时间是在处理位图。试想一下我们玩游戏时的情形:任务的走动,场景的变换等都是跟位图的操作息息相关的,所以我特地它作为单独的一部分来讲述。
我们主要来关注两种位图:空位图和从磁盘导入的位图。
空位图的建立:
HBITMAP CreateCompatibleBitmap( HDC hdc,
int nWidth,
int nHeight) 用于创建空的位图
我们所创建的空位图的颜色格式是跟它所在的设备环境一致的。
注意:对于使用CreateCompatibleBitmap()创建的位图来说,它里面的数据都是随机不定的,因为我们只是向系统申请了一块内存,要想清理里面的数据就要把它选入到设备环境并使用FillRect()之类的函数对其进行清理。
从磁盘导入位图文件:
HANDLE LoadImage(HINSTANCE hinst,
LPCTSTR lpszName,
UINT uType,
int cxDesired,
int cyDesired,
UINT fuLoad);
注意这个函数不但用于导入位图,还用于导入图标和光标,所以要在必要的时候进行类型转换。我们也看到了这个函数返回的是个普通句柄,可以任意转换的。下面我们一起来看一下各个参数的详细定义:
LoadImage 参数名 用处
hinst 如果所要导入的位图是这个程序的资源文件,那么这个参数应该是当前程序的句
柄,如果不是,这个参数就为NULL
lpszName 所要导入的位图名称
uType 所要导入图片的类型(位图(IMAGE_BITMAP),光标(IMAGE_CURSOR),图标
(IMAGE_ICON))
cxDesired 设定导入后的宽度(0为默认大小)
cyDesired 设定导入后的高度(0为默认大小)
fuLoad 标志位,当导入位图时它应该是LR_LOADFROMFILE
使用位图:
照例,要想使用位图时许要把它选入到设备环境中去的。如果你不想把位图选入到窗口设备环境中去你可以选入到内存设备环境中去,还使用代码来说明问题吧,毕竟在程序世界里这是最易交流的工具了:
创建空位图:
{
…………
HDC hdcCompatible=GetDC(hWindow);
hdcMem=CreateCompatibleDC(hdcCompatible); //hdcMem是一个全局变量
hbmNew=CreateCompatibleBitmap(hdcCompatible,WIDTH,HEIGHT);
ReleaseDC(hWindow,hdcCompatible);
hbmOld=(HBITMAP)SelectObject(hdcMem,hbmNew);
…………
}
不用去想这段代码是用来干吗的,这样只能徒增烦恼,只要看里面的关键语句就够了。
从磁盘导入位图:
{
…………
HDC hdcCompatible=GetDC(hWindow);
hdcMem=CreateCompatibleDC(hdcCompatible); //hdcMem是一个全局变量
hbmNew=(HBITMAP)LoadImage(NULL,"FileName.bmp",IMAGE_BITMAP,0,0,LR_LAODFROMFILE); //导入位图
ReleaseDC(hWindow,hdcCompatible);
hbmOld=(HBITMAP)SelectObject(hdcMem,hbmNew);
…………
}
销毁位图:
{
…………
SelectObject(hdcMem,hbmOld);
DeleteObject(hbmNew);
…………
}
在以后的学习中我们将发现上面的小片段都将频繁出现,不用刻意记忆,但我保证你很快就能耳熟能详。
还是让我们停下来思考这样一个问题,无论是创建还是导入位图的代码都不是很长,但是如果我们要导入100张图片甚至更多呢?代码会直线上升。不过不用担心,在后面我们将创建一个用来方便操作位图的类,它会很好的解决这个问题。
关于BitBlt:
接下来的这个主题将是非常有趣的,把一个设备环境里的内容移动到另外一个设备环境中,用来完成这个工作的函数就是: BitBlt()
BOOL BitBlt( HDC hdcDest, //目的设备环境
int nXDest, //目的设备环境的x坐标
int nYDest, //目的设备环境的y坐标
int nWidth, //目的设备环境的宽度
int nHeight, //目的设备环境的高度
HDC hdcSrc, //源设备环境
int nXSrc, //源设备环境的x坐标
int nYSrc, //源设备环境的y坐标
DWORD dwRop); //光栅选项
该函数把源设备环境中的从坐标nXSrc,nYSrc开始的宽度为nWidth,高度为nHeight的区域复制到目的设备环境的指定坐标(nXDest,nYDest)处,在结合这些数据的具体方式要根据dwRop这个选项来定.
一些关于光栅的话题:
函数BitBlt中的大多数参数都不用过多解释的,dwRop是个例外,初次接触肯定不知道这个选项有什么用处,其实它是用来控制如何结合源设备环境与目的设备环境数据的问题的,以下是它的可选项以及解释:
可选项 含义
SRCCOPY 直接将源设备环境的数据填充到墓地设备环境中去,也就是直接覆盖
SRCAND 在结合源与目的数据时采用'与'运算
SRCPAINT 在结合源与目的数据时采用'或'运算
SRCINVERT 在结合源与目的数据时采用'异或'运算
下面是一个很有趣的例子:
case WM_MOUSEMOVE:
{
int x=LOWORD(lParam);
int y=HIWORD(lParam);
HDC hdc=GetDC(hWindow);
ShowTheCursor(hdc); //一个自定义函数,见下面
ptCursor.x=x; //一个全局变量用来存储光标的位置
ptCursor.y=y;
ShowTheCursor(hdc);
ReleaseDC(hWindow,hdc);
}break;
void ShowTheCursor(HDC hdc) //BITMAPWIDTH,BITMAPHEIGHT用于存储位图大小的全局变量
{
BitBlt(hdc,ptCursor.x-BITMAPWIDTH/2,ptCursor.y- BITMAPHEIGHT/2,BITMAPWIDTH,BITMAPHEIGHT,hdcMem,0,0,SRCINVERT);
}
把以上两个程序片段放在程序的相应位置,运行时你将发现位图跟着光标的位置移动,在处理鼠标移动消息时涉及到两次ShowTheCursor()的调用,第一次调用时为了把上一个位置的位图给删掉,一个位图跟自己做'异或'运算的结果.
GDI下的双缓冲:
我们前面做过一些小例子都有一个致命缺陷:当不管是由于某种原因使得窗口重新绘制的时候上面就只有背景了,其它内容都没有了。为什么呢?因为系统并没有为我们以前所绘制的数据保留一个副本以恢复。那么我们自己是不是可以弄一个副本呢?当然可以了,下面我们就来做这个工作:
我们需要另外一个缓冲区,把我们所需要的数据都暂时存到这个缓冲区里面,具体说我们需要一个内存设备环境,还有一张位图,用于存放我们的窗口数据。当我们改变数据的时候直接操作内存设备环境中的数据,而不是屏幕数据,但我们为了看到效果必须把内存设备环境中的内容拷贝到屏幕上去,在拷贝的时候就有说法了,这里我们有两种选择:一,把所有的数据都拷贝一遍。二,我们只选择那些需要重绘的区域中的数据来拷贝,因为窗口每次刷新并不是傻乎乎的全部更新的,而是首先看一下是哪些区域需要更新,然后把需要更新的数据拷贝过去即可,这样可以提高系统的整体性能。我们当然是选择第二种做法了。
怎么来实现这个好的想法呢? 利用更新矩形。具体该怎样工作呢?我们把需要更新的数据放在一个更新矩形里面,如果这个更新矩形是个空的,那我们就什么也不用做了;如果更新矩形不为空,那么我们也只是需要将更新矩形里面的数据拷贝到屏幕上即可。那么我们如何来实现这个更新矩形呢?
当更新矩形是空的时,任何加过来的矩形就变成了更新矩形;当更新矩形不为空时,更新矩形就要跟与之相加的矩形合并从而得到新的更新矩形UnionRect.