位图,设备上下文和BitBlt
范例:bmp_one
GDI
MS
Windows跟DOS比起来,真正比较伟大的一件事就是你不用知道你用的什么显示硬件就可以显示图形.与DOS不同的是,Windows提供了一套API就作图形设备接口(Graphics
Device InterFace),或称之为GDI.GDI用一套通用的图形对象来向屏幕,內存甚至是打印机绘图.
设备上下文
GDI跟一个跟设备上下文(Device Context,DC)的概念联系得很紧密,由HDC这个数据类型来代表(Handle to Device
Context,设备上下文的句柄).一个HDC大概就是一个你可以向其绘图的句柄;它可以代表整个屏幕,一个窗口的客戶区域,一个存在內存中的位图,或是一个打印机.比较好的一点就是你甚至不需要知道它是指向哪里,你可以用一种方法来使用它,在你写自定义的绘图函数时候你就可以把它用于任何上述设备而为每个设备作修改.
HDC跟大多数的GDI对象一样有点生硬,就是说你不能直接访问它的数据...但是你可以将其传给可以操作它的很多GDI函数,比如绘图,获取它的消息,或对其作出一些修改.
例如,如果你想在一个窗口上绘图,首先你要用GetDC()来获取代表这个窗口的HDC,然后你就可以用任何以HDC为参数的GDI函数比如BitBlt()来绘图,TextOut()来输出文字,LineTo()来画線等等.
位图
位图可以如前面章节的例子中的图标一样来装入,有一个LoadBitmap()来完成大多数的基本操作比如简单地装入一个位图资源,还有LoadImage()可用来从一个*.bmp文件装入位图,就像从图标中一样.
GDI有个不方便的地方是你不能直接从向位图对象(HBITMAP类型)绘图.记住绘图操作已经被设备上下文抽象,所以要对一个位图对象绘图,你首先要创建一个內存DC,然后用SelectObject()把HBITMAP选入其中.最后的效果就是这个HDC指向的''设备''就是內存中的位图,当你操作这个HDC时,操作实际上是在那个位图上起了效果.
我提到的,有一个很方便的方法,只要你可写为向一个HDC绘图写代码你就可以它其用在一个窗口DC或一个內存DC上而不需要任何的检查和改动.
你自己是不能操作在內存中的位图数据的.你可以用设备独立位图(Device independent
Bitmaps,DIB)来这样做,并且你甚至可以把GDI和一般的操作一起用在DIB上.但是这里,因为已经超出了我们这个简单的教程的范围所以我们现在只讨论简单的GDI操作本身.
GDI泄漏
一旦完成了对一个HDC的操作,释放它是很重要的(具体怎么样做取決于你是怎么样得到它的,这点我们马上将要谈到).GDI对象是有限的.
在windows95之前的版本,它们不限是难以致信的有限而且是在整个系统中共享,所以如果一个程序用了过多的话,別的程序就根本不能绘任何东西!幸运的是现在的情況已经不这样了,而且你可以在Windows2000和XP中用很多个的资源,直至情況变得很糟...但是释放GDI对象是很容易被忘记的,在Windows
9x下你的程序可以很快地用完GDI资源.理论上讲在NT(NT/2K/XP)系统中你不应该可能把系统的GDI资源用完,但是还是存在极端情況,或者你就是不经意地冒险.
如果你的程序在刚刚开始的几分钟运行正常然后开始奇怪地绘图或根本就不画图了,这就表明你很可能泄漏了GDI
资源.值得你注意要释放的GDI对象不仅仅只有HDC,但是把位图,字体之类的资源在程序的整个运行周期中保存基本上是安全的,这样做比每次需要就装入一次要有效率些.
还有,一个HDC一次只能保存一种类型的对象(位图,字体,笔...),当你选入一个新的进去的时候它将原来的返回.
合理地处理这个对象是很重要的.如果你完全忽略它,它将在內存中堆积并导致GDI泄漏.当一个HDC被创建,它也和某些默认选入它的对象一起创建...当它们返回至你的时候最好将它们存起来,并在你完成了HDC绘图你就要把它们存回去.这不仅仅只是把你自己的对象从HDC刪除(这是个好事情)也可以将默认的对象合理地放在当你释放或销毀这个HDC的时候可以看到的地方(这是非常好的).
重要更新:不是所有的对象默认地选入HDC,你可以看看MSDN找出哪些不是这样的.因为我先前不确定HBITMAP是否是它们中的一个,因为看起来沒有关于这个的确定的文档,并且样例(即使是微软自己的)经常忽略默认的位图.自从几年前原来的教程被写作后,我确定了事实上有个默认的位图需要释放.这个消息是由Shaun
Ivory提供的,一个微软的工程师,也是我在#winprog上的一个朋友.
事实上原来微软写的一个屏幕保护程序有一个bug,后来被发现就是因为默认的位图对象沒有被替換或销毀,它就慢慢地用完了GDI
资源.警惕!这是一个很容易犯的错误.
显示位图
好了,开始谈正事.对于一个窗口最简单的绘图操作发生于对WM_PAINT消息的处理中.当你的窗口从最小化或是从一个覆盖它的窗口恢复到被显示,
Windows向你的窗口发送WM_PAINT消息来让你知道你的窗口需要重新绘它的內容.当你在屏幕上绘图时候,并不是永久地绘上去,只是在它被別的什么东西覆盖它的之前存在,而且在适当的时候你需要重新绘它.
HBITMAP g_hbmBall = NULL;
case WM_CREATE:
g_hbmBall = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BALL));
if(g_hbmBall == NULL)
MessageBox(hwnd, "Could not load IDB_BALL!", "Error", MB_OK | MB_ICONEXCLAMATION);
break;
首先要做的当然是装入位图,对于一个位图资源来说这很简单,跟装入其它的什么资源沒有什么很大的差異.然后我们开始绘图...
case WM_PAINT:
{
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = SelectObject(hdcMem, g_hbmBall);
GetObject(g_hbmBall, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
}
break;
获取窗口DC
在开始的时候我们定义一些变量.注意第一个是一个位图,而不是一个HBITMAP.BITMAP是一个保存实际的GDI对象HBITMAP的有关消息的一个结构体.我们需要一种得到HBITMAP的高度和宽度的方法,所以我们使用GetObject()这个函数,跟它的名字相反,它并不能真正地获取一个对象,而是获取一个已经存在的对象的相关消息.如果是”GetObjectInfo”这个名字还更恰切些.GetObject()可以对不同种类的GDI对象使用,具体地是通过第二个参数,即结构体的大小来区分的.
PAINTSTRUCT是一个结构体,保存有要绘图的窗口和要实际绘图的內容相关的消息.对于大多数简单的任务,你可以简单地忽略它包含的消息,但调用BeginPaint()时要用到它.BeginPaint()这个函数,如同它的名字一样被特意设计来处理WM_PAINT消息的.当不是处理一个WM_PAINT消息的时候,你可以使用GetDC(),这个我们马上会在我们的定时动画样例中看到...但是在WM_PAINT消息的处理中,BeginPaint()与EndPaint()的使用是很重要的.
BeginPaint()向我们返回一个HDC,代表我们传给它的HWND,就是WM_PAINT消息来处理的那个句柄.我们对这个HDC所做的任何操作将立即在屏幕上显示出来.
为位图创建一个內存DC
与同我在上面提到的,为了在位图上或用位图来绘图,我们要在內存中创建一个DC...这里最简单的方法是用CreateCompatibleDC()与我们已经有的那个来创建.这样做我们就会得到一个与我们的窗口的HDC的顏色深度和显示属性相兼容的內存DC.
现在我们调用SelectObject()将这个位图选入这个DC来,小心地存储默认的位图这样我们才能在后面来替換它时候不引起GDI对象泄漏.
绘图
一旦我们将这个位图的大小得到并填入到BITMAP结构体中去,我们就可以调用BitBlt()来将这个图像从內存DC拷贝到窗口DC中去,以在屏幕上显示.
还是老话,你可以在MSDN中查它的每个参数,但简单地讲,它们是:目标,位置与大小,源与源位置,最后是光栅操作(Raster
Operation,ROP代码),这是用来指明怎样复制的.在本例中我们只想简单地把源进行复制,不搞花里胡哨.
BitBlt()可能是所有Win32
API中最Happy的一个函数,任何想在Windows下写点遊戏或是別的图形应用的人都会喜欢它.也可能是我第一个记住了所有的参数的API函数.
清除
这个时候位图应该已经在屏幕上了,我们要自己来做清洁工作.要做的第一件事情是把內存DC恢复到我们获取它时候的状态,意思就是要用我们存储的默认的位图来替換我们的位图.下一步就可以用DeleteDC()来把它们一起刪除.
最后我们用EndPaint()来释放从BeginPaint()得到的窗口DC.
销毀一个HDC有时有点令人迷惑,因为根据你获得它的方式,至少有三种方法来完成这个操作.
下面是一些常用的获取一个DC,和怎样在使用完释放的方法.
·GetDC() -- ReleaseDC()
·BeginPaint() -- EndPaint()
·CreateCompatibleDC() -- DeleteDC()
最后在我们程序终止的时候,我们想把我们配置的所有的资源释放掉.从技术上讲这不是絕对地需要,因为现代的Windows平台在你的程序终止的时候已经可以很智能地把你的所有东西释放掉,但是把你自己的东西管理好是很有好处的,因为如果你偷懒不去刪除它们,它们的习惯就会变得很松散.而且毫无质疑的,尤其在一些老的版本的Windows中还有很多的bug,并不能完全清除你的GDI对象,如果你自己不完成这一点的话.
case WM_DESTROY:
DeleteObject(g_hbmBall);
PostQuitMessage(0);
break;
|