图形基础--画点和线

 

画点和线

第一章,我们谈论过Windows图形设备接口将图形输出设备的设备驱动程序与计算机连在一起的方式。在理论上,只要提供SetPixel和GetPixel函数,就可以使用图形设备驱动程序绘制一切东西了。其余的一切都可以使用GDI模块中实作的更高阶的例程来处理。例如,画线时,只需GDI呼叫SetPixel数次,并适当地调整x和y坐标。

在实际情况中,也的确可以仅使用SetPixel和GetPixel函数进行您需要的任何绘制。您也可以在这些函数的基础上设计出简洁和构造良好的图形编程系统。唯一的问题是启能。如果一个函数通过几次呼叫才能到达SetPixel函数,那么它执行起来会非常慢。如果一个图形系统画线和进行其它复杂的图形操作是在设备驱动程序的层次上,它就会更有效得多,因为设备驱动程序对完成这些操作的程序代码进行了最佳化。此外,一些显示卡包含了图形协处理器,它允许视讯硬件自己绘制图形。

设定图素

即使Windows GDI包含了SetPixel和GetPixel函数,但很少使用它们。在本书,仅在第七章的CONNECT程序中使用了SetPixel函数,仅在第八章的WHATCLR程序中使用了GetPixel函数。尽管如此,由它们开始来研究图形仍是非常方便。

SetPixel函数在指定的x和y坐标以特定的颜色设定图素:

SetPixel (hdc, x, y, crColor) ;
        

如同在任何绘图函数中一样,第一个参数是设备内容的句柄。第二个和第三个参数指明了坐标位置。通常要获得窗口显示区域的设备内容,并且x和y相对于该显示区域的左上角。最后一个参数是COLORREF型态指定了颜色。如果在函数中指定的颜色视讯显示器不支持,则函数将图素设定为最接近的纯色并从函数传回该值。

GetPixel函数传回指定坐标处的图素颜色:

crColor = GetPixel (hdc, x, y) ;
        

直线

Windows可以画直线、椭圆线(椭圆圆周上的曲线)和贝塞尔曲线。Windows 98支援的7个画线函数是:

  • LineTo 画直线。
     
  • Polyline和PolylineTo 画一系列相连的直线。
     
  • PolyPolyline 画多组相连的线。
     
  • Arc 画椭圆线。
     
  • PolyBezier和PolyBezierTo 画贝塞尔曲线。
     

另外,Windows NT还支持3种画线函数:

  • ArcTo和AngleArc 画椭圆线。
     
  • PolyDraw 画一系列相连的线以及贝塞尔曲线。
     

这三个函数Windows 98不支援。

在本章的后面我将介绍一些既画线也填入所画图形的封闭区域的函数,这些函数是:

  • Rectangle 画矩形。
     
  • Ellipse 画椭圆。
     
  • RoundRect 画带圆角的矩形。
     
  • Pie 画椭圆的一部分,使其看起来像一个扇形。
     
  • Chord 画椭圆的一部分,以呈弓形。
     

设备内容的五个属性影响着用这些函数所画线的外观:目前画笔的位置(仅用于LineTo、PolylineTo、PolyBezierTo和ArcTo )、画笔、背景方式、背景色和绘图模式。

画一条直线,必须呼叫两个函数。第一个函数指定了线的开始点,第二个函数指定了线的终点:

MoveToEx (hdc, xBeg, yBeg, NULL) ;
        
LineTo (hdc, xEnd, yEnd) ;
        

MoveToEx实际上不会画线,它只是设定了设备内容的「目前位置」属性。然后LineTo函数从目前的位置到它所指定的点画一条直线。目前位置只是用于其它几个GDI函数的开始点。在内定的设备内容中,目前位置最初设定在点(0,0)。如果在呼叫LineTo之前没有设定目前位置,那么它将从显示区域的左上角开始画线。


小历史:

Windows的16位版本中,用来改变目前位置的函数是MoveTo。该函数只调整三个参数-设备内容句柄、x和y坐标。函数通过两个16位数拼成的32位无正负号长整数传回先前的目前位置。然而,在Windows的32位版本中,坐标是32位的数值,而C的32位版本中又没有定义64位的整数数据型态,因此这种改变意味着MoveTo在其传回值中不再指出先前的目前位置。在实际的程序写作中,由MoveTo传回的值几乎从来不用,因此就需要一个新函数,这就是MoveToEx。


MoveToEx的最后一个参数是指向POINT结构的指针。从该函数传回后,POINT结构的x和y字段指出了先前的目前位置。如果您不需要这种信息(通常如此),可以简单地如上面的例子所示的那样将最后一个参数设定为NULL。


警告:

尽管Windows 98中的坐标值看起来是32位的,实际上却只用到了低16位,坐标值实际上被限制在-32,768到32,767之间。在Windows NT中,使用完整的32位值。


如果您需要目前位置,就可以通过以下呼叫获得:

GetCurrentPositionEx (hdc, &pt) ;
        

其中,pt是POINT结构的。

下面的程序代码从窗口的左上角开始,在显示区域中画一个网格,线与线之间相隔100个图素,其中hwnd是窗口句柄,hdc是设备内容句柄,而x和y是整数:

GetClientRect (hwnd, &rect) ;
        
for (       x = 0 ; x < rect.right ; x+= 100)
        
{
        
    MoveToEx (hdc, x, 0, NULL) ;
        
  LineTo (hdc, x, rect.bottom) ;
        
}
        
for (y = 0 ; y < rect.bottom ; y += 100)
        
{
        
    MoveToEx (hdc, 0, y, NULL) ;
        
    LineTo (hdc, rect.right, y) ;
        
}
        

虽然用两个函数来画一条直线显得有些麻烦,但是在希望画一组相连的直线时,目前画笔位置属性又会变得很有用。例如,您可能想定义一个包含5个点(10个值)的数组,来画一个矩形的边界框:

POINT apt[5] = { 100, 100, 200, 100, 200, 200, 100, 200, 100, 100 } ;
        

注意,最后一个点与第一个点相同。现在,只需要使用MoveToEx移到第一个点,并对后面的点使用LineTo:

MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ;
        
for (       i = 1 ; i < 5 ; i++)
        
    LineTo (hdc, apt[i].x, apt[i].y) ;
        

由于LineTo从目前位置画到(但不包括)LineTo函数中给出的点,所以这段程序代码没有在任何坐标处画两次。虽然在显示器上多输出几次不存在问题,但是在绘图机上或者在其它绘图方式(下面马上会讲到)下,视觉效果就不太好了。

当您要将数组中的点连接成线时,使用Polyline函数要简单得多。下面这条叙述画出与上面一段程序代码相同的矩形:

Polyline (hdc, apt, 5) ;
        

最后一个参数是点的数目。我们还可以使用(sizeof (apt) / sizeof (POINT))来表示这个值。Polyline与一个MoveToEx函数后面加几个LineTo函数的效果相同,但是,Polyline既不使用也不改变目前位置。PolylineTo有些不同,这个函数使用目前位置作为开始点,并将目前位置设定为最后一根线的终点。下面的程序代码画出与上面所示一样的矩形:

MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ;
        
PolylineTo (hdc, apt + 1, 4) ;
        

您可以对几条线使用Polyline和PolylineTo,这些函数在绘制复杂曲线最有用了。您使用由几百甚至几千条线组成的极短线段,把它们连在一起就像一条曲线一样。例如,画正弦波就是这样的,程序5-2所示的SINEWAVE程序显示了如何做到这一点。

程序5-2  SINEWAVE
        
SINEWAVE.C
        
/*-------------------------------------------------------------------
        
  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 ("SineWave") ;
        
    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 ("Program requires Windows NT!"),
        
                   szAppName, MB_ICONERROR) ;
        
                   return 0 ;
        
    }
        
   
        
    hwnd = CreateWindow ( szAppName, TEXT ("Sine Wave Using Polyline"),
        
                          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  cxClient, cyClient ;
        
    HDC         hdc ;
        
    int         i ;
        
    PAINTSTRUCT ps ;
        
    POINT       apt [NUM] ;
        
   
        
    switch (message)
        
    {
        
    case   WM_SIZE:
        
            cxClient = LOWORD (lParam) ;
        
           cyClient = HIWORD (lParam) ;
        
            return 0 ;
        
        
        
    case   WM_PAINT:
        
            hdc = BeginPaint (hwnd, &ps) ;
        
        
        
            MoveToEx (hdc, 0,             cyClient / 2, NULL) ;
        
            LineTo   (hdc, cxClient, cyClient / 2) ;
        
        
        
            for (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) ;
        
            return 0 ;
        
        
        
    case   WM_DESTROY:
        
            PostQuitMessage (0) ;
        
            return 0 ;
        
    }
        
    return DefWindowProc (hwnd, message, wParam, lParam) ;
        
}
        

这个程序有一个含有1000个POINT结构的数组。随着for循环从0增加到999,结构的x成员设定为从0递增到数值cxClient。结构的y成员设定为一个周期的正弦曲线值,并被放大以填满显示区域。整个曲线的绘制仅仅使用了一个Polyline呼叫。因为Polyline函数是在设备驱动程序层次上实作的,因此它要比呼叫1000次LineTo快得多,结果如图5-5所示。

图5-5 SINEWAVE显示

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值