Chapter 05 绘图基础

整理了大半个月,终于把Chapter 05整理好,希望能够对自己以及网友有所帮助。本章我们将一起学习绘图基础,本章节会学习到GDI基础、绘制线条和填充区域的基础知识。Windows子系统负责在称为图形设备接口(Graphics Device Interface,GDI)的视频显示器和打印机上显示图形,GDI的重要性不仅体现在Windows上显示信息的应用程序时要使用GDI,Windows本身也会使用GDI显示用户界面的项目,比如菜单、滚动条、图标和鼠标指针。

5.1    GDI的结构

5.1.1 GDI原理

在Windows NT中,图形显示主要由GDI32.DLL中导出的函数处理,该动态链接库会调用你安装的视频显示器和打印机的设备驱动程序中的一些函数。视频驱动程序会直接访问视频显示器的硬件,而打印机驱动程序则将GDI命令转化为各种打印机所能理解的代码或者命令,因此不同的显示适配器和打印机需要使用不同的设备驱动程序。GDI提供了一种特殊的机制来彻底隔离应用程序和不同输出设备的特性,以提供与设备无关的图形。

5.1.2 GDI函数调用

GDI包含有几百个函数,可以分成如下几类:

 获取(或建立)和释放(或销毁)设备环境的函数:绘制时,需要使用一个设备环境句柄。BeginPaint和EndPaint函数(尽管从技术上来说,它们属于USER模块,而不是GDI模块)允许你在处理WM_PAINT消息时做到这一点。在处理其他消息时,可以通过GetDC和ReleaseDC函数来达到相同的目的。

 获取设备环境信息的函数:例如GetTextMetrics函数可以用来获取当前被选入设备环境的字体的尺寸信息。GetSystemMetrics函数可以用来获取显示设备和系统配置的尺寸信息(以像素为单位)。

 绘图函数:一旦绘图准备工作就绪,绘图函数才会发挥它的作用。例如TextOut函数在窗口的客户区显示,以及即将介绍的绘制线条和填充区域的GDI函数等等。

 设置和获取设备环境属性的函数:设备环境的属性确定会凸函数绘图时的各种细节。例如,可以使用SetTextColor函数来指定TextOut绘制的文本的颜色。所有的设备环境的属性都有一个默认值,这个默认值在获取设备环境时候已经被设置好了,对所有的以Set开头的函数,都有相应的一个以Get开头的函数用户获取当前设备环境的属性。

使用GDI“对象”的函数: GDI对象包括画笔、画刷、字体、区域、位图、调色板以及其他GDI对象。包含各种针对GDI对象的操作函数,例如使用CreatePen、CreatePenIndirect或者ExtCreatePen函数来创建逻辑画笔,使用SelectObject函数将逻辑画笔选入某设备环境中。

5.1.3 GDI的基本图形

在屏幕上或者打印机显示的图形类型可以分为下面几类,被称为“基本图形”。

线条和曲线 线条是任何矢量图形绘制系统的基础。GDI支持直线、矩形、椭圆和贝塞尔曲线。GDI使用当前选入设备环境的画笔绘制线条。

可悲填充的封闭区域 当一系列的线条或者曲线构成一个封闭区域时,你可以使用当前GDI的画刷对象来填充这个区域。

位图 位图是一个二维的位数组,每一个元素都对应显示设备上的一个像素。位图是光栅图形的基础。位图通常位于在视频显示器或者打印机上显示复杂(通常是真实世界)的图像。位图也通常用于显示必须要快速绘制的小图像,例如图标、鼠标指针以及出现在应用程序工具栏里的按钮。GDI支持两种类型的位图:旧式的设备相关位图和新式的设备无关位图(DIB,从Windows3.0起)。DIB可以存放在磁盘文件中。

文本 文本通常是任何计算机图形系统中最复杂的部分,而且也是最重要的部分。在所有Windows的数据结构中,用于定义GDI字体对象和获取字体信息的数据结构式最庞大的。GDI从Windows3.1开始支持TrueType字体,这种字体是以填充的轮廓线为基础的,某些GDI函数可以操控这些轮廓线。

5.1.4 其他

GDI的其他方面就不太容易分类,具体如下:

映射模式(mapping mode)和转换(transform):尽管在默认时是以像素为单位进行绘制的,但是并不是别无选择。GDI的映射模式允许以英寸(甚至几分之一英寸)、毫米或者其他你所想要的任何单位进行绘制。除此之外,Windows NT支持传统的世界坐标转换,适用于倾斜和旋转图形对象。

图元文件(metafile): 一个图元文件是以二进制形式存储的GDI命令的集合。图元文件主要用于通过剪贴板转换矢量图形绘制的表现形式。

区域(region):区域是一个任意形状的封闭图形,通常可以表示为由一系列简单区域进行布尔运算后得到的结果。在GDI内部,可以使用一个从已知区域出发的一系列扫描线来顶一个复杂的区域。可以使用区域进行轮廓绘制、填充或者剪裁。

路径(path):路径是存储在GDI内部的直线和曲线的集合。可以用于绘制、填充和剪裁。路径还可以转换为区域。

剪裁(clipping):当绘图被限制在客户区的一个特定的空间位置时,就发生了剪裁。那个特定的空间位置可以是矩形或者非矩形,它通常被指定为一个区域或者一个路径。

调色板(palettes):仅在支持256种颜色时,才能使用自定义的调色板。Windows仅保留中的20种色彩以供系统使用。你可以改变其他236种色彩,这样就可以准确显示按位图形式存储的真实图像。

打印(printing):尽管本章只讨论视频显示器,但在本章学到的所有知识几乎都可以应用于打印机。将在Chapter 13讨论打印机。

5.2 设备环境

开始绘图之前,首先让我们在第4章的基础上更严谨地讨论一下设备环境。

如果希望在图形设输出设备上绘制图形,必须首先获取设备环境(即DC)的句柄。当Windows把这个句柄交给你的程序,Windows同时也就给予你使用这个设备的权限。接着,在GDI函数中将这个句柄作为一个参数,告诉Windows在哪个设备上进行绘图。设备环境包含许多决定GDI函数如何工作的属性,这些属性使得GDI函数只需要提供少量的参数,而不需要提供Windows在设备上显示对象时需要的所有消息。

5.2.1 获取设备环境句柄

获取和释放设备环境句柄最常用的方法是在处理WM_PAINT消息时使用BeginPaint函数和EndPaint函数:

hdc= BeginPaint(hwnd, &ps);
    [Otherprogram lines]
EndPaint(hwnd,&ps);

其中,变量ps是一个类型为PAINTSTRUCT的结构。这个结构中的字段hdc和BeginPaint函数返回的设备环境句柄的值相同。PAINTSTRUCT结构还包含一个名为rcPaint的矩形结构,该结构定义了一个包围窗口客户区无效范围的矩形。使用从BeginPaint函数获取的设备环境句柄,就只能在这个矩形区域内绘图。调用EndPaint函数将使这个区域有效。

设备环境句柄还可以在处理非WM_PAINT消息时由Windows程序获取:

 hdc= GetDC(hwnd);
    [Other program lines]
 ReleaseDC(hwnd,hdc);

其中,设备环境指的是窗口句柄为hwnd的窗口客户区。调用这些函数和使用BeginPaint、EndPaint函数组合的主要差别是从GetDC函数返回的句柄可以在整个客户区内绘制。并且GetDC和ReleaseDC函数并不使任何客户区的无效区域变为很有效。

Windows程序还可以获得用于整个窗口的,而不仅仅是窗口客户区的设备环境句柄:

hdc= GetWindowDC(hwnd);
   [Otherprogram lines]
ReleaseDC(hwnd,hdc);

这里的设备环境除了客户区,还包括窗口标题栏、菜单、滚动条和客户区的外框。应用程序很少使用GetWindowDC函数。如果你想尝试使用它,则还应当捕获WM_NCPAINT(nonclient paint, 非客户区绘制)消息,Windows使用这个消息在窗口的非客户区绘图。

调用BeginPaint、GetDC和GetWindowDC函数可以获得在视频显示器上与一个特定的窗口相关联的设备环境。还有一个更通用的用于获取设备环境句柄的函数是CreateDC:

hdc= CreateDC(pszDriver,pszDevice,pszOutput,pData);
    [Otherprogram lines]
DeleteDC(hdc);

例如,你可以通过调用下面的函数获取当前整个屏幕的设备环境句柄:      

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


在窗口外输出文字或者图像不是很好,但是对于一些特殊的应用还是很有用的。(虽然在官方文档中,并没有提到这种方法,但是你还是可以通过在调用GetDC时使用一个NULL参数来得到整个屏幕的设备环境。)

有时候,仅需要获取一些关于设备环境的信息,而不需要在上面绘制任何东西。在这些情况下,可以调用CreateIC函数获取一个“信息上下文”(Information Context)句柄。这个函数的参数和CreateDC函数的参数相同。例如:    

hdc= CreateIC(TEXT(“DISPLAY”), NULL, NULL, NULL);

但是,往设备上写东西时,不能使用信息上下文句柄。

处理文图时,有时可能会用到一个“内存设备环境”:

hdcMem =CreateCompatibleDC(hdc);
  [Other program lines]
DeleteDC(hdcMem);

可以把一个位图选入内存设备环境,并且调用GDI函数绘制这个位图。将在Chapter14章节介绍这些技术。

      如前所述,图元文件是以二进制形式编码的GDI函数调用的集合。它可以通过获取一个图元文件的设备环境来创建:      

hdcMeta= CreateMetaFile(pszFileName);
     [Otherprogram lines]
hmf= ClosemetaFile(hdcMeta);

在图元文件设备环境有效时,使用hdcMeta所做的任何GDI调用都不会被显示出来,它们都会变成图元文件的一部分。当你调用CloseMetaFile时,图元文件设备环境句柄变为无效,该函数返回一个图元文件句柄(hmf)。我们将在Chapter 18讨论图元文件。

5.2.2 获取设备环境的信息

      设备环境通常指的是物理的显示设备,如视频显示器,或者打印机。经常需要获取这些设备的某些信息,包括显示器的大小(以像素或者物理尺寸的方式)和它的色彩能力。这些信息可以通过调用GetDeviceCaps(意思为获取设备的能力)函数来获取:

 iValue= GetDeviceCaps(hdc, iIndex);

其中,参数iIndex是定义在WINGDI.H头文件中的29个标识符之一。例如,当iIndex的值为HORZRES时,GetDeviceCaps函数以像素为单位返回设备的宽度;使用VERTRES参数值会以像素为单位返回设备的高度。如果hdc是一个屏幕设备环境的句柄,这里获取的信息和从GetSystemMetrics函数获取的信息是一样的。如果hdc是一个打印机设备环境,那么GetDeviceCaps将以像素为单位返回打印机显示区域的高度和宽度。

      还可以使用GetDeviceCaps函数来确定设备处理各种类型图形的能力,通常这对于视频显示器并不重要,但是对于打印机却非常重要。例如,大多数的绘图仪不能绘制位图图像,通过调用GetDeviceCaps函数可以让你提前知道这一情况。

5.2.3 DEVCAPS1程序

DEVCAPS1程序显示了在使用视频显示器设备环境时,可从GetDeviceCaps函数得到的部分(并不是全部)信息。代码如下所示:

*---------------------------------------------------------
   DEVCAPS1.C -- Device Capabilities Display Program No. 1
                 (c) Charles Petzold, 1998
  ---------------------------------------------------------*/

#include <windows.h>

#define NUMLINES ((int) (sizeof devcaps / sizeof devcaps [0]))
 
struct
{
     int     iIndex ;
     TCHAR * szLabel ;
     TCHAR * szDesc ;
}
devcaps [] =
{
     HORZSIZE,      TEXT ("HORZSIZE"),     TEXT ("Width in millimeters:"),
     VERTSIZE,      TEXT ("VERTSIZE"),     TEXT ("Height in millimeters:"),
     HORZRES,       TEXT ("HORZRES"),      TEXT ("Width in pixels:"),
     VERTRES,       TEXT ("VERTRES"),      TEXT ("Height in raster lines:"),
     BITSPIXEL,     TEXT ("BITSPIXEL"),    TEXT ("Color bits per pixel:"),
     PLANES,        TEXT ("PLANES"),       TEXT ("Number of color planes:"),
     NUMBRUSHES,    TEXT ("NUMBRUSHES"),   TEXT ("Number of device brushes:"),
     NUMPENS,       TEXT ("NUMPENS"),      TEXT ("Number of device pens:"),
     NUMMARKERS,    TEXT ("NUMMARKERS"),   TEXT ("Number of device markers:"),
     NUMFONTS,      TEXT ("NUMFONTS"),     TEXT ("Number of device fonts:"),
     NUMCOLORS,     TEXT ("NUMCOLORS"),    TEXT ("Number of device colors:"),
     PDEVICESIZE,   TEXT ("PDEVICESIZE"),  TEXT ("Size of device structure:"),
     ASPECTX,       TEXT ("ASPECTX"),      TEXT ("Relative width of pixel:"),
     ASPECTY,       TEXT ("ASPECTY"),      TEXT ("Relative height of pixel:"),
     ASPECTXY,      TEXT ("ASPECTXY"),     TEXT ("Relative diagonal of pixel:"),
     LOGPIXELSX,    TEXT ("LOGPIXELSX"),   TEXT ("Horizontal dots per inch:"),
     LOGPIXELSY,    TEXT ("LOGPIXELSY"),   TEXT ("Vertical dots per inch:"),
     SIZEPALETTE,   TEXT ("SIZEPALETTE"),  TEXT ("Number of palette entries:"),
     NUMRESERVED,   TEXT ("NUMRESERVED"),  TEXT ("Reserved palette entries:"),
     COLORRES,      TEXT ("COLORRES"),     TEXT ("Actual color resolution:")
} ;

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("DevCaps1") ;
     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 ("Device Capabilities"),
                          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 message, WPARAM wParam, LPARAM lParam)
{
     static int  cxChar, cxCaps, cyChar ;
     TCHAR       szBuffer[10] ;
     HDC         hdc ;
     int         i ;
     PAINTSTRUCT ps ;
     TEXTMETRIC  tm ;
     
     switch (message)
     {
     case WM_CREATE:
          hdc = GetDC (hwnd) ;
          
          GetTextMetrics (hdc, &tm) ;
          cxChar = tm.tmAveCharWidth ;
          cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
          cyChar = tm.tmHeight + tm.tmExternalLeading ;
          
          ReleaseDC (hwnd, hdc) ;
          return 0 ;
          
     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;
          
          for (i = 0 ; i < NUMLINES ; i++)
          {
               TextOut (hdc, 0, cyChar * i,
                        devcaps[i].szLabel,
                        lstrlen (devcaps[i].szLabel)) ;
               
               TextOut (hdc, 14 * cxCaps, cyChar * i,
                        devcaps[i].szDesc,
                        lstrlen (devcaps[i].szDesc)) ;
               
               SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
               
               TextOut (hdc, 14 * cxCaps + 35 * cxChar, cyChar * i, szBuffer,
                        wsprintf (szBuffer, TEXT ("%5d"),
                             GetDeviceCaps (hdc, devcaps[i].iIndex))) ;
               
               SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
          }
          
          EndPaint (hwnd, &ps) ;
          return 0 ;
          
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}


5.2.4 设备的尺寸

视频显示器和打印机是两种非常不同的设备,但是最不明显的区别或许是“分辨率”和设备联系起来的方式。使用打印机时,分辨率通常用每英寸的点数表示。例如有的激光打印机分辨率是每英寸300点或者600点。但是,视频显示器的分辨率却是以水平和垂直方向上显示的总的像素数给出的,如1024768。

在《Windows程序设计》这本书中,“分辨率“被严格定义为每度量单位(通常是英寸)中含有的像素数。我们将使用“像素尺寸(pixel size)”或者“像素规模(pixel dimension)”来表示设备在水平方向和垂直方向上显示的总的像素数。“度量尺寸”(metrical size)和“度量规模”(metrical dimension)是以每英寸或者毫米为单位的设备的客户区域的大小。

Windows应用程序可以通过在调用GetSystemMetrics函数时使用SM_CXSCREEN和SM_CYSCREEN参数来获取显示器的像素规模。从DEVCAPS1程序可以看出来,一个程序可以在调用GetDeviceCaps函数时使用HORZRES(“水平分辨率”)和VERTRES(“垂直分辨率”)来获取相同的值。在HORZRES中“分辨率”指的是像素尺寸,而不是每度量单位的像素数。

前两个设备能力HORZSIZE和VERTSIZE,官方文档中称为“以毫米计的物理屏幕宽度”和“以毫米计的物理屏幕高度”。

在计算机排版中,1点(point size亦称磅值)通常假定正好是1/72英寸。用TEXTMETRIC结构中的术语来说,字号(字体的大小)等于tmHeight减去tmInternalLeading。tmHeight字段表示文本的相邻行在屏幕上或者打印机上间隔有多大。

在Windows 98中,Windows是通过用户选择的显示器像素规模和用户为系统字体大小选择的分辨率来计算显示器的尺寸的。

在Windows NT中,HORZRES和VERTRES的值仍表示水平方向上和垂直方向上像素的数目,并且LOGPIXELX和LOGPIXELY仍和你在控制面板中的【显示】程序中设置视频分辨率时选择的字体相关。与Windows 98不同之处是:HORZSIZE和VERTSIZE的值是固定的,用来表示标准显示器的尺寸。对一般的适配器,你获取的HORZSIZE和VERTSIZE的值分别是320毫米和240毫米。这些值是相同的,不管你选择什么样的像素规模。

如果程序需要视频显示器的实际物理尺寸,最好的解决办法是提供一个对话框来实际要求用户输入它们。

最后,另外三个从GetDeviceCaps函数获得的值是与视频尺寸相关的。这三个值分别是ASPECTX、ASPECTY和ASPECTXY,它们分别表示每个像素点相对的宽度、高度和对角线长度,并且四舍五入到整数。

5.2.5 色彩ABC

只能显示黑色像素和白色像素的视频显示器要显示每个像素只需要一位的内存。彩色显示器的每个像素却需要多个位的内存。位数越多,可表示的色彩越多;更精确一点,2的位数次方就是它可以表示的不同色彩数目。

真彩(true color)视频显示器有每像素24位的分辨率(8位表示红色、8位表示绿色和8位表示蓝色)。红、绿和蓝为“三原色”。许多其他的颜色可以由这三种颜色混合。

高彩(high color)显示器有每像素16位的分辨率,通常5位表示红色、6位表示绿色和5位表示蓝色。

一个显示256色的视频适配器每像素8位的内存,然而,这8位的值通常是一个索引值,它指向一个调色板中定义的某种实际颜色。我们将在Chapter16章节中详细地介绍。

最后,16色的视频板卡每个像素需要4位的内存。这16种颜色通常固定为暗或者亮的红、绿、蓝、青、紫、黄、两种灰色、黑和白。

虽然只有在一些奇怪的程序中才有必要知道视频适配器板卡上的内存组织形式,但是调用GetDeviceCaps函数总可以帮助你确定这些信息。各个像素的多个色彩位可以在显卡内存中以顺序方式存储,也可以不同的色彩位被放在内存的不同平面(plane)上。色彩平面的数目可以由下面的函数调用获得:     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值