三、Windows图像处理—画点和线(直线)
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所示。