资源文件中的MATTHEW.BMP文件是我侄子的一幅黑白数字照片,宽200图素,高320图素,每图素8位。不过,另外制作个BITMASK只是因为此文件的内容是任何东西都可以。
注意,BITMASK将窗口背景设为亮灰色。这样就确保我们能正确地屏蔽位图,而不只是将其涂成白色。
下面让我们看一下WM_CREATE的处理程序:BITMASK用LoadBitmap函数获得hBitmapImag变量中原始图像的句柄。用GetObject函数可取得位图的宽度高度。然后将位图句柄选进句柄为hdcMemImag的内存设备内容中。
程序建立的下一个单色位图与原来的图大小相同,其句柄储存在hBitmapMask,并选进句柄为hdcMemMask的内存设备内容中。在内存设备内容中,使用GDI函数,屏蔽位图就涂成了黑色背景和一个白色的椭圆:
SelectObject (hdcMemMask, GetStockObject (BLACK_BRUSH)) ;
Rectangle (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
SelectObject (hdcMemMask, GetStockObject (WHITE_BRUSH)) ;
Ellipse (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
因为这是一个单色的位图,所以黑色区域的位是0,而白色区域的位是1。
然后BitBlt呼叫就按此屏蔽修改了原图像:
BitBlt (hdcMemImag, 0, 0, cxBitmap, cyBitmap,
hdcMemMask, 0, 0, SRCAND) ;
SRCAND位映像操作在来源位(屏蔽位图)和目的位(原图像)之间执行了位AND操作。只要屏蔽位图是白色,就显示目的;只要屏蔽是黑色,则目的就也是黑色。现在原图像中就形成了一个黑色包围的椭圆区域。
现在让我们看一下WM_PAINT处理程序。此程序同时改变了选进内存设备内容中的图像位图和屏蔽位图。两次BitBlt呼叫完成了这个魔术,第一次在窗口上执行屏蔽位图的BitBlt:
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326) ;
这里使用了一个没有名称的位映像操作。逻辑运算子是D & ~S。回忆来源-即屏蔽位图-是黑色(位值0)包围的一个白色(位值1)椭圆。位映像操作首先将来源反色,也就是改成白色包围的黑色椭圆。然后位操作在这 个已转换的来源和目的(即窗口上)之间执行位AND操作。当目的和位值1「AND」时保持不变;与位值0「AND」时,目的将变黑。因此,BitBlt操 作将在窗口上画一个黑色的椭圆。
第二次的BitBlt呼叫则在窗口中绘制图像位图:
BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT) ;
位映像操作在来源和目的之间执行位「OR」操作。由于来源位图的外面是黑色,因此保持目的不变;而在椭圆区域内,目的是黑色,因此图像就原封不动地复制了过来。执行结果如图14-9所示。
注意事项:
有时您需要一个很复杂的屏蔽-例如,抹去原始图像的整个背景。您将需要在画图程序中手工建立然后将其储存到成文件。
如果正在为Windows NT编写类似的应用程序,那么您可以使用与MASKBIT程序类似的MaskBlt函数,而只需要更少的函数呼叫。Windows NT还包括另一个类似BitBlt的函数,Windows 98不支持该函数。此函数是PlgBlt(「平行四边形位块移动:parallelogram blt」)。这个函数可以对图像进行旋转或者倾斜位图图像。
最后,如果在您的机器上执行BITMASK程序,您就只会看见黑色、白色和两个灰色的阴影,这是因为您执行的显示模式是16色或256色。对于16色模式,显示效果无法改进,但在256色模式下可以改变调色盘以显示灰阶。您将在第十六章学会如何设定调色盘。
简单的动画
小张的位图显示起来非常快,因此可以将位图和Windows定时器联合使用,来完成一些基本的动画。
现在开始这个弹球程序。
BOUNCE程序,如程序14-10所示,产生了一个在窗口显示区域弹来弹去的小球。该程序利用定时器来控制小球的行进速度。小球本身是一幅位图, 程序首先通过建立位图来建立小球,将其选进内存设备内容,然后呼叫一些简单的GDI函数。程序用BitBlt从一个内存设备内容将这个位图小球画到显示器 上。
程序14-10 BOUNCE
BOUNCE.C
/*---------------------------------------------------------------------------
BOUNCE.C -- Bouncing Ball Program
(c) Charles Petzold, 1998
----------------------------------------------------------------------------*/
#include <windows.h>
#define ID_TIMER 1
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("Bounce") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox ( NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow ( szAppName, TEXT ("Bouncing Ball"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
static HBITMAP hBitmap ;
static int cxClient, cyClient, xCenter, yCenter, cxTotal, cyTotal,
cxRadius, cyRadius, cxMove, cyMove, xPixel, yPixel ;
HBRUSH hBrush ;
HDC hdc, hdcMem ;
int iScale ;
switch (iMsg)
{
case WM_CREATE:
hdc = GetDC (hwnd) ;
xPixel = GetDeviceCaps (hdc, ASPECTX) ;
yPixel = GetDeviceCaps (hdc, ASPECTY) ;
ReleaseDC (hwnd, hdc) ;
SetTimer (hwnd, ID_TIMER, 50, NULL) ;
return 0 ;
case WM_SIZE:
xCenter = (cxClient = LOWORD (lParam)) / 2 ;
yCenter = (cyClient = HIWORD (lParam)) / 2 ;
iScale = min (cxClient * xPixel, cyClient * yPixel) / 16 ;
cxRadius = iScale / xPixel ;
cyRadius = iScale / yPixel ;
cxMove = max (1, cxRadius / 2) ;
cyMove = max (1, cyRadius / 2) ;
cxTotal = 2 * (cxRadius + cxMove) ;
cyTotal = 2 * (cyRadius + cyMove) ;
if (hBitmap)
DeleteObject (hBitmap) ;
hdc = GetDC (hwnd) ;
hdcMem = CreateCompatibleDC (hdc) ;
hBitmap = CreateCompatibleBitmap (hdc, cxTotal, cyTotal) ;
ReleaseDC (hwnd, hdc) ;
SelectObject (hdcMem, hBitmap) ;
Rectangle (hdcMem, -1, -1, cxTotal + 1, cyTotal + 1) ;
hBrush = CreateHatchBrush (HS_DIAGCROSS, 0L) ;
SelectObject (hdcMem, hBrush) ;
SetBkColor (hdcMem, RGB (255, 0, 255)) ;
Ellipse (hdcMem, cxMove, cyMove, cxTotal - cxMove, cyTotal - cyMove) ;
DeleteDC (hdcMem) ;
DeleteObject (hBrush) ;
return 0 ;
case WM_TIMER:
if (!hBitmap)
break ;
hdc = GetDC (hwnd) ;
hdcMem = CreateCompatibleDC (hdc) ;
SelectObject (hdcMem, hBitmap) ;
BitBlt (hdc, xCenter - cxTotal / 2,
yCenter - cyTotal / 2, cxTotal, cyTotal,
hdcMem, 0, 0, SRCCOPY) ;
ReleaseDC (hwnd, hdc) ;
DeleteDC (hdcMem) ;
xCenter += cxMove ;
yCenter += cyMove ;
if ((xCenter + cxRadius >= cxClient) || (xCenter - cxRadius <= 0))
cxMove = -cxMove ;
if ((yCenter + cyRadius >= cyClient) || (yCenter - cyRadius <= 0))
cyMove = -cyMove ;
return 0 ;
case WM_DESTROY:
if (hBitmap)
DeleteObject (hBitmap) ;
KillTimer (hwnd, ID_TIMER) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
BOUNCE每次收到一个WM_SIZE消息时都重画小球。这就需要与视讯显示器兼容的内存设备内容:
hdcMem = CreateCompatibleDC (hdc) ;
小球的直径设为窗口显示区域高度或宽度中较短者的十六分之一。不过,程序构造的位图却比小球大:从位图中心到位图四个边的距离是小球半径的1.5倍:
hBitmap = CreateCompatibleBitmap (hdc, cxTotal, cyTotal) ;
将位图选进内存设备内容后,整个位图背景设成白色:
Rectangle (hdcMem, -1, -1, xTotal + 1, yTotal + 1) ;
那些不固定的坐标使矩形边框在位图之外着色。一个对角线开口的画刷选进内存设备内容,并将小球画在位图的中央:
Ellipse (hdcMem, xMove, yMove, xTotal - xMove, yTotal - yMove) ;
当小球移动时,小球边界的空白会有效地删除前一时刻的小球图像。在另一个位置重画小球只需在BitBlt呼叫中使用SRCCOPY的ROP代码:
BitBlt (hdc, xCenter - cxTotal / 2, yCenter - cyTotal / 2, cxTotal, cyTotal,
hdcMem, 0, 0, SRCCOPY) ;
BOUNCE程序只是展示了在显示器上移动图像的最简单的方法。在一般情况下,这种方法并不能令人满意。如果您对动画感兴趣,那么除了在来源和目的 之间执行或操作以外,您还应该研究其它的ROP代码(例如SRCINVERT)。其它动画技术包括Windows调色盘(以及 AnimatePalette函数)和CreateDIBSection函数。对于更高级的动画您只好放弃GDI而使用DirectX接口了。
窗口外的位图
SCRAMBLE程序,如程序14-11所示,编写非常粗糙,我本来不应该展示这个程序,但它示范了一些有趣的技术,而且在交换两个显示矩形内容的BitBlt操作的程序中,用内存设备内容作为临时储存空间。
程序14-11 SCRAMBLE
SCRAMBLE.C
/*---------------------------------------------------------------------------
SCRAMBLE.C -- Scramble (and Unscramble) Screen
(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/
#include <windows.h>
#define NUM 300
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static int iKeep [NUM][4] ;
HDC hdcScr, hdcMem ;
int cx, cy ;
HBITMAP hBitmap ;
HWND hwnd ;
int i, j, x1, y1, x2, y2 ;
if (LockWindowUpdate (hwnd = GetDesktopWindow ()))
{
hdcScr = GetDCEx (hwnd, NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE) ;
hdcMem = CreateCompatibleDC (hdcScr) ;
cx = GetSystemMetrics (SM_CXSCREEN) / 10 ;
cy = GetSystemMetrics (SM_CYSCREEN) / 10 ;
hBitmap = CreateCompatibleBitmap (hdcScr, cx, cy) ;
SelectObject (hdcMem, hBitmap) ;
srand ((int) GetCurrentTime ()) ;
for (i = 0 ; i < 2 ; i++)
for (j = 0 ; j < NUM ; j++)
{
if (i == 0)
{
iKeep [j] [0] = x1 = cx * (rand () % 10) ;
iKeep [j] [1] = y1 = cy * (rand () % 10) ;
iKeep [j] [2] = x2 = cx * (rand () % 10) ;
iKeep [j] [3] = y2 = cy * (rand () % 10) ;
}
else
{
x1 = iKeep [NUM - 1 - j] [0] ;
y1 = iKeep [NUM - 1 - j] [1] ;
x2 = iKeep [NUM - 1 - j] [2] ;
y2 = iKeep [NUM - 1 - j] [3] ;
}
BitBlt (hdcMem, 0, 0, cx, cy, hdcScr, x1, y1, SRCCOPY) ;
BitBlt (hdcScr, x1, y1, cx, cy, hdcScr, x2, y2, SRCCOPY) ;
BitBlt (hdcScr, x2, y2, cx, cy, hdcMem, 0, 0, SRCCOPY) ;
Sleep (10) ;
}
DeleteDC (hdcMem) ;
ReleaseDC (hwnd, hdcScr) ;
DeleteObject (hBitmap) ;
LockWindowUpdate (NULL) ;
}
return FALSE ;
}