六、矩形、区域和剪裁—CLOVER程序
CLOVER程序用四个椭圆组成一个剪裁区域,将这个剪裁区域选进设备内容中,然后画出从窗口显示区域的中心出发的一系列直线,这些直线只出现在剪裁区域所限定的范围,结果显示如图5-20所示。
要用常规的方法画出这个图形,就必须根据椭圆的边线公式计算出每条直线的端点。利用复杂的剪裁区域,可以直接画出这些线条,而让Windows确定其端点。CLOVER如程序5-8所示。
程序5-8 CLOVER CLOVER.C /*-------------------------------------------------------------------------- CLOVER.C -- Clover Drawing Program Using Regions (c) Charles Petzold, 1998 ----------------------------------------------------------------------*/ #include <windows.h> #include <math.h> #define TWO_PI (2.0 * 3.14159) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Clover") ; 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 ("Draw a Clover"), 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 HRGN hRgnClip ; static int cxClient, cyClient ; double fAngle, fRadius ; HCURSORhCursor ; HDC hdc ; HRGN hRgnTemp[6] ; int i ; PAINTSTRUCT ps ; switch (iMsg) { case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; hCursor= SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; if (hRgnClip) DeleteObject (hRgnClip) ; hRgnTemp[0] = CreateEllipticRgn (0, cyClient / 3, cxClient / 2, 2 * cyClient / 3) ; hRgnTemp[1] = CreateEllipticRgn (cxClient / 2, cyClient / 3, cxClient, 2 * cyClient / 3) ; hRgnTemp[2] = CreateEllipticRgn (cxClient / 3, 0, 2 * cxClient / 3, cyClient / 2) ; hRgnTemp[3] = CreateEllipticRgn (cxClient / 3, cyClient / 2, 2 * cxClient / 3, cyClient) ; hRgnTemp[4] = CreateRectRgn (0, 0, 1, 1) ; hRgnTemp[5] = CreateRectRgn (0, 0, 1, 1) ; hRgnClip = CreateRectRgn (0, 0, 1, 1) ; CombineRgn (hRgnTemp[4], hRgnTemp[0], hRgnTemp[1], RGN_OR) ; CombineRgn (hRgnTemp[5], hRgnTemp[2], hRgnTemp[3], RGN_OR) ; CombineRgn (hRgnClip, hRgnTemp[4], hRgnTemp[5], RGN_XOR) ; for (i = 0 ; i < 6 ; i++) DeleteObject (hRgnTemp[i]) ; SetCursor (hCursor) ; ShowCursor (FALSE) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ; SelectClipRgn (hdc, hRgnClip) ; fRadius = _hypot (cxClient / 2.0, cyClient / 2.0) ; for (fAngle = 0.0 ; fAngle < TWO_PI ; fAngle += TWO_PI / 360) { MoveToEx (hdc, 0, 0, NULL) ; LineTo (hdc, (int) ( fRadius * cos (fAngle) + 0.5), (int) (-fRadius * sin (fAngle) + 0.5)) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: DeleteObject (hRgnClip) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }
由于剪裁区域总是使用设备坐标,CLOVER程序必须在每次接收到WM_SIZE消息时重新建立剪裁区域。几年前,这可能需要几秒钟。现在的快速机器在一瞬间就可以画出来。
CLOVER从建立四个椭圆剪裁区域开始,这四个椭圆存放在hRgnTemp数组的头四个元素中,然后建立三个「空」剪裁区域:
hRgnTemp [4]= CreateRectRgn (0, 0, 1, 1) ; hRgnTemp [5]= CreateRectRgn (0, 0, 1, 1) ; hRgnClip = CreateRectRgn (0, 0, 1, 1) ;
显示区域左右的两个椭圆区域组合起来:
CombineRgn (hRgnTemp [4], hRgnTemp [0], hRgnTemp [1], RGN_OR) ;
同样,显示区域上下两个椭圆区域组合起来:
CombineRgn (hRgnTemp [5], hRgnTemp [2], hRgnTemp [3], RGN_OR) ;
最后,两个组合后的区域再组合到hRgnClip中:
CombineRgn (hRgnClip, hRgnTemp [4], hRgnTemp [5], RGN_XOR) ;
RGN_XOR标识符用于从结果区域中排除重迭部分。最后,删除6个临时区域:
for (i = 0 ; i < 6 ; i++) DeleteObject (hRgnTemp [i]) ;
与画出的图形比起来,WM_PAINT的处理很简单。视端口原点设定为显示区域的中心(使画直线更容易一些),在WM_SIZE消息处理期间建立的区域选择为设备内容的剪裁区域:
SetViewportOrg (hdc, xClient / 2, yClient / 2) ; SelectClipRgn (hdc, hRgnClip) ;
现在,剩下的就是画直线了,共360条,每隔一度画一条。每条线的长度为变量fRadius,这是从中心到显示区域的角落的距离:
fRadius = hypot (xClient / 2.0, yClient / 2.0) ; for (fAngle = 0.0 ; fAngle < TWO_PI ; fAngle += TWO_PI / 360) { MoveToEx (hdc, 0, 0, NULL) ; LineTo (hdc, (int) ( fRadius * cos (fAngle) + 0.5), (int) (-fRadius * sin (fAngle) + 0.5)) ; }
在处理WM_DESTROY消息时,删除该剪裁区域:
DeleteObject (hRgnClip) ;
这不是本书关于图形程序设计的最后内容。第十三章讨论打印,第十四章和十五章讨论位图,第十七章讨论文字和字体,第十八章讨论MetaFile。