六、矩形、区域和剪裁—CLOVER程序

要用常规的方法画出这个图形,就必须根据椭圆的边线公式计算出每条直线的端点。利用复杂的剪裁区域,可以直接画出这些线条,而让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。
