• 第5章 绘图基础_5.1-5.4 GDI绘图


    5.1 GDI的原理和结构

    (1)提供一种特殊机制彻底隔离应用程序与不同输出设备(eg.显示器或打印机),以便支持 与设备无关的图形。

                    光栅设备(如显示器、激光打印机):图像是由点构成的矩阵

                    图形输出设备   

                    矢量设备(如绘图仪):使用 线条来绘制图形

    (2)Windows GDI允许使用逻辑坐标系统来保证程序与硬件的独立,也可以统用设备坐标系统 (单位:像素)来迎合硬件的需求。

    (3)GDI总体上是一个静态显示系统,对动画的支持有限。DirectX可支持动画。

    5.2 设备环境

    5.2.1 获取设备环境句柄

    (1)在WM_PAINT中获取的是无效区的句柄

       hDC = BeginPaint(hWnd,&ps);

          //其他代码

       EndPaint(hWnd,&ps);

    (2)在非WM_PAINT中

    获取整个客户区DC

    获取整个窗口DC(含非客户区)

    hDC = GetDC(hWnd);

    //GetDC(NULL)时为屏幕DC

    //其他代码

    Release(hWnd,hDC);

    hDC = GetWindowDC(hWnd);

    //其他代码

    Release(hWnd,hDC);

    (3)更通用的方法(未必一定要窗口相关联,也可以是内存或打印机的DC)

      //①整个屏幕DC

      hDC =CreateDC(TEXT(“DISPLAY”),NULL,NULL,NULL);

      DeleteDC(hDC);

      //②内存DC

      hdcMem= CreateCompatibleDC(hDC);

      DeleteDC(hdcMem);

      //③获得图元文件的设备环境句柄

      hdcMeta= CreateMetaFile(pszFileName);

      hmf =CloseMetaFile(hdcMeta);

    (4)只需要获取设备环境信息,而不在其上绘制:CreateIC——(Information Context)

    5.2.2 获取设环境的信息

    (1)设备的分辨率

      ①显示器:每毫米(或英寸)的像素总数。

      ②打印机:每毫米(或英寸)的点数,1磅≈1/72英寸。

    (2)GetDeviceCaps(hdc,iIndex)

    iIndex

    iValue(返回值)

    备注

    HORZRES

    以像素为单位的设备宽度

    1、当hdc为整个屏幕DC时,与GetSystemMetrics获得的信息一致。

    2、当hdc为打印机时,获得的是打机印显示区域的宽度和高度。

    VERTRES

    以像素为单位的设备高度

    HORZSIZE

    以毫米为单位的屏幕宽度

    1、Windows98下:

    HORZSIZE = 25.4×HORZRES/LOGPIXELSX.

    VERTSIZE = 25.4×VERTRES/LOGPIXELSY

    2、Windows NT下:为“标准显示器”的大小,即320×240,但可通过上述公 式算出这两个真实的值出来。

    3、对于打印机是物理尺寸,对于显示器是逻辑尺寸。

    VERTSIZE

    以毫米为单位的屏幕高度

    LOGPIXELSX

    每英寸的水平像素数

    1、对于打印机:可以用上述公式运算出来。

    2、对于显示器:与Windows设置的字体有关:正常字体96dpi,大字体为120dpi

    LOGPIXELSX

    每英寸的垂直像素数

    5.2.3 关于GetDeviceCaps的扩展学习

    (1)屏幕物理分辨率与屏幕分辨率的区别

      ①物理分辨率:屏幕的最高分辨率,显示器一生产出来,就固定下来了。

      ②屏幕像素规模(HORZRES和VERTRES)与屏幕分辨率(屏幕dpi)

           A、屏幕像素规模:如1024×768

           B、屏幕dpi = 25.4×HORZRES/HORZSIZE或 25.4×VERTRES/VERTSIZE

      屏幕dpi是可改变。降低像素规模时(如1024×768改成800×600),dpi可能会变化,也可能不变。经测试,台式机和笔记本上dpi变化有所不同。

    显示效果

    说明

    笔记本

    LED显示器

    屏幕显示区域缩窄,但像素大小不变

    同时影响HORZRES和HORZSIZE, 但dpi不变

    台式机

    CRT显示器

    屏幕显示区域不变,但像素增大

    (LED某些分辨率下也会出现)

    只影响HORZRES,屏幕dpi变大

      ③下面以台式机CRT显示器为例,讨论屏幕dpi变化的情况下,显示的图像大小变化:

      如15.6英寸(13.5×7.6)在全屏幕模式下,当设置为1366×768像素时,dpi 为100。当设置683×384下,dpi为50。明显,屏幕上每个像素大小,第2种情况应比第1种大 1倍,因为GPU会把屏幕上的4个像素当1个像素(2×2)用。这就导致了显示的图像变大、变 模糊了。当然,如果是在笔记本显示器下测试,因dpi不变,显示出来的图像大小也就不变了。因 此当改变了屏幕像素规模时,一定要看下屏幕dpi是否变化,才能判断出显示出来的图像大小是否 有变化。

    (2)逻辑dpi(=LOGPIXELSX或LOGPIXELSY),Windows下默认为96dpi。

      ①为什么要使用逻辑dpi,而不直接使用屏幕实际的分辨率

      因为早期显示器并不存储物理尺寸等信息,所以Windows无法获取显示器的真实尺寸,也 就没办法得到屏幕分辨率(dpi),这将导致windows在输出时,不知该将1英寸长度的物体,转为 多少个像素输出。所以只能采用市面上常见的显示器的分辨率,硬性规定一个默认值(如Windows 下为96dpi),如此便可以将1英寸长度转化为96个像素输出(注意,这时屏幕上看到的那段长度 不一定就是严格的1英寸长,因为96个像素被输往屏幕后,要根据屏幕上每个像素的实际大小来算 出显示的实际长度,而屏幕像素大小要通过其dpi计算得到)。现在由于EDID技术,将物理尺寸等 信息直接存进显示器,所以将物体按屏幕实际dpi(不是逻辑dpi)显示将成为一种趋势,因为这 可以让我们的显示器按照物体的真实尺寸来显示的。

      ②逻辑dpi的含义:将1英寸的长度转化为相应的像素,以这样的像素量输出。

      如:10磅字体,在逻辑96dpi下将被转化为10/72*96,即13个像素输出;在120dpi下将被 转化为16个像素输出。至于在屏幕上这96个像素(或120)显示出来的实际大小,要根据屏幕dpi 去算出来。通常,在屏幕dpi相等的情况下(如1024×768,设dpi为100),逻辑dpi为120时 显示出来的字要更大,因为13<16。这两种字体在屏幕上的实际长度分别为13/100英寸和 16/100英寸。

      ③改变屏幕dpi与改变逻辑dpi的不同。

    实验:将96px×96px,物理尺寸为1×1英寸蓝色方块输往屏幕

    A、将屏幕dpi由200dpi→100dpi

    (如1600×1200→800×600,假设这两种情况都是全屏下。否则,这时windows 会调整桌面大小,而让屏幕dpi保持不变)

    改变了屏幕dpi,这时意味着屏幕上每个像素变大了,所以显示出来方块会比实际的1×1 英寸大。

    B、将逻辑dpi由96→120。

    (在相同屏幕dpi下,如1024×768下,只调整逻辑dpi的值)

    1、注意这时屏幕像素大小并没变,因为屏幕dpi没变化。

    2、改变了逻辑dpi,意味着要将1英寸长度转为120个像素输出。注意到了吗,Windows偷偷地 将方块像素比增加了,从96px×96px方块调大成120px×120px方块输出。所以,显示 出来的图形也就比原来的更大了。

    5.2.4 色彩ABC

      #define RGB(r,g,b) ((COLORREF)(((BYTE)(r))|

               ((WORD)((BYTE)(g))<<8))|

                                   (((DWORD)(BYTE)(b))<<16)))

    5.2.5 保存设备环境属性

    (1)GetDC或BeginPaint返回的设备环境属性值是默认的。ReleaseDC或EndPaint后,所做的 任何改变都会丢失。

    (2)如何解决?

      ①注册窗口类时wndClass.style=CS_HREDRAW | CS_VREDRAW | CS_OWNDC,这样这样基于 这个窗口类创建的窗口都有它私有的设备环境。CS_OWNDC只影响通过GETDC和BeginPaint获得的DC ,通过其他函数(如GetWindowDC)获得的设备环境并不受影响。

      ②在WM_CREATE消息初始化设备环境的属性

      case WM_CREATE:

        hdc = GetDC(hWnd);

        //初始化各个属性

        ReleaseDC(hWnd,hdc);

    (3)保存与恢复

      idSaved= SaveDC(hdc); //可多次save,并保存在不同的id中。

      RestoreDC(hdc,idSaved);//RestoreDC(hdc,-1)为恢复到最近一次保存的状态。

    5.3 点和线的绘制

    5.3.1 设定像素

      SetPixel(hdc,x,y,crColor);

      COLORREF crColor = GetPixel(hdc,x,y);

    5.3.2 直线

    (1)MoveTo和MoveToEx

      MoveTo返回值是DWORD型,用于表示运行函数前的当前位置(x,y)——早期 windows。而现在的坐标(x,y)都为32位的,所以该函数不能现在的windows中返回正确的坐标。

      MoveToEx返回值BOOL。MoveToEX(hdc,x,y,&pt),第三个参数为运行该函数前的当前 位置。不需要为NULL。即MoveToEX(hdc,x,y,NULL)——现在Windows中。

    (2)获取当前位置:GetCurrentPosition(hdc,&pt)

    (3)LineTo、PolyLineTo等带To的函数会把最后一次的终点设为当前位置。

    (4)PolyLine和PolyLineTo的区别

    POINT apt[5] ={100,100,200,100,200,200,100,200,100,100} //首尾点相同
    int iArrayLen = sizeof(apt)/sizeof(POINT);
     
    //用
    
    MoveToEx和LineTo绘制
    MoveToEx(hdc,apt[0].x,apt[0].y,NULL)
    for(int i=1;i<iArrayLen;i++)
    {
       LineTo(hdc,apt[i].x,apt[i].y);
    } 
    
    //用
    
    PolyLine绘制,并不改变当前位置。
    PolyLine(hdc,apt,iArrayLen);//最后一个参数表示要绘制的点的个数。 
    
    //用
    
    PolyLineTo绘制,要将当前位置作为起点,绘制完后,会改变当前位置。
    MoveToEx(hdc,apt[0].x,apt[0].y,NULL);//从当前绘制为起点
    PolyLine(hdc,apt+1,iArrayLen-1); //画下后面的点。

    【SINWAVE程序】

    /*------------------------------------------------------------
    SINEWAVE.C -- Sine Wave Using Polyline
    (c) Charles Petzold, 1998
    ------------------------------------------------------------*/
    #include <windows.h>
    #include <math.h>
    
    #define NUM 1000
    #define TWOPI (2*3.14159)
    
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    
    int WINAPI 
    
    WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT("SinWave");
        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,                  // window class name
            TEXT("SinWave"), // window caption
            WS_OVERLAPPEDWINDOW,        // window style
            CW_USEDEFAULT,              // initial x position
            CW_USEDEFAULT,              // initial y position
            CW_USEDEFAULT,              // initial x size
            CW_USEDEFAULT,              // initial y size
            NULL,                       // parent window handle
            NULL,                       // window menu handle
            hInstance,                  // program instance handle
            NULL);                     // creation parameters
    
        ShowWindow(hwnd, iCmdShow);
        UpdateWindow(hwnd);
    
        while (GetMessage(&msg, NULL, 
    
    0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return 
    
    msg.wParam;
    }
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC         hdc;
        PAINTSTRUCT ps;
        static int cxClient, cyClient;
        POINT  apt[NUM];
        switch 
    
    (message)
        {
        case 
    
    WM_CREATE:
    
            return 0;
        case 
    
    WM_SIZE:
            cxClient = LOWORD(lParam);
            cyClient = HIWORD(lParam);
            return 0;
        case 
    
    WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
    
            //
    
    画x轴
            MoveToEx(hdc, 0, cyClient / 2, NULL);
            LineTo(hdc, cxClient, cyClient / 2);
    
            //
    
    计算坐标
            for (int i = 0; i < NUM; i++)
            {
                apt[i].x = i * cxClient / NUM;
                apt[i].y = (int)(cyClient / 
    
    2 * (1 - 
    
    sin(TWOPI * i / NUM)));
            }
            //
    
    画正弦曲线
            Polyline(hdc, apt, NUM); //调用一次且在设备驱动程序层面上,比调用1000次LineTo要快很多
    
    
    
            EndPaint(hwnd, &ps);
            return 0;
    
        case 
    
    WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return 
    
    DefWindowProc(hwnd, message, wParam, lParam);
    }

     5.3.3 边框绘制函数

    (1)边界偏差(off-by-one):Windows在“边框”内绘图,即只画右坐标与底坐 标之前的点。即不包含右坐标与底坐标的点。

    (2)图解边框绘图函数 

     

    【LineDraw】

    /*------------------------------------------------------------
    LINEDRAW.C -- Line-Draw Demonstration Program
    (c) Charles Petzold, 1998
    ------------------------------------------------------------*/
    #include <windows.h>
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    int WINAPI 
    
    WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT("LineDemo");
        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,                  // window class name
            TEXT("LineDraw Demo"), // window caption
            WS_OVERLAPPEDWINDOW,        // window style
            CW_USEDEFAULT,              // initial x position
            CW_USEDEFAULT,              // initial y position
            CW_USEDEFAULT,              // initial x size
            CW_USEDEFAULT,              // initial y size
            NULL,                       // parent window handle
            NULL,                       // window menu handle
            hInstance,                  // program instance handle
            NULL);                     // creation parameters
    
        ShowWindow(hwnd, iCmdShow);
        UpdateWindow(hwnd);
    
        while (GetMessage(&msg, NULL, 
    
    0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return 
    
    msg.wParam;
    }
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC         hdc;
        PAINTSTRUCT ps;
        static int cxClient, cyClient;
        switch 
    
    (message)
        {
        case 
    
    WM_CREATE:
    
            return 0;
        case 
    
    WM_SIZE:
            cxClient = LOWORD(lParam);
            cyClient = HIWORD(lParam);
            return 0;
        case 
    
    WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
    
            //
            Rectangle(hdc, cxClient / 8, 
    
    cyClient / 8, 7 * cxClient / 8, 7 * cyClient / 8);
    
            //
    
    画对角线
            MoveToEx(hdc, 0, 0, NULL);
            LineTo(hdc, cxClient, cyClient);
            MoveToEx(hdc, cxClient, 0, NULL);
            LineTo(hdc, 0, cyClient);
            Ellipse(hdc, cxClient / 8, 
    
    cyClient / 8, 7 * cxClient / 8, 7 * cyClient / 8);
            RoundRect(hdc, cxClient / 4, 
    
    cyClient / 4, 3 * cxClient / 4, 3 * cyClient / 4, 
    
    cxClient / 4, cyClient / 4);
    
            EndPaint(hwnd, &ps);
            return 0;
    
        case 
    
    WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return 
    
    DefWindowProc(hwnd, message, wParam, lParam);
    }

    5.3.4 贝塞尔样条曲线

    (1)PolyBezier(hdc,apt,iCount)

       ①apt是个POINT结构的数组,前四点分别表示曲线起点、第一个控制点、 第二个控点、第二个点。随后的每一条贝塞尔样条曲线则只需要给出三个点,因为前一条贝塞尔 样条曲线的终点就是后一条的起点

    ②iCount=3*曲线的条数+1;

    (2)PolyBezierTo函数把当前位置作为第一个起点,所以后面每画一条曲线只需再给3个点。 当函数返回时,把曲线终点设置为当前位置。

    【Bezier】

    /*------------------------------------------------------------
    LINEDRAW.C -- Bezier Splines Demo
    (c) Charles Petzold, 1998
    ------------------------------------------------------------*/
    #include <windows.h>
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    int WINAPI 
    
    WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT("Bezier");
        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,                  // window class name
            TEXT("Bezier"), // window caption
            WS_OVERLAPPEDWINDOW,        // window style
            CW_USEDEFAULT,              // initial x position
            CW_USEDEFAULT,              // initial y position
            CW_USEDEFAULT,              // initial x size
            CW_USEDEFAULT,              // initial y size
            NULL,                       // parent window handle
            NULL,                       // window menu handle
            hInstance,                  // program instance handle
            NULL);                     // creation parameters
    
        ShowWindow(hwnd, iCmdShow);
        UpdateWindow(hwnd);
    
        while (GetMessage(&msg, NULL, 
    
    0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return 
    
    msg.wParam;
    }
    void 
    
    DrawBezier(HDC hdc, POINT apt[])
    {
        //画贝
    
    塞尔曲线
        PolyBezier(hdc, apt, 4);
        //画控
    
    制线
        MoveToEx(hdc, apt[0].x, apt[0].y, NULL);
        LineTo(hdc, apt[1].x, apt[1].y);
        MoveToEx(hdc, apt[3].x, apt[3].y, NULL);
        LineTo(hdc, apt[2].x, apt[2].y);
    }
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC         hdc;
        PAINTSTRUCT ps;
        static int cxClient, cyClient;
        static POINT apt[4];
        switch 
    
    (message)
        {
        case 
    
    WM_CREATE:
    
            return 0;
        case 
    
    WM_SIZE:
            cxClient = LOWORD(lParam);
            cyClient = HIWORD(lParam);
            apt[0].x = cxClient / 4;
            apt[0].y = cyClient / 2;
            apt[1].x = cxClient / 2;
            apt[1].y = cyClient / 4;
            apt[2].x = cxClient / 2;
            apt[2].y = 3 * cyClient / 4;
            apt[3].x = 3 * cxClient / 4;
            apt[3].y = cyClient / 2;
            return 0;
        case WM_MOUSEMOVE:     //鼠标移动时
        case  WM_LBUTTONDOWN:  //左键按下时
        case WM_RBUTTONDOWN:   //右键按下时
            if (wParam & MK_LBUTTON || wParam 
    
    &MK_RBUTTON)
            {
                hdc = GetDC(hwnd);
                SelectObject(hdc, GetStockObject(WHITE_PEN));
                DrawBezier(hdc, apt); //用白色画刷重绘一遍,即擦除旧的曲线
                if (wParam & MK_LBUTTON)  //左键改变第1个控制点
    
    
                {
                    apt[1].x = LOWORD(lParam);
                    apt[1].y = HIWORD(lParam);
                }
                if (wParam & MK_RBUTTON)  
    
    //右键改变第2个控
    
    制点
                {
                    apt[2].x = LOWORD(lParam);
                    apt[2].y = HIWORD(lParam);
                }
                SelectObject(hdc, GetStockObject(BLACK_PEN));
                DrawBezier(hdc, apt);
    
                ReleaseDC(hwnd, hdc);
            }
            return 0;
        case 
    
    WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
    
            DrawBezier(hdc, apt);
            EndPaint(hwnd, &ps);
            return 0;
    
        case 
    
    WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return 
    
    DefWindowProc(hwnd, message, wParam, lParam);
    }

    5.3.5 使用现有画笔(备用画笔——画笔宽度总 是为1)

    (1)获取画笔句柄——3种默认画笔(WHITE_PEN、BLACK_PEN、NULL_PEN),其中 NULL_PEN表示不绘任何图形。比如,画矩形时只要填充区域,不要边框时,可用NULL_PEN。

      HPEN hPen= GetStockObject(WHITE_PEN);

    (2)选入设备环境:

      HPEN hOldPen = SelectObject(hdc, (HGDIOBJ)hPen);

      HPEN hOldPen = SelectObject(hdc, GetStockObject(WHITE_PEN));

    5.3.6 创建、选择和删除画笔

    (1)使用GDI对象的规则

       ①当GDI对象被选入一个有效的设备环境时,不要删除它

    ②最终应删除你所创建的所有GDI对象

    ③不要删除备用库里的对象

    HPEN hPen = CreatePen(PS_DASH, 1, RGB(255, 0, 0));//创建画笔

    HPEN hOldPen = SelectObject(hdc, (HGDIOBJ)hPen); //选入画笔

    //画图代码

    SelectObject(hdc, (HGDIOBJ)hOldPen);   //恢复旧画笔

    DeleteObject((HGDIOBJ)hPen); //删除画笔,上一行不能省略。

    (2)hPen = CreatePen(iPenStyle,iWidth,crColor);

    iPenStyle参数

    iWidth

    crColor


     

    ①当iWidth =0时,Windows会重设为1个像素

    ②对于PS_SOLID、PS_NULL、PS_INSIDEFRAME,iWidth表示画笔的宽度

    ③如果是虚线或点线,当iWidth>1时,会用实心画笔代替。

    ①设备画笔颜色,是COLORREF值。

    ②PS_INSIDEFRAME唯一能使用混合色的画笔样式,但要iWidth>1。

    (3)hPen = CreatePenIndirect(&logpen);

    LOGPEN结构(LOGPEN logpen)

    lopnStyle

    画笔样式

    lopnWidth

    画笔宽度,是一个POINT结构,x字段画笔的宽度,y字段被忽略

    lopnColor

    画笔颜色,COLORREF值。

    (4)删除未被保存句柄的设备环境中的画笔——不能删除被选入设备环境句柄的 画笔!

      ① 方法1://选入默认画笔,返回的是设备中的画笔,再删除。

        DeleteObject(SelectObject(hdc,GetStockObject(BLACK_PEN)));

      ②方法2:

        hPen = SelectObject(hdc,CreatePen(PS_DASH),0,RGB(255,0,0));//自定义画笔

        DeleteObject(SelectObject(hdc,hPen));//返回自定义画笔句柄,并删除。

    (5)获取画笔

      ①hPen =GetCurrentObject(hdc,OBJ_PEN); //获得当前设备环境中的画笔句柄

      ②GetObject(hPen,sizeof(LOGPEN),(LPVOID)&logpen);//画笔属性保存logpen中。  

    5.3.7 填充空隙——点式画笔或虚线画笔之间空 隙的颜色

    空隙颜色由设备环境的背景模式和背景颜色决定(注意不是窗口的背景颜色 )

    背景模式

    OPAQUE(不透明)

    TRANSPARENT(透明)

    SetBkMode(hdc,TRANSPARENT);

    int iMode = GetBkMode (hdc);

    背景颜色

    COLORREF值

    SetBkColor(hdc,crColor);

    COLORREF crColor = GetBkColor(hdc);

    5.3.8 绘图模式(默认R2_COPYPEN)

    (1)线条最终显示的颜色由画笔颜色及目标区域表面的颜色共同决定。

    (2)光栅操作(rasteroperation,ROP):画笔的像素颜色与目标表面像素按位布尔运算。因 画线只涉及两种像素颜色(画笔与目标),也被称为二元光栅操作(ROP2)。

    (3)16种不同的ROP2运算:如R2_COPYPEN、R2_MASKNOTPEN、R2_BLACK等。

    (4)获取当前绘制模式和设置绘图模式:

      ①iDrawMode =GetROP2(hdc);

      ②SetROP2(hdc,iDrawMode);

    5.4 绘制填充

    5.4.1 画刷

    (1)备用库画刷:6种(WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DKGRAY_BRUSH、 BLACK_BRUSH和NULL_BRUSH)。

    (2)使用画刷

        HBRUSHhBrush = GetStockObject(GRAY_BRUSH); //获取画刷

        SelectObject(hdc,hBrush);  //选入设备环境中

    (3)应用举例——画矩形

    不要边框线,只要填充

    SelectObject(hdc,GetStockObject(NULL_PEN));

    只绘边框,不要填充内部

    SelectObject(hdc,GetStockObject(NULL_BRUSH));

    5.4.2 Polygon函数和多边形填充模式

    (1)Polygon和PolyPolygon函数

      ① Polygon(hdc,apt,iCount);

    apt

    POINT结构的数组,表示各个顶点

    若最后一个点与第一个点不同,Windows会自动加1条连接这两个点的线,以形成闭合区域

    iCount

    顶点的个数

      ②PolyPolygon(hdc, ,aiCounts,iPolyCount);

    apt

    POINT结构的数组,表示所有的顶点

    aiCounts

    是个数组,每个元素表示1个多边形的顶点数。

    iPolyCount

    多边形的个数

      ③PolyPolygon的功能等价代码

    for(int i=0,iAccum =0;i<iPolycount;i++)

    {

         Polygon(hdc,apt+iAccum,aiCounts[i]); //第i个多边形;

         iAccum +=aiCounts[i];  //绘完第i个多边形后,顶点索引 后移;

    }

    (2)图解多边形的填充模式:ALTERNATE(交替)和WINDING(螺旋)

      SetPolyFillMode(hdc,iMode);

     【ALTWIND程序】
    1、特征图形与填充效果图


    效果图
    2、代码实现
    /*------------------------------------------------------------
    ALTWIND.C -- Alternate and Winding Fill Modes
    (c) Charles Petzold, 1998
    ------------------------------------------------------------*/
    #include <windows.h>
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
    int WINAPI 
    
    WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        PSTR szCmdLine, int iCmdShow)
    {
        static TCHAR szAppName[] = TEXT("AltWind");
        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,                  // window class name
            TEXT("Alternate And Winding Fill Modes"), // window caption
            WS_OVERLAPPEDWINDOW,        // window style
            CW_USEDEFAULT,              // initial x position
            CW_USEDEFAULT,              // initial y position
            CW_USEDEFAULT,              // initial x size
            CW_USEDEFAULT,              // initial y size
            NULL,                       // parent window handle
            NULL,                       // window menu handle
            hInstance,                  // program instance handle
            NULL);                     // creation parameters
    
        ShowWindow(hwnd, iCmdShow);
        UpdateWindow(hwnd);
    
        while (GetMessage(&msg, NULL, 
    
    0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        return 
    
    msg.wParam;
    }
    LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        HDC         hdc;
        PAINTSTRUCT ps;
        static int cxClient, cyClient;
        static POINT aptFigure[10] = { 10, 70, 50, 70, 50, 10, 90, 10, 90, 50,
            30, 50, 30, 90, 70, 90, 70, 30, 10, 30 }; //特征图形
        POINT apt[10];
        switch 
    
    (message)
        {
        case 
    
    WM_SIZE:
            cxClient = LOWORD(lParam);
            cyClient = HIWORD(lParam);
            return 0;
        case 
    
    WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);
            SelectObject(hdc, GetStockObject(GRAY_BRUSH));
            //
    
    左幅图ALTERNATE模式填充
            for (int i = 0; i < 10; i++)
            {
                apt[i].x = cxClient * aptFigure[i].x / 200;
                apt[i].y = cyClient * aptFigure[i].y / 100; //按比例放大,因为apt[i].y /cylient = aptFigure[i].y / 100;
            }
            SetPolyFillMode(hdc, ALTERNATE);
            Polygon(hdc, apt, 10);
            //
    
    右幅图ALTERNATE模式填充
            for (int i = 0; i < 10; i++)
            {
                apt[i].x += cxClient / 2;
            }
            SetPolyFillMode(hdc, WINDING);
            Polygon(hdc, apt, 10);
            EndPaint(hwnd, &ps);
            return 0;
    
        case 
    
    WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return 
    
    DefWindowProc(hwnd, message, wParam, lParam);
    }

    5.4.3 用画刷(8×8的小位图)填充内部

    (1)创建逻辑画刷hBrush =CreateSolidBrush(crColor); //也可能是一个抖动位图

    (2)阴影线标记的画刷hBrush = CreateHatchBrush(iHatchStyle,crColor);

    iHatchStyle表示阴影线标记的外观

    说明:1、阴影线颜色由crColor指定。

          2、阴影线之间的空隙由背景模式和背影颜色决定,由画笔 类似。

    (3)用自己的位图画刷:CreatePatternBrush和CreateDIBPatternBrushPt。

    (4)包含其他4个函数功能的(更强大):hBrush = CreateBrushIndirect (&logbrush);

    LOGBRUSH结构体(三个字段,lbStyle 字段的值决定Windows如何解释其他两个字段)

    lbStyle(UINT)

    lbColor(COLORREF)

    lbHatch(LONG)

    BS_SOLID

    画刷的颜色

    被忽略

    BS_HOLLOW

    被忽略

    被忽略

    BS_HATCHED

    阴影线的颜色

    阴影线画刷的样式

    BS_PATTERN

    被忽略

    位图的句柄

    BS_DIBPATTERNPT

    被忽略

    指向DIB的指针

    (5)获取画刷信息GetObject(hBrush,sizeof(LOGBRUSH), (LPVOID)&logbrush); 

  • 相关阅读:
    Python之迭代器,生成器
    Python函数--装饰器进阶
    Python之函数的本质、闭包、装饰器
    Python之函数--命名空间、作用域、global、nonlocal、函数的嵌套和作用域链
    Python函数的定义与调用、返回值、参数
    Python之文件操作
    Python之集合
    基本数据类型补充,深浅copy
    Python基础-元组、列表、字典
    Python常用模块(一)
  • 原文地址:https://www.cnblogs.com/5iedu/p/4621235.html
Copyright © 2020-2023  润新知