windows下画一张非矩形的位图并不容易,主要是因为外部的位图一般是矩形状的,如果要将此矩形状的位图显示成非矩形图(即只显示大图中一部分形状),需要采用“掩码”位图和一些光栅操作。这个在一些小游戏中很常用,使用一张大图,将游戏中常用的小图标都包含进去,然后挨个读取其中的一些图标来单独操作。
1,根据《Windows程序设计》的例子,先看两张图:
原图 实现后的效果(图1.2)
2,实现代码
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
static HBITMAP hBitmapImag, hBitmapMask ;
static HINSTANCE hInstance ;
static int cxClient, cyClient, cxBitmap, cyBitmap ;
BITMAP bitmap ;
HDC hdc, hdcMemImag, hdcMemMask ;
int x, y ;
PAINTSTRUCT ps ;
switch (message) {
case WM_CREATE:
hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
hBitmapImag = LoadBitmap (hInstance, TEXT ("Matthew")) ;
GetObject (hBitmapImag, sizeof (BITMAP), &bitmap) ;
cxBitmap = bitmap.bmWidth ;
cyBitmap = bitmap.bmHeight ;
hdcMemImag = CreateCompatibleDC (NULL) ;
SelectObject (hdcMemImag, hBitmapImag) ;
hBitmapMask = CreateBitmap (cxBitmap, cyBitmap, 1, 1, NULL) ;
hdcMemMask = CreateCompatibleDC (NULL) ;
SelectObject (hdcMemMask, hBitmapMask) ;
SelectObject (hdcMemMask, GetStockObject (BLACK_BRUSH)) ;
Rectangle (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
SelectObject (hdcMemMask, GetStockObject (WHITE_BRUSH)) ;
Ellipse (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
BitBlt (hdcMemImag, 0, 0, cxBitmap, cyBitmap,
hdcMemMask, 0, 0, SRCAND) ;
DeleteDC (hdcMemImag) ;
DeleteDC (hdcMemMask) ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
hdcMemImag = CreateCompatibleDC (hdc) ;
SelectObject (hdcMemImag, hBitmapImag) ;
hdcMemMask = CreateCompatibleDC (hdc) ;
SelectObject (hdcMemMask, hBitmapMask) ;
x = (cxClient - cxBitmap) / 2 ;
y = (cyClient - cyBitmap) / 2 ;
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326) ;
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT) ;
DeleteDC (hdcMemImag) ;
DeleteDC (hdcMemMask) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
DeleteObject (hBitmapImag) ;
DeleteObject (hBitmapMask) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
3,上面的代码虽然不多,但对于初学者却很不好理解,下面对重点部分进行解释。
3.1 ,WM_CREATE部分。BITMASK用LoadBitmap函数在变量hBitmapImag中取得原始图像的句柄,通过GetObject函数将此位图的宽和高赋给bitmap变量,然后将原始位图的句柄选入内存设备环境中。注意内存设备环境和普通dhc主要区别是它只存在于内存中(在其上面画图时,先要在内存设备环境上铺上一张位图(hbitmap)),并不在显示器上显示出来。
3.2 ,程序创建一个和原始图像一样大小的单色位图,并将此选入内存设备环境中。接着就是选画一个这个图一样大的黑色矩形,然后在黑色矩形上画一个白色的椭圆(这是一个单色图,按位图颜色可知,黑色区域是0,白色是1):
3.3 ,通过BitBlt将掩码应用 在原始图像上:
图3.3
注意,为什么把这部分代码放在WM_CREATE中,是因为CreateCompatibleDC函数创建的内存设备环境只涉及到内存中操作,并不显示在界面上,值都是固定的,不用更新。
4,WM_PAINT部分。
这部分代码的主要作用就是将上面图像部分的黑色背景去掉。主要代码就是两个BitBlt函数:
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326) ;
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT) ;
这两行代码主要的意义在于最后这个参数ROP(一共有255种操作,具体见下面的“windows光栅操作”项)。0x220326代表逻辑操作D&~S(目标和取反的源进行按位操作),操作后的图像如下:
图4.1
即画上一个黑色的椭圆。
然后将图4.1和图3.3进行SRCPAINT逻辑操作,源图是图3.3,目标图是4.1,图3.3的外部是黑色的,于是图4.1对应部分被保留。而图4.1的内部是黑色的,所以图3.3对应部分被保留。最终实现效果就是图1.1的效果。
Windows 光栅操作
光栅操作涉及到三个对象的结合,三者的不同组合一共构成了256种操作的结果,也就是有256种三元光栅操作。使用BitBlt、PatBlt和StretchBlt时都会使用到这三钟光栅操作码,即ROP 码,像SRCCOPY ,PATPAINT ,SRCAND 等(只有15种操作有具体的名字)。这256种光栅操作在windows种由四个字节定义,他们都是规定好的。其高位字(high-order word)是布尔操作索引,其低位字(low-order word)是操作码。16位的操作索引从0开始。具体的十六进制值如下所示,你会发现这些编码是从0x00 - 0xFF。
0x000042 ,0x010289 ,0x020C89 ,0x0300AA ,0x040C88 ,0x0500A9 ,0x060865 ,0x0702C5 ,
0x080F08 ,0x090245 ,0x0A0329 ,0x0B0B2A ,0x0C0324 ,0x0D0B25 ,0x0E08A5 ,0x0F0001 ,
0x100C85 ,0x1100A6 ,0x120868 ,0x1302C8 ,0x140869 ,0x1502C9 ,0x165CCA ,0x171D54 ,
0x180D59 ,0x191CC8 ,0x1A06C5 ,0x1B0768 ,0x1C06CA ,0x1D0766 ,0x1E01A5 ,0x1F0385 ,
0x200F09 ,0x210248 ,0x220326 ,0x230B24 ,0x240D55 ,0x251CC5 ,0x2606C8 ,0x271868 ,
0x280369 ,0x2916CA ,0x2A0CC9 ,0x2B1D58 ,0x2C0784 ,0x2D060A ,0x2E064A ,0x2F0E2A ,
0x30032A ,0x310B28 ,0x320688 ,0x330008 ,0x3406C4 ,0x351864 ,0x3601A8 ,0x370388 ,
0x38078A ,0x390604 ,0x3A0644 ,0x3B0E24 ,0x3C004A ,0x3D18A4 ,0x3E1B24 ,0x3F00EA ,
0x400F0A ,0x410249 ,0x420D5D ,0x431CC4 ,0x440328 ,0x450B29 ,0x4606C6 ,0x47076A ,
0x480368 ,0x4916C5 ,0x4A0789 ,0x4B0605 ,0x4C0CC8 ,0x4D1954 ,0x4E0645 ,0x4F0E25 ,
0x500325 ,0x510B26 ,0x5206C9 ,0x530764 ,0x5408A9 ,0x550009 ,0x5601A9 ,0x570389 ,
0x580785 ,0x590609 ,0x5A0049 ,0x5B18A9 ,0x5C0649 ,0x5D0E29 ,0x5E1B29 ,0x5F00E9 ,
0x600365 ,0x6116C6 ,0x620786 ,0x630608 ,0x640788 ,0x650606 ,0x660046 ,0x6718A8 ,
0x6858A6 ,0x690145 ,0x6A01E9 ,0x6B178A ,0x6C01E8 ,0x6D1785 ,0x6E1E28 ,0x6F0C65 ,
0x700CC5 ,0x711D5C ,0x720648 ,0x730E28 ,0x740646 ,0x750E26 ,0x761B28 ,0x7700E6 ,
0x7801E5 ,0x791786 ,0x7A1E29 ,0x7B0C68 ,0x7C1E24 ,0x7D0C69 ,0x7E0955 ,0x7F03C9 ,
0x8003E9 ,0x810975 ,0x820C49 ,0x831E04 ,0x840C48 ,0x851E05 ,0x8617A6 ,0x8701C5 ,
0x8800C6 ,0x891B08 ,0x8A0E06 ,0x8B0666 ,0x8C0E08 ,0x8D0668 ,0x8E1D7C ,0x8F0CE5 ,
0x900C45 ,0x911E08 ,0x9217A9 ,0x9301C4 ,0x9417AA ,0x9501C9 ,0x960169 ,0x97588A ,
0x981888 ,0x990066 ,0x9A0709 ,0x9B07A8 ,0x9C0704 ,0x9D07A6 ,0x9E16E6 ,0x9F0345 ,
0xA000C9 ,0xA11B05 ,0xA20E09 ,0xA30669 ,0xA41885 ,0xA50065 ,0xA60706 ,0xA707A5 ,
0xA803A9 ,0xA90189 ,0xAA0029 ,0xAB0889 ,0xAC0744 ,0xAD06E9 ,0xAE0B06 ,0xAF0229 ,
0xB00E05 ,0xB10665 ,0xB21974 ,0xB30CE8 ,0xB4070A ,0xB507A9 ,0xB616E9 ,0xB70348 ,
0xB8074A ,0xB906E6 ,0xBA0B09 ,0xBB0226 ,0xBC1CE4 ,0xBD0D7D ,0xBE0269 ,0xBF08C9 ,
0xC000CA ,0xC11B04 ,0xC21884 ,0xC3006A ,0xC40E04 ,0xC50664 ,0xC60708 ,0xC707AA ,
0xC803A8 ,0xC90184 ,0xCA0749 ,0xCB06E4 ,0xCC0020 ,0xCD0888 ,0xCE0B08 ,0xCF0224 ,
0xD00E0A ,0xD1066A,0xD20705 ,0xD307A4 ,0xD41D78 ,0xD50CE9 ,0xD616EA ,0xD70349 ,
0xD80745 ,0xD906E8,0xDA1CE9 ,0xDB0D75 ,0xDC0B04 ,0xDD0228 ,0xDE0268 ,0xDF08C8 ,
0xE003A5 ,0xE10185 ,0xE20746 ,0xE306EA ,0xE40748 ,0xE506E5 ,0xE61CE8 ,0xE70D79 ,
0xE81D74 ,0xE95CE6 ,0xEA02E9 ,0xEB0849 ,0xEC02E8 ,0xED0848 ,0xEE0086 ,0xEF0A08 ,
0xF00021 ,0xF10885 ,0xF20B05 ,0xF3022A ,0xF40B0A ,0xF50225 ,0xF60265 ,0xF708C5 ,
0xF802E5 ,0xF90845 ,0xFA0089 ,0xFB0A09 ,0xFC008A ,0xFD0A0A ,0xFE02A9 ,0xFF0062
其实这些光栅操作编码只需要一个字节来定义就够了。如果P是画笔,S是源头像, D是目标位。给出如下的定义:
如果操作的结果总是P,那么定义它为Rop_P = 0xF0 //1 1 1 1 0 0 0 0
如果操作的结果总是S,那么定义它为Rop_S = 0xCC//1 1 0 0 1 1 0 0
如果操作的结果总是D,那么定义它为Rop_D =0xAA//1 0 1 0 1 0 1 0
所有的其它光栅操作都可以由这三个操作推导出来。布尔逻辑操作逻辑与用AND(或&)表示,逻辑或用OR(或 || )表示,逻辑非用NOT(~)表示,逻辑异或用XOR(或^)表示。比如有如下的需求:
如果源位图是1,那么结果是0;如果源位图为1,那么结果是目标位图。
根据这个描述可以得到这个格式:~Rop_S || (~Rop_S && Rop_D) 代码上面的值,可以得到结果0x22。查表可得这个光栅操作的编码为:0x220326。