代码改变世界

C语言Windows程序设计 -> 第九天 -> GDI绘图基础

2012-11-07 14:09  wid  阅读(14494)  评论(4编辑  收藏  举报

 

•GDI介绍
    GDI(Graphics Device Interface), 图形设备接口。
    
    GDI的作用:
        负责系统与绘图程序之间的信息交换,处理Windows程序的图形输出。
        
    GDI的特点:
         1>. 开发者无需关心物理硬件的设备类型;
      2>. 不允许应用程序直接访问物理显示硬件, 通过获取显示设备的"设备环境"句柄实现与显示硬件的间接通信;
      3>. 程序与物理显示设备进行通信时, 必须首先获得与特定窗口相关联的设备环境;
      4>. Windows根据设备环境的数据结构完成信息的输出。
    


在第六天的学习中学习了以上的内容, 这次将较为详细的介绍下GDI相关的知识。
    
  GDI是由许多的函数组成的一套完善的图形输出系统, 这些函数大约有600个左右, 主要负责系统与外部硬件的交互工作, 例如, 显示器上的图形、文字的输出, 打印机的打印工作等。应用程序开发者可以调用这些函数完成应用程序在屏幕上的信息显示、图形的绘制等工作, 使用GDI的一大优点就是可以做到无需关心用户PC的显示器等输出硬件的实际类型, 只需要调用这些GDI函数告诉系统将要输出就可以了, 系统会根据不同硬件中的设备驱动程序中的函数, 驱动函数再将GDI命令转换为该硬件设备能够理解的代码或者命令完成输出, 避免的应用程序直接与硬件打交道, 降低了应用程序开发的难度, 同时也避免了输出过程中的各种繁琐的情况。
    
  不仅如此, Windows自身也使用GDI来显示相关的图形, 比如最常见的菜单、滚动条、应用程序图标、鼠标指针等, 但是需要注意的是, GDI只是一套静态显示系统, 对动态快速频繁的输出, 例如较为复杂的游戏、视频的播放等就显得力不从心了, 编写复杂的动画输出请参考微软的DirectX(多媒体编程接口)或OpenGL(一个专业的跨平台图形程序接口)。
    
  这些GDI函数主要来自动态链接库的GDI32.dll文件中, 通过将dll中的函数导出对图形进行处理, 要查看这些函数的具体名称可以从WINGDI.H头文件中寻找, 或者直接通过命令将GDI32.dll中的函数名称导出:
  通过命令提示符进入到system32文件夹, 执行:

    Dumpbin -exports gdi32.dll

  这样就可以在命令提示符窗口中看到相关的信息了, 不过在命令提示符窗口中是无法显示完全的, 要全部显示可以将信息输出到文本中, 命令如下:

    Dumpbin -exports gdi32.dll >> out.txt

  这样dll中的信息就会被导出到out.txt文件中。
    
  在system32文件夹中同样还有一个GDI.exe文件, 它的作用是负责16位程序的图像输出。
    
  在诸多的GDI函数中从功能上来说实际上可以主要分为以下5种类型:
        1>. 获取、释放设备环境的函数;
        2>. 获取设备环境信息的函数;
        3>. 绘图函数;
        4>. 设置、获取设备环境属性的函数;
        5>. 使用GDI"对象"的函数;
        
  在正式开始学习绘图之前, 还应该先了解下在屏幕上绘图的相关流程, 首先, 获取设备环境的句柄, 当获取成功时就意味着你的应用程序有了在屏幕上绘图的权限, 然后你就可以调用GDI中的绘图函数通过设备环境句柄对屏幕进行绘制, 等绘制结束后你应该释放这个句柄。

 

1. 获取设备环境句柄
    Windows提供了许多种方法用来获取不同的设备环境句柄, 这里不能一次性讲全, 当前我们需要使用的主要有以下几种:
    
    1>. 使用BeginPaint函数和EndPaint函数:
        BeainPaint函数原型:

        HDC BeginPaint(
          HWND hwnd,                 // 窗口的句柄
          LPPAINTSTRUCT lpPaint     // 绘制信息
      );

 

        参数二为PAINTSTRUCT类型的结构, 函数的返回值就是设备环境句柄, PAINTSTRUCT结构定义在WINUSER.H头文件中, 如下:

        typedef struct tagPAINTSTRUCT
        {
            HDC         hdc;
            BOOL        fErase;
            RECT        rcPaint;
            BOOL        fRestore;
            BOOL        fIncUpdate;
            BYTE        rgbReserved[32];
        } PAINTSTRUCT ;

        在该结构中的第一个成员HDC的值正是设备环境句柄, 函数返回的设备环境句柄也正是来源于此。
        
        通过BeginPaint函数来获取设备环境句柄通常用于处理WM_PAINT消息时, 一般的使用结构:

        hdc = BeginPaint( hwnd, &ps ) ;
            [相关的处理语句]
        EndPaint( hwnd, &ps ) ;            //释放设备环境句柄

 


    2>. 使用GetDC函数
            GetDC函数可以用来获取制定窗口句柄的设备环境句柄, 同时也可以获取整个屏幕的设备环境句柄, 其函数原型为:

            HDC GetDC( HWND hWnd ) ;        //函数参数为窗口句柄        

            当函数调用成功时返回设备环境的句柄, 失败返回NULL, 当函数参数为NULL时返回的就是整个屏幕的设备环境句柄。


            当设备环境句柄使用完成后应该及时使用ReleaseDC函数释放该设备环境句柄, 值得一提的是, GetDC和ReleaseDC函数不能使无效的客户区变成有效, 当使用GetDC方式重绘完成后可以显性调用ValidateRect函数使其有效, ValidateRect原型:

    BOOL ValidateRect(
       HWND hWnd,              // 窗口的句柄
       CONST RECT *lpRect      // 指向RECT结构的指针
    ) ;

            参数一为被有效化的窗口句柄, 若该参数为NULL, 系统将更新所有的窗口; 参数二为一个包含需要生效的矩形的更新区域坐标的RECT结构体, 当参数为NULL时, 整个客户区都将有效化。
            
            使用的一般形式:

    hdc = GetDC( hwnd ) ;
      [相关的处理语句]
    ReleaseDC( hwnd, hdc ) ;

 


    3>. 使用GetWindowDC
            与GetDC不同, GetDC可以用来获取窗口的客户区部分的设备环境句柄, 而GetWindowDC用来获取整个窗口的设备环境句柄, 整个窗口是指包括窗口的标题栏、菜单栏、滚动条、状态栏以及客户区和客户区的外缘边框部分, 函数原型:

    HDC GetWindowDC( HWND hWnd // 窗口句柄 );

            在使用完成后同样要使用ReleaseDC对设备环境句柄进行释放, 使用的一般形式:

    hdc = GetWindowDC( hwnd ) ;
       [相关的处理语句]
    ReleaseDC( hwnd, hdc ) ;


    4>. 使用CreateDC
        CreateDC的作用是通过使用指定的名字为一个设备创建一个设备环境句柄, 在使用完成后应当由DeleteDC函数进行删除释放, 而不是ReleaseDC。
        函数原型:

        HDC CreateDC(
            LPCTSTR lpszDriver,
            LPCTSTR lpszDevice,
            LPCTSTR lpszOutput,
            const DEVMODE *lpInitData
        );

 

        参数一LPCTSTR lpszDriver指向一个以NULL结尾的字符串的指针, 当字符串为TEXT("DISPLAY")时,是获取整个屏幕的设备环境, 为TEXT("WINSPOOL")则是访问打印驱动的设备环境;
            注意: 当参数为TEXT("WINSPOOL")时其他参数均为NULL。
            
        参数二LPCTSTR lpszDevice指向一个以null结尾的字符串的指针, 该字符串指定了正在使用的特定输出设备的名字;
        
        参数三LPCTSTR lpszOutput在32位环境下通常被忽略, 并置为NULL, 该参数的存在主要是为了提供与16位应用程序兼容;
        
        参数四const DEVMODE *lpInitData指向包含设备驱动程序的设备指定初始化数据的DEVMODE结构的指针, DEVMODE数据结构中包含了有关设备初始化和打印机环境的信息 , 如果设备驱动程序使用用户指定的缺省初始化值。则lplnitData参数必须为NULL。
        
        对于这个函数可能暂时有点难以理解, 不过暂时也用不到他, 现在只需要记住一条用法:

        hdc = CreateDC( TEXT( "DISPLAY" ), NULL, NULL, NULL ) ;        //获取当前整个屏幕的设备环境句柄

 

        在使用完成后记住要使用DeeteDC进行释放, 而不是ReleaseDC。
        

2. 获取设备环境信息
    获取设备环境信息, 也成属性, 通常是指物理硬件的的某些信息, 比如显示器的分辨率、尺寸、色彩能力等, 要获取设备环境的信息首先要得到设备环境句柄, 然后通过GetDeviceCaps函数获取, GetDeviceCaps的原型:

    int GetDeviceCaps(
        HDC hdc,
        int nIndex
    );

 

    参数nIndex用来指明需要获取的信息类型, 例如当参数为HORZRES时, 函数返回以像素为单位的设备环境的宽度, HORZRES是定义在WINGDI.H中的29个标识符之一, 这29个标识符如下:

 

/* Device Parameters for GetDeviceCaps() */
#define DRIVERVERSION 0     /* Device driver version                    */
#define TECHNOLOGY    2     /* Device classification                    */
#define HORZSIZE      4     /* Horizontal size in millimeters           */
#define VERTSIZE      6     /* Vertical size in millimeters             */
#define HORZRES       8     /* Horizontal width in pixels               */
#define VERTRES       10    /* Vertical height in pixels                */
#define BITSPIXEL     12    /* Number of bits per pixel                 */
#define PLANES        14    /* Number of planes                         */
#define NUMBRUSHES    16    /* Number of brushes the device has         */
#define NUMPENS       18    /* Number of pens the device has            */
#define NUMMARKERS    20    /* Number of markers the device has         */
#define NUMFONTS      22    /* Number of fonts the device has           */
#define NUMCOLORS     24    /* Number of colors the device supports     */
#define PDEVICESIZE   26    /* Size required for device descriptor      */
#define CURVECAPS     28    /* Curve capabilities                       */
#define LINECAPS      30    /* Line capabilities                        */
#define POLYGONALCAPS 32    /* Polygonal capabilities                   */
#define TEXTCAPS      34    /* Text capabilities                        */
#define CLIPCAPS      36    /* Clipping capabilities                    */
#define RASTERCAPS    38    /* Bitblt capabilities                      */
#define ASPECTX       40    /* Length of the X leg                      */
#define ASPECTY       42    /* Length of the Y leg                      */
#define ASPECTXY      44    /* Length of the hypotenuse                 */

#if(WINVER >= 0x0500)
#define SHADEBLENDCAPS 45   /* Shading and blending caps                */
#endif /* WINVER >= 0x0500 */

#define LOGPIXELSX    88    /* Logical pixels/inch in X                 */
#define LOGPIXELSY    90    /* Logical pixels/inch in Y                 */

#define SIZEPALETTE  104    /* Number of entries in physical palette    */
#define NUMRESERVED  106    /* Number of reserved entries in palette    */
#define COLORRES     108    /* Actual color resolution                  */

 

关于这些标识符的详细介绍可查阅MSDN -> GetDeviceCaps函数对参数int nIndex的解释。

 

3. 绘图函数
    绘图函数就是GDI中最重要的部分了, 其中有许多的绘图函数, 这里同样不能一次讲完, 也不打算讲完, 这些都没有什么意义, 只需要查阅下MSDN或Google下找到这些函数并尝试着使用它就行了, 这里仅描述几个最基本的绘图函数。
    
    1>. SetPixel绘制像素点
        这是一个十分不常用的函数, 如果你想拿这个来绘制一条直线, 那么效率就太低了, 这个函数为 SetPixel, 其函数的原型为:

        COLORREF SetPixel(
            HDC hdc,
            int X,                    //x坐标
            int Y,                    //y坐标
            COLORREF crColor          //颜色, 使用RGB宏
        );

 

        RGB宏定义在WINGDI.H中, 定义如下:

        #define RGB(r,g,b)          ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))

 

        RGB宏有三个参数, 将红(R)、绿(G)、蓝(B)组合成一个无符号型的长整形用来表示颜色, 当三个参数都为0时颜色为黑色, 都为255时颜色为白色。

  使用示例:

        //在屏幕坐标为( 100, 100 )将该像素置为黑色
        SetPixel( hdc, 100, 100, RGB( 0, 0, 0 ) ) ;

 

  

  2>. LineTo绘制直线

 

        BOOL LineTo(
            HDC hdc,
            int nXEnd,        //结束x坐标
            int nYEnd        //结束y坐标
        );

 

        该函数需要和MoveToEx函数配合使用才能制定设备环境上的任意两点间绘制一条直线, MoveToEx函数的作用就是规定直线的起点坐标, 其函数原型为:

        BOOL MoveToEx(
            HDC hdc,
            int X,                    //起点x坐标
            int Y,                    //起点y坐标
            LPPOINT lpPoint            //一个POINT结构, 用来接收当前位置, 为NULL表示不接收
        );

 

  

  使用示例:

  //绘制一条起点为( 100, 100 ), 终点为( 500, 100 )的水平直线
   MoveToEx( hdc, 100, 100, NULL );
   LineTo( hdc, 500, 100 );

 

 

    3>. Polyline绘制折线
        函数原型:

        BOOL Polyline(
            HDC hdc,
            const POINT *lppt,        //指向POINT结构数组
            int cPoints                //在该组坐标中所使用的点数, 必须 >= 2
        );

       用法示例:

   //绘制一条折线
     POINT apt[5] = { {100, 100}, {300, 200}, {100, 400}, {400, 300}, {500, 200} } ;
     Polyline( hdc, apt, 5 ) ;

 


    4>. PolyPolyline绘制多条折线
        PolyPolyline实际上是对Polyline函数中使用的坐标组提供了分组功能, 分为几组就意味着绘制多少条折线, PolyPolyline函数的原型:

        BOOL PolyPolyline(
            HDC hdc,
            const POINT *lppt,                //坐标组
            const DWORD *lpdwPolyPoints,      //对坐标进行分组
            DWORD cCount                      //折线条数
        );

        用法示例:

        //8个点的坐标组
        POINT apt[8] = { { 100, 100}, {200, 200}, {300, 200}, {200, 300}, {400, 200}, {600, 300}, {300, 400}, {500, 200} } ;
        DWORD  lpPts[] = { 4, 4 };               //将这些点分为两组, 4个点为一组
        PolyPolyline( hdc, apt, lpPts, 2 ) ;         //绘制两条折线

 


    5>. Rectangle绘制矩形
        绘制一个矩形十分简单, 只需要提供矩形左上角坐标与右下角坐标即可, 函数原型如下:

        BOOL Rectangle(
            HDC hdc,
            int nLeftRect,         //左上角x坐标
            int nTopRect,          //左上角y坐标
            int nRightRect,        //右下角x坐标
            int nBottomRect        //右下角y坐标
        );

        例如我们绘制一个正方形:

  Rectangle( hdc, 50, 50, 200, 200 ) ;

        看下效果图:


        可以看到, 确实是一个由四条直线围成的矩形, 但是需要提醒的是, 这个矩形是经过自动填充的, 填充的颜色就是默认的白色颜色, 在这个图中填充颜色为白色, 和背景颜色相同, 所以我们才不易觉察到这是一个经过填充的图形, 被填充意思上就是说你以前在该矩形里或者说经过该矩形的图形会被白色给覆盖掉, 这点需要注意。
        


    6>. Ellipse绘制椭圆
        函数原型:

        BOOL Ellipse(
            HDC hdc,
            int nLeftRect,
            int nTopRect,
            int nRightRect,
            int nBottomRect
        );

        绘制椭圆的参数和绘制矩形的参数是相同的, 那么他是如何绘制的呢? 这里用图示说明下:


        示例中的矩形参数与椭圆的参数是相同的, 很明显的可以看出椭圆是根据矩形的大小自动填充的, 需要提示的是, 它填充的是整个矩形, 而不仅仅是自身的椭圆。
        


    7>. RoundRect画圆角矩形
    函数原型:

    BOOL RoundRect(
        HDC hdc,
        int nLeftRect,
        int nTopRect,
        int nRightRect,
        int nBottomRect,
        int nWidth,
        int nHeight
    );

    相对于画矩形函数这里又多出两个参数int nWidthint nHeight, 实际上, 这两个参数是用来描述圆角的宽和高, 如图:


    


    8>. Arc弧线、Chord拱形与Pie扇形
        之所以将这三个函数放在一起讲是因为他们三个拥有相同的参数,  他们三个的函数原型如下:

        BOOL Arc  ( HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd ) ;
        BOOL Chord( HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd ) ;
        BOOL Pie  ( HDC hdc, int xLeft, int yTop, int xRight, int yBottom, int xStart, int yStart, int xEnd, int yEnd ) ;

        这些参数都表示什么意思呢? 用文字恐怕不太好描述, 图示如下:

 


        
        在指定好外部矩形的坐标后剩下的就是指定绘制的起点坐标与终点了, 多尝试几次就能掌握其使用规律。
       
以上的这些就是GDI种较为基础的绘图函数了, 更多的绘图函数请自行查阅MSDN Library。

 

下面练习使用下这些函数:

WinMain函数部分, 已折叠显示:

 

View Code - WinMain Function
 1 #include<windows.h>
 2 
 3 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ;
 4 
 5 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow )
 6 {
 7     static TCHAR szAppName[] = TEXT( "DrawDemo" ) ;
 8     HWND hwnd ;
 9     MSG msg ;
10     WNDCLASS wndclass ;
11 
12     wndclass.lpfnWndProc    = WndProc ;
13     wndclass.style            = CS_HREDRAW | CS_VREDRAW ;
14     wndclass.hInstance        = hInstance ;
15     wndclass.lpszClassName    = szAppName ;
16     wndclass.hbrBackground    = (HBRUSH) GetStockObject( WHITE_BRUSH ) ;
17     wndclass.cbClsExtra        = 0 ;
18     wndclass.cbWndExtra        = 0 ;
19     wndclass.hCursor        = LoadCursor( NULL, IDC_ARROW ) ;
20     wndclass.hIcon            = LoadIcon( NULL, IDI_APPLICATION ) ;
21     wndclass.lpszMenuName    = NULL ;
22 
23     if( !RegisterClass( &wndclass ) )
24     {
25         MessageBox( NULL, TEXT( "错误, 无法注册窗口类!" ), TEXT( "错误" ), MB_OK  ) ;
26         return 0 ;
27     }
28 
29     hwnd = CreateWindow( szAppName, TEXT( "Demo" ),
30                         WS_OVERLAPPEDWINDOW,
31                         CW_USEDEFAULT, CW_USEDEFAULT,
32                         CW_USEDEFAULT, CW_USEDEFAULT,
33                         NULL, NULL, hInstance, NULL ) ;
34 
35     ShowWindow( hwnd, iCmdShow ) ;
36     UpdateWindow( hwnd ) ;
37 
38     while( GetMessage( &msg, NULL, 0, 0 ) )
39     {
40         TranslateMessage( &msg ) ;
41         DispatchMessage( &msg ) ;
42     }
43 
44     return msg.wParam ;
45 }

 

窗口过程函数部分:

 1 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
 2 {
 3     HDC hdc ;
 4     PAINTSTRUCT ps ;
 5     POINT apn[4] = { {150, 50}, {200, 200}, {150, 300}, {150, 500} } ;        //坐标组
 6     POINT apt[8] = { { 200, 50}, {300, 200}, {250, 200}, {200, 300}, {250, 300}, {300, 350}, {250, 400}, {250, 500} } ;        //坐标组
 7     DWORD  lpPts[] = { 4, 4 };        //使用PolyPolyline进行多条折线绘制时的分组
 8     int i ;
 9 
10     switch( message )
11     {
12     case WM_PAINT:
13         hdc = BeginPaint( hwnd, &ps ) ;
14 
15         //在(50, y)方向上绘制90个点, 颜色为黑色
16         for( i = 0; i < 90; i++ )
17             SetPixel( hdc, 50, 50 + i * 5, RGB( 0, 0, 0 ) ) ;
18 
19         //画线, 起点坐标( 100, 50 ), 终点坐标( 100, 500 )
20         MoveToEx( hdc, 100, 50, NULL );
21         LineTo( hdc, 100, 500 );
22 
23         //画一条折线
24         Polyline( hdc, apn, 4 ) ;
25 
26         //画2条折线, 将apt分为2组
27         PolyPolyline( hdc, apt, lpPts, 2 ) ; 
28 
29         //画椭圆弧线
30         Arc(hdc, 350, 50, 500, 500, 400, 100, 400, 500 ) ;
31 
32         //画矩形
33         Rectangle( hdc, 450, 50, 500, 500 ) ;
34 
35         //画椭圆
36         Ellipse( hdc, 550, 50, 600, 500 ) ;
37 
38         //画圆角矩形
39         RoundRect( hdc, 650, 50, 700, 500, 20, 20 ) ;
40 
41         //画扇形
42         Pie( hdc, 750, 50, 850, 150, 850, 80, 850, 160 ) ;
43 
44         //画拱形
45         Chord( hdc, 750, 400, 850, 500, 850, 450, 750, 450 ) ;
46 
47         EndPaint( hwnd, &ps ) ;
48         return 0 ;
49 
50     case WM_DESTROY:
51         PostQuitMessage( 0 ) ;
52         return 0 ;
53     }
54 
55     return DefWindowProc( hwnd, message, wParam, lParam ) ;
56 }

编译运行后的效果图:

 

如果我们想给这些图形填充颜色或者改变其线条的粗细该怎么做呢? 很简单, 下一次学习画笔的使用时就可以轻松解决这些问题。

 

--------------------


wid, 2012.11.07

 

上一篇: C语言Windows程序设计-> 第八天-> 滚动条