Chapter 5 Basic Drawing 第五章 绘图基础 — Drawing Dots and Lines - 绘制点和线

类别:编程语言 点击:0 评论:0 推荐:
ps:您可以转载,但请注明出处;你可以修改,但请将修改结果告诉我。
  Drawing Dots and Lines 绘制点和线   In the first chapter, I discussed how the Windows Graphics Device Interface makes use of device drivers for the graphics output devices attached to your computer. In theory, all that a graphics device driver needs for drawing is a SetPixel function and a GetPixel function. Everything else could be handled with higher-level routines implemented in the GDI module. Drawing a line, for instance, simply requires that GDI call the SetPixel routine numerous times, adjusting the x- and y-coordinates appropriately. 在上一章中,我讨论了 Windows 图形设备接口如何利用关联设备于附加到你的计算机的图形输出设备。理论上,对于绘制图形驱动程序需要的所有东西是 SetPixel 函数和 GetPixel 函数。其它每件事都可以由在 GDI 模型中实现的高级程序处理。例如,绘制一条线只需要 GDI 适当地调整 x 和 y 坐标多次调用 SetPixel 程序。   In reality, you can indeed do almost any drawing you need with only SetPixel and GetPixel functions. You can also design a neat and well-structured graphics programming system on top of these functions. The only problem is performance. A function that is several calls away from each SetPixel function will be painfully slow. It is much more efficient for a graphics system to do line drawing and other complex graphics operations at the level of the device driver, which can have its own optimized code to perform the operations. Moreover, some video adapter boards contain graphics coprocessors that allow the video hardware itself to draw the figures. 实际上,你的确可以只用 SetPixel 和 GetPixel 函数做几乎你需要的任何绘制。你也可以在这些函数之上设计一个优雅的和结构良好的图形程序设计系统。唯一的问题是性能。远离每个 SetPixel 函数的函数将会很慢。对于图形系统来说绘制直线和其它在图形驱动程序层次上的复杂的图形操作是更有效率的,它可以有它自己的执行操作的优化代码。而且,一些图形适配器板包含允许视频硬件自己绘制图形的图形微处理器。   Setting Pixels 设置像素   Even though the Windows API includes SetPixel and GetPixel functions, they are not commonly used. In this book, the only use of the SetPixel function is in the CONNECT program in Chapter 7, and the only use of GetPixel is in the WHATCLR program in Chapter 8. Still, they provide a convenient place to begin examining graphics. 即使 Windows API 包含 SetPixel 和 GetPixel 函数,但它们是不常用的。在本书中,SetPixel 函数唯一用到的地方是在第 7 章中的 CONNECT 程序中,而 GetPixel 唯一用到的地方是在第 8 章中的 WHATCLR 程序中。它们仍然提供了一个方便的开始考查图形的地方。   The SetPixel function sets the pixel at a specified x- and y-coordinate to a particular color: SetPixel 函数设置在指定的 x 和 y 坐标的点为特殊的颜色:   SetPixel (hdc, x, y, crColor) ;   As in any drawing function, the first argument is a handle to a device context. The second and third arguments indicate the coordinate position. Mostly you'll obtain a device context for the client area of your window, and x and y will be relative to the upper left corner of that client area. The final argument is of type COLORREF to specify the color. If the color you specify in the function cannot be realized on the video display, the function sets the pixel to the nearest pure nondithered color and returns that value from the function. 正如在任何绘图函数中,第一个参数是关联设备的句柄。第二和第三个参数指的是坐标位置。通常你将获得你的窗口的客户区的关联设备,而 x 和 y 将是相对于该客户区的左上角。最后一个参数是指定颜色的类型 COLORREF。如果你在函数中指定的颜色不可以在视频显示器上被认识,那么这个函数设置像素为最近的纯的非抖动的颜色并从函数返回该值。   The GetPixel function returns the color of the pixel at the specified coordinate position: GetPixel 函数返回指定坐标位置的像素的颜色:   crColor = GetPixel (hdc, x, y) ;   Straight Lines 直线   Windows can draw straight lines, elliptical lines (curved lines on the circumference of an ellipse), and Bezier splines. Windows 98 supports seven functions that draw lines: Windows 可以绘制直线,椭圆线(在椭圆周围的曲线)和贝塞尔曲线。Windows 98 支持七个绘制线的函数: LineTo Draws a straight line. LineTo 绘制直线。 Polyline and PolylineTo Draw a series of connected straight lines. Polyline 和 PolylineTo 绘制一系列相连的直线。 PolyPolyline Draws multiple polylines. PolyPolyline 绘制多组相连的线。 Arc Draws elliptical lines. Arc 绘制椭圆线。 PolyBezier and PolyBezierTo Draw Bezier splines. PolyBezier 和 PolyBezierTo 绘制贝塞尔曲线。 In addition, Windows NT supports three more line-drawing functions: 另外,Windows NT 支持另外三个绘线函数: ArcTo and AngleArc Draw elliptical lines. ArcTo 和 AngleArc 绘制椭圆线。 PolyDraw Draws a series of connected straight lines and Bezier splines. PolyDraw 绘制一系列相连的直线和贝塞尔曲线。 These three functions are not supported under Windows 98. 这三个函数在 Windows 98 下不支持。   Later in this chapter I'll also be discussing some functions that draw lines but that also fill the enclosed area within the figure they draw. These functions are 在本章后面我也将讨论一些绘线函数,但是它们也填充在它们绘制的图形内的封闭区域。这些函数是 Rectangle Draws a rectangle. Rectangle 绘制矩形。 Ellipse Draws an ellipse. Ellipse 绘制椭圆。 RoundRect Draws a rectangle with rounded corners. RoundRect 绘制有圆角的矩形。 Pie Draws a part of an ellipse that looks like a pie slice. Pie 绘制椭圆的一部分,它看起来像扇形。 Chord Draws part of an ellipse formed by a chord. Chord 绘制椭圆的一部分,以成弓形。 Five attributes of the device context affect the appearance of lines that you draw using these functions: current pen position (for LineTo, PolylineTo, PolyBezierTo, and ArcTo only), pen, background mode, background color, and drawing mode. 关联设备的五个属性影响你用这些函数绘制的线的外观:当前画笔位置(只对于 LineTo,PolylineTo,PolyBezierTo 和 ArcTo),画笔,背景模式,背景颜色和绘制模式。   To draw a straight line, you must call two functions. The first function specifies the point at which the line begins, and the second function specifies the end point of the line: 为了绘制一条直线,你必须调用两个函数。第一个函数指定线开始的点,而第二个函数指定线结束的点:   MoveToEx (hdc, xBeg, yBeg, NULL) ; LineTo (hdc, xEnd, yEnd) ;   MoveToEx doesn't actually draw anything; instead, it sets the attribute of the device context known as the "current position." The LineTo function then draws a straight line from the current position to the point specified in the LineTo function. The current position is simply a starting point for several other GDI functions. In the default device context, the current position is initially set to the point (0, 0). If you call LineTo without first setting the current position, it draws a line starting at the upper left corner of the client area. MoveToEx 实际上什么也不绘制;代替的,它设置叫做“当前位置”的关联设备的属性。然后 LineTo 函数绘制一条从当前位置到在 LineTo 函数中指定的点的直线。当前位置只是用于其它几个 GDI 函数的开始的点。在缺省的关联设备中,当前位置被初始地设置为点(0,0)。如果你没有首先设置当前位置就调用 LineTo,那么它绘制一条从客户区左上角开始的线。   A brief historical note: In the 16-bit versions of Windows, the function to set the current position was MoveTo. This function had just three arguments—the device context handle and x- and y-coordinates. The function returned the previous current position packed as two 16-bit values in a 32-bit unsigned long. However, in the 32-bit versions of Windows, coordinates are 32-bit values. Because the 32-bit versions of C do not define a 64-bit integral data type, this change meant that MoveTo could no longer indicate the previous current position in its return value. Although the return value from MoveTo was almost never used in real-life programming, a new function was required, and this was MoveToEx. 一个简短的历史的注释:在 16 位版本的 Windows 中,设置当前位置的函数是 MoveTo。这个函数只有三个参数——关联设备句柄和 x 和 y 坐标。这个函数返回前一个当前位置,它在 32 位无符号长整形中封装为两个 16 位值。然而,在 32 位版本的 Windows 中,坐标是 32 位值。因为 32 位版本的 C 没有定义 64 位整数数据类型,这个改变意味着 MoveTo 不再在它的返回值中指出前一个当前位置。尽管 MoveTo 的返回值几乎从没用在真正的程序设计中,但是一个新函数是需要的,而它就是 MoveToEx。   The last argument to MoveToEx is a pointer to a POINT structure. On return from the function, the x and y fields of the POINT structure will indicate the previous current position. If you don't need this information (which is almost always the case), you can simply set the last argument to NULL as in the example shown above. MoveToEx 的最后一个参数是 POINT 结构的指针。在由函数返回时,POINT 结构的 x 和 y 域将指出前一个当前位置。如果你不需要这个信息(几乎总是这样),那么你可以像在上面显示的例子中一样只将最后一个参数设置为 NULL。   And now the caveat: Although coordinate values in Windows 98 appear to be 32-bit values, only the lower 16 bits are used. Coordinate values are effectively restricted to -32,768 to 32,767. In Windows NT, the full 32-bit values are used. 那么警告:尽管在 Windows 98 中坐标值表现为 32 位值,但是只有低 16 位是有用的。坐标值严格受限于 -32,768 到 32,767。在 Windows NT 中,全部 32 位值都是有用的。   If you ever need the current position, you can obtain it by calling 如果你需要当前位置,那么你可以通过调用   GetCurrentPositionEx (hdc, &pt) ;   where pt is a POINT structure. 获得它,这里 pt 是 POINT 结构。   The following code draws a grid in the client area of a window, spacing the lines 100 pixels apart starting from the upper left corner. The variable hwnd is assumed to be a handle to the window, hdc is a handle to the device context, and x and y are integers: 下面的代码在窗口的客户区中绘制一个格子,它从左上角开始,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) ; }   Although it seems like a nuisance to be forced to use two functions to draw a single line, the current position comes in handy when you want to draw a series of connected lines. For instance, you might want to define an array of 5 points (10 values) that define the outline of a rectangle: 尽管强制使用两个函数绘制一条单独的线似乎是麻烦的,但是当我们想绘制一系列相连的线时,当前位置很容易取得。例如,你可能想定义一个定义矩形轮廓的 5 个点(10 个值)的数组:   POINT apt[5] = { 100, 100, 200, 100, 200, 200, 100, 200, 100, 100 } ;   Notice that the last point is the same as the first. Now you need only use MoveToEx for the first point and LineTo for the successive points: 注意最后一个点和第一个相同。现在你只需要将 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) ;   Because LineTo draws from the current position up to (but not including) the point in the LineTo function, no coordinate gets written twice by this code. While overwriting points is not a problem with a video display, it might not look good on a plotter or with some drawing modes that I'll discuss later in this chapter. 因为 LineTo 从当前位置一直到(但不包括) LineTo 函数中的点绘制,所以没有坐标被这段代码输出两次。虽然重复输出点没有就视频显示器的问题,但是在绘图机或用我们在本章后面讨论的一些绘图模式时它可能看起来不好。   When you have an array of points that you want connected with lines, you can draw the lines more easily using the Polyline function. This statement draws the same rectangle as in the code shown above: 当你有一个你想用线连接的点的数组时,你可以用 Polyline 函数更容易地绘制这些线。这个语句绘制和上面显示的代码中相同的矩形:   Polyline (hdc, apt, 5) ;   The last argument is the number of points. We could also have represented this value by sizeof (apt) / sizeof (POINT). Polyline has the same effect on drawing as an initial MoveToEx followed by multiple LineTo functions. However, Polyline doesn't use or change the current position. PolylineTo is a little different. This function uses the current position for the starting point and sets the current position to the end of the last line drawn. The code below draws the same rectangle as that last shown above: 最后一个参数是点的数量。我们也可以通过 sizeof(apt)/sizeof(POINT) 代表它。Polyline 有和初始化的 MoveToEx 后面跟着多个 LineTo 函数绘制相同的效果。然而,Polyline 不使用或改变当前位置。PolylineTo 有一点不同。这个函数用当前位置作开始点并设置当前位置为最后绘制的线的终点。下面的代码绘制和最近上面显示的相同的矩形:   MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ; PolylineTo (hdc, apt + 1, 4) ;   Although you can use Polyline and PolylineTo to draw just a few lines, the functions are most useful when you need to draw a complex curve. You do this by using hundreds or even thousands of very short lines. If they're short enough and there are enough of them, together they'll look like a curve. For example, suppose you need to draw a sine wave. The SINEWAVE program in Figure 5-6 shows how to do it. 尽管你可以用 Polyline 和 PolylineTo 绘制仅仅少数线,但是当你需要绘制复杂的曲线时,这些函数是最有用的。你通过使用成百或甚至上千的非常短的线这样做。如果它们是足够短并且有足够的它们,那么它们在一起将看起来像一个曲线。例如,假定你需要绘制一个正弦波。图 5-6 中的 SINEWAVE 程序显示如何这样做。   Figure 5-6. The SINEWAVE program. 图 5-6 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) ; }

The program has an array of 1000 POINT structures. As the for loop is incremented from 0 through 999, the x fields of the POINT structure are set to incrementally increasing values from 0 to cxClient. The program sets the y fields of the POINT structure to sine curve values for one cycle and enlarged to fill the client area. The whole curve is drawn using a single Polyline call. Because the Polyline function is implemented at the device driver level, it is faster than calling LineTo 1000 times. The results are shown in Figure 5-7. 这个程序有一个 1000 个 POINT 结构的数组。随着 for 循环从 0 到 999 增加,POINT 结构的 x 域被设置为从 0 到 cxClient 递增的值。程序设置 POINT 结构的 y 域为一个周期的正弦曲线值并扩大以填充客户区。整个曲线被用单独的 Polyline 调用绘制。因为 Polyline 函数在设备驱动程序级实现,它比调用 LineTo 快 1000 倍。结果在图 5-7 中显示。   Figure 5-7. The SINEWAVE display. 图 5-7 SINEWAVE 显示   The Bounding Box Functions 边界框函数   I next want to discuss the Arc function, which draws an elliptical curve. However, the Arc function does not make much sense without first discussing the Ellipse function, and the Ellipse function doesn't make much sense without first discussing the Rectangle function, and if I discuss Ellipse and Rectangle, I might as well discuss RoundRect, Chord, and Pie. 我接着想讨论 Arc 函数,它绘制一个椭圆曲线。然而,没有首先讨论 Ellipse 函数的话,Arc 函数没有更多的理解,而没有首先讨论 Rectangle 函数的话,Ellipse 函数没有更多的理解,而如果我讨论 Ellipse 和 Rectangle,那么我可能最好讨论 RoundRect,Chord 和 Pie。   The problem is that the Rectangle, Ellipse, RoundRect, Chord, and Pie functions are not strictly line-drawing functions. Yes, the functions draw lines, but they also fill an enclosed area with the current area-filling brush. This brush is solid white by default, so it may not be obvious that these functions do more than draw lines when you first begin experimenting with them. The functions really belong in the later section "Drawing Filled Areas", but I'll discuss them here regardless. 问题是 Rectangle,Ellipse,RoundRect,Chord 和 Pie 函数不是严格的画线函数。是的,这些函数画线,但是它们也用当前填充区域画刷填充封闭的区域。缺省情况下这个画刷是实心的白色,因此当你第一次开始用它们做试验时这些函数做比绘制更多的事可能不是显而易见的。这些函数实际上应归入后面的章节“绘制填充的区域”,但是不管如何我将在这里讨论它们。   The functions I've listed above are all similar in that they are built up from a rectangular "bounding box." You define the coordinates of a box that encloses the object—the bounding box—and Windows draws the object within this box. 我在上面列出的函数在它们都是由矩形的“边界框”绘制这一点上是相似的。你定义围绕对象的框——边界框——的坐标,然后 Windows 在这个框之内绘制该对象。   The simplest of these functions draws a rectangle: 这些函数中最简单的是绘制矩形:   Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;   The point (xLeft, yTop) is the upper left corner of the rectangle, and (xRight, yBottom) is the lower right corner. A figure drawn using the Rectangle function is shown in Figure 5-8. The sides of the rectangle are always parallel to the horizontal and vertical sides of the display. 点(xLeft,yTop)是矩形的左上角,而(xRight,yBottom)是右下角。用 Rectangle 函数绘制的图形在图 5-8 中显示。矩形的边总是平行于显示器的水平和垂直的边。   Figure 5-8. A figure drawn using the Rectangle function. 图 5-8 用 Rectangle 函数绘制的图形   Programmers who have experience with graphics programming are often familiar with "off-by-one" errors. Some graphics programming systems draw a figure to encompass the right and bottom coordinates, and some draw figures up to (but not including) the right and bottom coordinates. Windows uses the latter approach, but there's an easier way to think about it. 有图形程序设计经验的程序员常常熟悉于“off-by-one”错误。一些图形程序设计系统绘制包含右边和底端坐标的图形,而一些绘制一直到(但不保函)右边和底端坐标的图形。Windows 用后一种的方法,但是有一个容易的方法考虑它。   Consider the function call 考虑这个函数调用   Rectangle (hdc, 1, 1, 5, 4) ;   I mentioned above that Windows draws the figure within a "bounding box." You can think of the display as a grid where each pixel is within a grid cell. The imaginary bounding box is drawn on the grid, and the rectangle is then drawn within this bounding box. Here's how the figure would be drawn: 上面我提到 Windows 在“边界框”内绘制图形。你可以把显示器看作是一个每个像素在网格单元内的网格。想象的边界框是在网格上绘制,然后矩形在这个边界框内被绘制。这里是图形将如何被绘制的:     The area separating the rectangle from the top and left of the client area is 1 pixel wide. 把矩形从客户区的顶端和左边分开的区域是 1 像素宽。   As I mentioned earlier, Rectangle is not strictly just a line-drawing function. GDI also fills the enclosed area. However, because by default the area is filled with white, it might not be immediately obvious that GDI is filling the area. 正如我之前提到的,Rectangle 不是严格地只是一个画线函数。GDI 也填充封闭的区域。然而,因为缺省情况下区域是由白色填充的,所以 GDI 正在填充区域可能不是立即地显而易见。   Once you know how to draw a rectangle, you also know how to draw an ellipse, because it uses the same arguments: 一旦你知道如何绘制矩形,那么你也知道如何绘制椭圆,因为它用相同的参数:   Ellipse (hdc, xLeft, yTop, xRight, yBottom) ;   A figure drawn using the Ellipse function is shown (with the imaginary bounding box) in Figure 5-9. 用 Ellipse 函数绘制的图形在图 5-9 中显示(有假想的边界框)。   Figure 5-9. A figure drawn using the Ellipse function. 图 5-9 用 Ellipse 函数绘制的图形   The function to draw rectangles with rounded corners uses the same bounding box as the Rectangle and Ellipse functions but includes two more arguments: 绘制有圆角的函数用和 Rectangle 和 Ellipse 函数相同的边界框,但是它包含两个更多的参数:   RoundRect (hdc, xLeft, yTop, xRight, yBottom, xCornerEllipse, yCornerEllipse) ;   A figure drawn using this function is shown in Figure 5-10. 用这个函数绘制的图形在图 5-10 中显示。   Figure 5-10. A figure drawn using the RoundRect function. 图 5-10 用 RoundRect 函数绘制的图形   Windows uses a small ellipse to draw the rounded corners. The width of this ellipse is xCornerEllipse, and the height is yCornerEllipse. Imagine Windows splitting this small ellipse into four quadrants and using one quadrant for each of the four corners. The rounding of the corners is more pronounced for larger values of xCornerEllipse and yCornerEllipse. If xCornerEllipse is equal to the difference between xLeft and xRight, and yCornerEllipse is equal to the difference between yTop and yBottom, then the RoundRect function will draw an ellipse. Windows 用一个小的椭圆来绘制圆角。这个椭圆的宽度是 xCornerEllipse,而高度是 yCornerEllipse。设想 Windows 将这个小椭圆放进四个象限并且每个象限用于四个角的一个。对于更大的 xCornerEllipse 和 yCornerEllipse 角的圆形更显著。如果 xCornerEllipse 等于 xLeft 和 xRight 之差,而 yCornerEllipse 等于 yTop 和 yBottom 之差,那么 RoundRect 函数将绘制一个椭圆。   The rounded rectangle in Figure 5-10 was drawn using corner ellipse dimensions calculated with the formulas below. 图 5-10 中的圆形矩形是用由下面的公式计算的角上的椭圆尺寸绘制的。   xCornerEllipse = (xRight - xLeft) / 4 ; yCornerEllipse = (yBottom- yTop) / 4 ;   This is an easy approach, but the results admittedly don't look quite right because the rounding of the corners is more pronounced along the larger rectangle dimension. To correct this problem, you'll probably want to make xCornerEllipse equal to yCornerEllipse in real dimensions. 这是一个容易的方法,但是无可否认地这个结果看起来不十分合适,因为角的圆形在矩形的长边更显著。为了改正这个问题,你将可能想使 xCornerEllipse 在实际尺寸上等于 yCornerEllipse。   The Arc, Chord, and Pie functions all take identical arguments: Arc,Chord 和 Pie 都有同样的参数:   Arc (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ; Chord (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ; Pie (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;   A line drawn using the Arc function is shown in Figure 5-11; figures drawn using the Chord and Pie functions are shown in Figures 5-12 and 5-13. Windows uses an imaginary line to connect (xStart, yStart) with the center of the ellipse. At the point at which that line intersects the ellipse, Windows begins drawing an arc in a counterclockwise direction around the circumference of the ellipse. Windows also uses an imaginary line to connect (xEnd, yEnd) with the center of the ellipse. At the point at which that line intersects the ellipse, Windows stops drawing the arc. 用 Arc 函数绘制的线在图 5-11 中显示;用 Chord 和 Pie 函数绘制的图形在图 5-12 和 5-13 中显示。Windows 用一个假想的线连接(xStart,yStart)和椭圆的中心。在这条线与椭圆相交的点,Windows 开始逆时针方向环绕椭圆的周围绘制一个弧。Windows 也用一条假想的线连接(xEnd,yEnd)和椭圆的中心。在这条线与椭圆相交的点,Windows 停止绘制这个弧。     Figure 5-11. A line drawn using the Arc function. 图 5-11 用 Arc 函数绘制的直线     Figure 5-12. A figure drawn using the Chord function. 图 5-12 用 Chord 函数绘制的图形     Figure 5-13. A figure drawn using the Pie function. 图 5-13 用 Pie 函数绘制的图形   For the Arc function, Windows is now finished, because the arc is an elliptical line rather than a filled area. For the Chord function, Windows connects the endpoints of the arc. For the Pie function, Windows connects each endpoint of the arc with the center of the ellipse. The interiors of the chord and pie-wedge figures are filled with the current brush. 对于 Arc 函数,Windows 现在就完成了,因为弧是一条椭圆的线而不是一个填充的区域。对于 Chord 函数,Windows 连接弧的端点。对于 Pie 函数,Windows 连接弧的每个端点和椭圆的中心。弦和扇形的内部是用当前画刷填充的。   You may wonder about this use of starting and ending positions in the Arc, Chord, and Pie functions. Why not simply specify starting and ending points on the circumference of the ellipse? Well, you can, but you would have to figure out what those points are. Windows' method gets the job done without requiring such precision. 你能惊讶于 Arc,Chord 和 Pie 函数中开始和结束的点的这种用法。为什么不简单地指定椭圆的圆周上的开始和结束的点呢?噢,你可以,但是你将不得不计算出那些点是什么。Windows 的方法不需要如此精确度就完成这项工作。   The LINEDEMO program shown in Figure 5-14 draws a rectangle, an ellipse, a rectangle with rounded corners, and two lines, but not in that order. The program demonstrates that these functions that define closed areas do indeed fill them, because the lines are hidden behind the ellipse. The results are shown in Figure 5-15. 图 5-14 中的 LINEDEMO 程序绘制了一个矩形,一个椭圆,一个有圆角的矩形和两条线,但不是按照该顺序。该程序演示这些定义封闭区域的函数的确填充它们,因为这些线隐藏在椭圆背后。结果在图 5-15 中显示。   Figure 5-14. The LINEDEMO program. 图 5-14 LINEDEMO 程序

LINEDEMO.C

/*-------------------------------------------------- LINEDEMO.C -- Line-Drawing 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 ("Program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Line Demonstration"), 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 ; PAINTSTRUCT ps ; switch (message) { 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, 0, cyClient, NULL) ; LineTo (hdc, cxClient, 0) ; 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) ; }

Figure 5-15. The LINEDEMO display. 图 5-15 LINEDEMO 显示   Bezier Splines 贝塞尔曲线   The word "spline" once referred to a piece of flexible wood, rubber, or metal used to draw curves on a piece of paper. For example, if you had some disparate graph points, and you wanted to draw a curve between them (either for interpolation or extrapolation), you'd first mark the points on a piece of graph paper. You'd then anchor a spline to the points and use a pencil to draw the curve along the spline as it bent around the points. 单词“曲尺”曾经指的是惯于在一张纸上绘制曲线的一片柔软的木头,橡胶或金属。例如,如果你有一些不同的图形点,并且你想在它们之间画一条曲线(内插或外插),那么你首先在一张方格纸上标记这些点。然后你将曲尺锚定在这些点上并用铅笔沿着曲尺绕着这些点的方向绘制这条曲线。   Nowadays, of course, splines are mathematical formulas. They come in many different flavors, but the Bezier spline has become the most popular for computer graphics programming. It is a fairly recent addition to the arsenal of graphics tools available on the operating system level, and it comes from an unlikely source: In the 1960s, the Renault automobile company was switching over from a manual design of car bodies (which involved clay) to a computer-based design. Mathematical tools were required, and Pierre Bezier came up with a set of formulas that proved to be useful for this job. 当然,现在曲尺是数学的公式。它们有许多不同的风格,但是贝塞尔曲线曲尺已经成为最受计算机图形程序设计欢迎的。它是直到最近才被增加到在操作系统级上可用的图形工具的工厂的,而它来源于一个不可能的来源:在十九世纪六十年代,雷诺汽车公司从汽车车体的手工设计(它包含粘土)转换到基于计算机的设计。数学的工具是需要的,并且 Pierre Bezier 提出了一套提供给用于该项工作的公式。   Since then, the two-dimensional form of the Bezier spline has shown itself to be the most useful curve (after the straight line and ellipse) for computer graphics. In PostScript, the Bezier spline is used for all curves—even elliptical lines are approximated from Beziers. Bezier curves are also used to define the character outlines of PostScript fonts. (TrueType uses a simpler and faster form of spline.) 其后,二维形式的贝塞尔曲线已经显示自己是对于计算机图形最有用的曲线(在直线和椭圆之后)。在 PostScript 中,贝塞尔曲线用于所有的曲线——即使椭圆线接近贝塞尔曲线。贝塞尔曲线也用于定义 PostScript 字体的字符轮廓。(TrueType 用更简单和更快速的曲尺形式。)   A single two-dimensional Bezier spline is defined by four points—two end points and two control points. The ends of the curve are anchored at the two end points. The control points act as "magnets" to pull the curve away from the straight line between the two end points. This is best illustrated by an interactive program, called BEZIER, which is shown in Figure 5-16. 一条单独的二维贝塞尔曲线由四个点定义——两个端点和两个控制点。曲线的端点锚定在这两个端点。控制点承担“磁铁”的角色,它把曲线拉离两个端点之间的直线。这最好由一个叫做 BEZIER 的交互式程序做说明,它在图 5-16 中显示。   Figure 5-16. The BEZIER program. 图 5-16 BEZIER 程序

BEZIER.C

/*--------------------------------------- BEZIER.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 ("Program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Bezier Splines"), 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 ; } 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[2].x, apt[2].y, NULL) ; LineTo (hdc, apt[3].x, apt[3].y) ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static POINT apt[4] ; HDC hdc ; int cxClient, cyClient ; PAINTSTRUCT ps ; switch (message) { 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_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MOUSEMOVE: if (wParam & MK_LBUTTON || wParam & MK_RBUTTON) { hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (WHITE_PEN)) ; DrawBezier (hdc, apt) ; if (wParam & MK_LBUTTON) { apt[1].x = LOWORD (lParam) ; apt[1].y = HIWORD (lParam) ; } if (wParam & MK_RBUTTON) { 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: InvalidateRect (hwnd, NULL, TRUE) ; 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) ; }

Because this program uses some mouse processing logic that we won't learn about until Chapter 7, I won't discuss its inner workings (which might be obvious nonetheless). Instead, you can use the program to experiment with manipulating Bezier splines. In this program, the two end points are set to be halfway down the client area, and ¼ and ¾ of the way across the client area. The two control points are manipulable, the first by pressing the left mouse button and moving the mouse, the second by pressing the right mouse button and moving the mouse. Figure 5-17 shows a typical display. 因为这个程序用了一些直到第 7 章我们才会了解的鼠标处理逻辑,所以我不讨论它的内部工作方式(但是这可能是显而易见的)。然而,你可以用这个程序试验操作贝塞尔曲线。在这个程序中,这两个端点被设置为客户区的上下居中,并且左右 1/4 和 1/4 处。这两个控制点是可操作的,第一个通过压住鼠标左键并移动鼠标,第二个通过压住鼠标右键并移动鼠标。图 5-17 显示一个典型的显示。   Aside from the Bezier spline itself, the program also draws a straight line from the first control point to the first end point (also called the begin point) at the left, and from the second control point to the end point at the right. 除贝塞尔曲线本身外,程序也绘制从第一个控制点到左边第一个端点(也叫做起点)和第二个控制点到右边端点的直线。   Bezier splines are considered to be useful for computer-assisted design work because of several characteristics. First, with a little practice, you can usually manipulate the curve into something close to a desired shape. 由于几个特征,贝塞尔曲线被认为是对计算机辅助设计工作有用的。首先,用一点练习,你通常可以操纵曲线到一些接近想要的形状。     Figure 5-17. The BEZIER display. 图 5-17 BEZIER 显示   Second, the Bezier spline is very well controlled. In some splines, the curve does not pass through any of the points that define the curve. The Bezier spline is always anchored at the two end points. (This is one of the assumptions that is used to derive the Bezier formulas.) Also, some forms of splines have singularities where the curve veers off into infinity. In computer-based design work, this is rarely desired. The Bezier curve never does this; indeed, it is always bounded by a four-sided polygon (called a "convex hull") that is formed by connecting the end points and control points. 其次,贝塞尔曲线非常好控制。在一些曲尺中,曲线不经过定义曲线的任何点。贝塞尔曲线总是锚定在两个端点。(这是用来得出贝塞尔公式的假定之一。)同样,一些形式的曲尺有奇异点,在这里曲线转向无穷远。在计算机算机设计的工作中,这是很少想要的。贝塞尔曲线从来不这么做;代替的是,它总是由一个四边形限制(叫做“凸壳”),它由端点和控制点连接形成。   Third, another characteristic of the Bezier spline involves the relationship between the end points and the control points. The curve is always tangential to and in the same direction as a straight line draw from the begin point to the first control point. (This is visually illustrated by the Bezier program.) Also, the curve is always tangential to and in the same direction as a straight line drawn from the second control point to the end point. These are two other assumptions used to derive the Bezier formulas. 第三,贝塞尔曲线的另一个特征包含端点和控制点的关系。曲线总是相切于从起点到第一个控制点绘制的直线并且和它有相同的方向。(这由 Bezier 程序可视地说明。)同样,曲线总是相切于从第二个控制点到另一个端点绘制的直线并且和它有相同的方向。   Fourth, the Bezier spline is often aesthetically pleasing. I know this is a subjective criterion, but I'm not the only person who thinks so. 第四,贝塞尔曲线常常是具有美感的。我知道这是一个主观的标准,但是我不是唯一这样认为的人。   Prior to the 32-bit versions of Windows, you'd have to create your own Bezier splines using the Polyline function. You would also need knowledge of the following parametric equations for the Bezier spline. The begin point is (x0, y0), and the end point is (x3, y3). The two control points are (x1, y1) and (x2, y2). The curve is drawn for values of t ranging from 0 to 1: 在 32 位版本的 Windows 之前,你将不得不用 Polyline 函数创建你自己的贝塞尔曲线。你也将需要下面的贝塞尔曲线的参数方程的知识。起点是(x0,y0),而终点时(x3,y3)。两个控制点是(x1,y1)和(x2,y2)。曲线根据范围从 0 到 1 的 t 值绘制:   x(t) = (1 - t)3 x0 + 3t (1 - t)2 x1 + 3t2 (1 - t) x2 + t3 x3 y(t) = (1 - t)3 y0 + 3t (1 - t)2 y1 + 3t2 (1 - t) y2 + t3 y3   You don't need to know these formulas in Windows 98. To draw one or more connected Bezier splines, you simply call 在 Windows 98 下你不需要知道这些公式。为了绘制一或多条连接的贝塞尔曲线,你只需要调用   PolyBezier (hdc, apt, iCount) ;   or 或   PolyBezierTo (hdc, apt, iCount) ;   In both cases, apt is an array of POINT structures. With PolyBezier, the first four points indicate (in this order) the begin point, first control point, second control point, and end point of the first Bezier curve. Each subsequent Bezier requires only three more points because the begin point of the second Bezier curve is the same as the end point of the first Bezier curve, and so on. The iCount argument is always one plus three times the number of connected curves you're drawing. 在这两种情况下,apt 是一个 PONIT 结构的数组。用 PolyBezier,前四个点指出(按这个顺序)第一条贝塞尔曲线的起点,第一个控制点,第二个控制点和终点。后面的每个贝塞尔曲线只需要另外三个点,因为第二条贝塞尔曲线的起点和第一条贝塞尔曲线的终点相同,诸如此类。iCount 参数总是等于 1 加上你正在绘制的相连的曲线的数量的三倍。   The PolyBezierTo function uses the current position for the first begin point. The first and each subsequent Bezier spline requires only three points. When the function returns, the current position is set to the last end point. PolyBezierTo 函数用当前位置作第一个起点。第一条和后面的每一条贝塞尔曲线只需要三个点。当函数返回时,当前位置被设为最后一个终点。   One note: when you draw a series of connected Bezier splines, the point of connection will be smooth only if the second control point of the first Bezier, the end point of the first Bezier (which is also the begin point of the second Bezier), and the first control point of the second Bezier are colinear; that is, they lie on the same straight line. 一个提示:当你绘制一系列相连的贝塞尔曲线时,连接点将只在第一条贝塞尔曲线的第二个控制点,第一条贝塞尔曲线的终点(她也是第二条贝塞尔曲线的起点)和第二条贝塞尔曲线的第一个控制点是线性相关时才是光滑的;也就是说,它们位于相同的直线上。   Using Stock Pens 用备用的画笔   When you call any of the line-drawing functions that I've discussed in this section, Windows uses the "pen" currently selected in the device context to draw the line. The pen determines the line's color, its width, and its style, which can be solid, dotted, or dashed. The pen in the default device context is called BLACK_PEN. This pen draws a solid black line with a width of one pixel. BLACK_PEN is one of three "stock pens" that Windows provides. The other two are WHITE_PEN and NULL_PEN. NULL_PEN is a pen that doesn't draw. You can also create your own customized pens. 当你调用我在本章节中讨论的任何画线函数时,Windows 用选进关连设备中的当前“画笔”绘制线。画笔确定线的颜色,它的宽度和它的风格(它可以是实心的,点的或线的)。在缺省的关联设备中的画笔叫做 BLACK_PEN。这个画笔绘制有一像素宽的实心的黑色的线。BLACK_PEN 是 Windows 提供的三个“备用画笔”之一。另外两个是 WHITE_PEN 和 NULL_PEN。NULL_PEN 是不绘制的画笔。你也可以创建你自己的自定义画笔。   In your Windows programs, you refer to pens by using a handle. The Windows header file WINDEF.H defines the type HPEN, a handle to a pen. You can define a variable (for instance, hPen) using this type definition: 在你的 Windows 程序中,你通过使用句柄引用画笔。Windows 头文件 WINDEF.H 定义 HPEN 类型,它是画笔的句柄。你可以用这个类型定义定义一个变量(例如,hPen):   HPEN hPen ;   You obtain the handle to one of the stock pens by a call to GetStockObject. For instance, suppose you want to use the stock pen called WHITE_PEN. You get the pen handle like this: 你通过调用 GetStockObject 获得备用画笔之一的句柄。例如,假设你想用叫做 WHITE_PEN 的备用画笔。你像这样获得该画笔句柄:   hPen = GetStockObject (WHITE_PEN) ;   Now you must "select" that pen into the device context: 现在你必须“选择”该画笔进入关联设备:   SelectObject (hdc, hPen) ;   Now the white pen is the current pen. After this call, any lines you draw will use WHITE_PEN until you select another pen into the device context or release the device context handle. 现在这个白色画笔是当前画笔。在这个调用之后,你绘制的任何线将使用 WHITE_PEN,直到你选择另一个画笔进入关联设备或者释放关联设备句柄。   Rather than explicitly defining an hPen variable, you can instead combine the GetStockObject and SelectObject calls in one statement: 除了明确地定义一个 hPen 变量之外,你还可以在一个语句中联合使用 GetStockObject 和 SelectObject:   SelectObject (hdc, GetStockObject (WHITE_PEN)) ;   If you then want to return to using BLACK_PEN, you can get the handle to that stock object and select it into the device context in one statement: 然后如果你想恢复到使用 BLACK_PEN,那么你可以在一个语句中获得那个备用对象的句柄并且选择它进入关联设备:   SelectObject (hdc, GetStockObject (BLACK_PEN)) ;   SelectObject returns the handle to the pen that had been previously selected into the device context. If you start off with a fresh device context and call SelectObject 返回先前被选进关联设备的画笔的句柄。如果你用一个新的关联设备开始并且调用   hPen = SelectObject (hdc, GetStockobject (WHITE_PEN)) ;   the current pen in the device context will be WHITE_PEN and the variable hPen will be the handle to BLACK_PEN. You can then select BLACK_PEN into the device context by calling 那么在该关联设备中的当前画笔将是 WHITE_PEN 并且变量 hPen 将是 BLACK_PEN 的句柄。然后你可以通过调用   SelectObject (hdc, hPen) ;   选择 BLACK_PEN 进关联设备。   Creating, Selecting, and Deleting Pens 创建,选择和删除画笔   Although the pens defined as stock objects are certainly convenient, you are limited to only a solid black pen, a solid white pen, or no pen at all. If you want to get fancier than that, you must create your own pens. 尽管定义为备用对象的画笔的确是方便的,但是你受限于只有实心黑色画笔,实心白色画笔或根本没有画笔。如果你想得到比这些更特别的,那么你必须创建你自己的画笔。   Here's the general procedure: You create a "logical pen," which is merely a description of a pen, using the function CreatePen or CreatePenIndirect. These functions return a handle to the logical pen. You select the pen into the device context by calling SelectObject. You can then draw lines with this new pen. Only one pen can be selected into the device context at any time. After you release the device context (or after you select another pen into the device context) you can delete the logical pen you've created by calling DeleteObject. When you do so, the handle to the pen is no longer valid. 这里是通常的过程:你用 CreatePen 或 CreatePenIndirect 创建一个“逻辑画笔”,它仅仅是画笔的描述。这些函数返回逻辑画笔的句柄。你通过调用 SelectObject 选择画笔进入关联设备。然后你可以用这个新的画笔绘制线。任何时间只有一个画笔可以被选进关联设备。在你释放关联设备句柄之后(或者在你选择另一个画笔进入关联设备之后),你可以通过调用 DeleteObject 删除你创建的逻辑画笔。当你这么做时,画笔的句柄不再是可用的。   A logical pen is a "GDI object," one of six GDI objects a program can create. The other five are brushes, bitmaps, regions, fonts, and palettes. Except for palettes, all of these objects are selected into the device context using SelectObject. 逻辑画笔是“GDI 对象”,它是程序可以创建的六个 GDI 对象之一。其它五个分别是画刷,位图,区域,字体和调色板。除调色板以外,所有这些对象都用 SelectObject 选进关联设备。   Three rules govern the use of GDI objects such as pens: 三个规则管理诸如画笔的 GDI 对象的用法: You should eventually delete all GDI objects that you create. 你应该最终删除你创建的所有 GDI 对象。 Don't delete GDI objects while they are selected in a valid device context. 当 GDI 对象被选进可用的关联设备时不要删除它们。 Don't delete stock objects. 不要删除备用对象。 These are not unreasonable rules, but they can be a little tricky sometimes. We'll run through some examples to get the hang of how the rules work. 这些不是不合理的规则,但是它们有时可以有一点变化。我将通过运行一些例子以掌握这些规则是如何工作的。   The general syntax for the CreatePen function looks like this: CreatePen 函数的一般语法看起来像这样:   hPen = CreatePen (iPenStyle, iWidth, crColor) ;   The iPenStyle argument determines whether the pen draws a solid line or a line made up of dots or dashes. The argument can be one of the following identifiers defined in WINGDI.H. Figure 5-18 shows the kind of line that each style produces. iPenStyle 参数确定画笔是绘制实心的线还是由点或线组成的线。该参数可以是在 WINGDI.H 中定义的下面的标识符之一。图 5-18 显示了每种风格产生的线的种类。

Figure 5-18. The seven pen styles. 图 5-18 七种画笔风格   For the PS_SOLID, PS_NULL, and PS_INSIDEFRAME styles, the iWidth argument is the width of the pen. An iWidth value of 0 directs Windows to use one pixel for the pen width. The stock pens are 1 pixel wide. If you specify a dotted or dashed pen style with a physical width greater than 1, Windows will use a solid pen instead. 对于 PS_SOLID,PS_NULL 和 PS_INSIDEFRAME 风格,iWidth 参数是画笔的宽度。0 的 iWidth 值指示 Windows 用一个像素作为画笔的宽度。备用画笔是 1 像素宽。如果你指定有比 1 更大的物理宽度的点或线画笔风格,那么 Windows 将使用实心的笔。   The crColor argument to CreatePen is a COLORREF value specifying the color of the pen. For all the pen styles except PS_INSIDEFRAME, when you select the pen into the device context, Windows converts the color to the nearest pure color that the device can render. The PS_INSIDEFRAME is the only pen style that can use a dithered color, and then only when the width is greater than 1. CreatePen 的 crColor 参数是指定画笔颜色的 COLORREF 值。对于除 PS_INSIDEFRAME 之外的所有画笔风格,当你选择画笔进入关联设备时,Windows 将该颜色转换为设备可以呈现的最近的纯色。PS_INSIDEFRAME 是唯一可以用抖动颜色的画笔风格,并且只在宽度比 1 大时。   The PS_INSIDEFRAME style has another peculiarity when used with functions that define a filled area. For all pen styles except PS_INSIDEFRAME, if the pen used to draw the outline is greater than 1 pixel wide, then the pen is centered on the border so that part of the line can be outside the bounding box. For the PS_INSIDEFRAME pen style, the entire line is drawn inside the bounding box. PS_INSIDEFRAME 风格在用定义已填充区域的函数时有另一个特性。对于除 PS_INSIDEFRAME 之外的所有画笔风格,如果用来绘制轮廓的画笔比 1 像素宽度更大,那么画笔是在边界中心,以便线的局部可以在边界框之外。对于 PS_INSIDEFRAME 画笔风格,整条线在边界框内绘制。   You can also create a pen by setting up a structure of type LOGPEN ("logical pen") and calling CreatePenIndirect. If your program uses a lot of different pens that you initialize in your source code, this method is probably more efficient. 你也可以通过使用 LOGPEN(“逻辑画笔”)类型的结构并调用 CreatePenIndirect 创建画笔。如果你的程序使用大量你在你的源代码中初始化的不同的画笔,那么这个方法可能是更有效的。   To use CreatePenIndirect, first you define a structure of type LOGPEN: 为了使用 CreatePenIndirect,首先你定义一个 LOGPEN 类型的结构:   LOGPEN logpen ;   This structure has three members: lopnStyle (an unsigned integer or UINT) is the pen style, lopnWidth (a POINT structure) is the pen width in logical units, and lopnColor (COLORREF) is the pen color. Windows uses only the x field of the lopnWidth structure to set the pen width; it ignores the y field. 这个结构有三个成员:lopnStyle(一个无符号整数或 UINT)是画笔风格,lopnWidth(一个 POINT 结构)是逻辑单位的画笔宽度,而 lopnColor(COLORREF)是画笔颜色。Windows 只用 lopnWidth 结构的 x 域设置画笔的宽度;它忽略 y 域。   You create the pen by passing the address of the structure to CreatePenIndirect: 你通过将该结构的地址传递给 CreatePenIndirect 创建画笔:   hPen = CreatePenIndirect (&logpen) ;   Note that the CreatePen and CreatePenIndirect functions do not require a handle to a device context. These functions create logical pens that have no connection with a device context until you call SelectObject. You can use the same logical pen for several different devices, such as the screen and a printer. 注意 CreatePen 和 CreatePenIndirect 函数不需要关联设备的句柄。这些函数创建逻辑画笔,它直到你调用 SelectObject 才和关联设备有联系。你可以将相同的逻辑画笔用于几个不同的设备,比如屏幕和打印机。   Here's one method for creating, selecting, and deleting pens. Suppose your program uses three pens—a black pen of width 1, a red pen of width 3, and a black dotted pen. You can first define static variables for storing the handles to these pens: 这里是一个创建,选择和删除画笔的方法。假设你的程序用三个画笔——一个宽度为 1 的黑色画笔,一个宽度为 3 的红色画笔和一个黑色的点画笔。你可以首先定义存储这些画笔的句柄的静态变量:   static HPEN hPen1, hPen2, hPen3 ;   During processing of WM_CREATE, you can create the three pens: 在 WM_CREATE 处理期间,你可以创建这三个画笔:   hPen1 = CreatePen (PS_SOLID, 1, 0) ; hPen2 = CreatePen (PS_SOLID, 3, RGB (255, 0, 0)) ; hPen3 = CreatePen (PS_DOT, 0, 0) ;   During processing of WM_PAINT (or any other time you have a valid handle to a device context), you can select one of these pens into the device context and draw with it: 在 WM_PAINT 处理期间(或者你有有效的关联设备的句柄的任何其它时间),你可以选择这些画笔之一进入关联设备并且用它绘制:   SelectObject (hdc, hPen2) ; [ line-drawing functions ] SelectObject (hdc, hPen1) ; [ line-drawing functions ]   During processing of WM_DESTROY, you can delete the three pens you created: 在 WM_DESTROY 处理期间,你可以删除你创建的三个画笔:   DeleteObject (hPen1) ; DeleteObject (hPen2) ; DeleteObject (hPen3) ;   This is the most straightforward method of creating selecting, and deleting pens, but obviously your program must know what pens will be needed. You might instead want to create the pens during each WM_PAINT message and delete them after you call EndPaint. (You can delete them before calling EndPaint, but you have to be careful not to delete the pen currently selected in the device context.) 这是最简单的创建,选择和删除画笔的方法,但是显而易见地你的程序必须知道什么画笔将是需要的。你可能想在每个 WM_PAINT 消息期间创建画笔并且在你调用 EndPaint 之后删除它们。(你可以在调用 EndPaint 之前删除它们,但是你必须小心不要删除当前选进关联设备中的画笔。)   You might want to create pens on the fly and combine the CreatePen and SelectObject calls in the same statement: 你可能想创建不工作的画笔并且在同一个语句中联合 CreatePen 和 SelectObject 调用:   SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;   Now when you draw lines, you'll be using a red dashed pen. When you're finished drawing the red dashed lines, you can delete the pen. Whoops! How can you delete the pen when you haven't saved the pen handle? Recall that SelectObject returns the handle to the pen previously selected in the device context. This means that you can delete the pen by selecting the stock BLACK_PEN into the device context and deleting the value returned from SelectObject: 现在当你绘制线时,你将用红色的线画笔。当你完成绘制红色的点线时,你可以删除这个画笔。哎呦!当你没有保存画笔句柄是你如何可以删除画笔?回想一下 SelectObject 返回先前选进关联设备的画笔的句柄。这意味着你可以通过选择备用的 BLACK_PEN 进入关联设备以删除该画笔并删除由 SelectObject 返回的值:   DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;   Here's another method. When you select a pen into a newly created device context, save the handle to the pen that SelectObject returns: 这里是另一种方法。当你选择画笔进入新创建的关联设备中时,保存 SelectObject 返回的画笔句柄:   hPen = SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;   What is hPen? If this is the first SelectObject call you've made since obtaining the device context, hPen is a handle to the BLACK_PEN stock object. You can now select that pen into the device context and delete the pen you create (the handle returned from this second SelectObject call) in one statement: hPen 是什么?如果它是自获得关联设备以来你进行的第一次 SelectObject 调用,那么 hPen 是 BLACK_PEN 备用对象的句柄。你现在可以在一个语句中选择该画笔进入关联设备并删除你创建的该画笔:   DeleteObject (SelectObject (hdc, hPen)) ;   If you have a handle to a pen, you can obtain the values of the LOGPEN structure fields by calling GetObject: 如果你有画笔句柄,那么你可以通过调用 GetObject 获得 LOGPEN 结构各域的值:   GetObject (hPen, sizeof (LOGPEN), (LPVOID) &logpen) ;   If you need the pen handle currently selected in the device context, call 如果你需要当前选进关联设备的画笔句柄,那么调用   hPen = GetCurrentObject (hdc, OBJ_PEN) ;   I'll discuss another pen creation function, ExtCreatePen, in Chapter 17. 我将在第 17 章中讨论另一个画笔创建函数 ExtCreatePen。   Filling in the Gaps 在间隙中填充   The use of dotted and dashed pens raises the question: what happens to the gaps between the dots and dashes? Well, what do you want to happen? 点和线画笔的用法产生一个问题:点和线之间的间隙发生了什么?噢,你希望发生什么?   The coloring of the gaps depends on two attributes of the device context—the background mode and the background color. The default background mode is OPAQUE, which means that Windows fills in the gaps with the background color, which by default is white. This is consistent with the WHITE_BRUSH that many programs use in the window class for erasing the background of the window. 间隙的着色依赖于关联设备的两个属性——背景模式和背景颜色。缺省的背景模式时 OPAQUE,这意味着 Windows 用背景颜色(缺省情况下它是白色)填充间隙。这和许多程序在窗口类中用来清除窗口背景的 WHITE_BRUSH 是一致的。   You can change the background color that Windows uses to fill in the gaps by calling 你可以通过调用   SetBkColor (hdc, crColor) ;   As with the crColor argument used for the pen color, Windows converts this background color to a pure color. You can obtain the current background color defined in the device context by calling GetBkColor. 改变 Windows 用来填充间隙的背景颜色。和惯用于画笔颜色的 crColor 参数一样,Windows 将该背景颜色转换为纯颜色。你可以通过调用 GetBkColor 获得在关联设备中定义的当前背景颜色。   You can also prevent Windows from filling in the gaps by changing the background mode to TRANSPARENT: 你也可以通过改变背景模式为 TRANSPARENT 防止 Windows 在间隙中填充:   SetBkMode (hdc, TRANSPARENT) ;   Windows will then ignore the background color and not fill in the gaps. You can obtain the current background mode (either TRANSPARENT or OPAQUE) by calling GetBkMode. 然后 Windows 将忽略背景颜色并在间隙中填充。你可以通过调用 GetBkMode 获得当前背景模式(TRANSPARENT 或 OPAQUE)。   Drawing Modes 绘制模式   The appearance of lines drawn on the display is also affected by the drawing mode defined in the device context. Imagine drawing a line that has a color based not only on the color of the pen but also on the color of the display area where the line is drawn. Imagine a way in which you could use the same pen to draw a black line on a white surface and a white line on a black surface without knowing what color the surface is. Could such a facility be useful to you? It's made possible by the drawing mode. 在显示器上绘制的线的外观也受在关联设备中定义的绘制模式影响。想象一下绘制一条线,它的颜色不但基于画笔的颜色,而且基于线被绘制的显示器区域的颜色。想象一下你不用知道表面的颜色是什么,就可以用相同的画笔在白色的表面上绘制一条黑色的线并且在黑色的表面上绘制一条白色的线。这样的方便性对你来说有用吗?它可能由绘制模式使用。   When Windows uses a pen to draw a line, it actually performs a bitwise Boolean operation between the pixels of the pen and the pixels of the destination display surface, where the pixels determine the color of the pen and display surface. Performing a bitwise Boolean operation with pixels is called a "raster operation," or "ROP." Because drawing a line involves only two pixel patterns (the pen and the destination), the Boolean operation is called a "binary raster operation," or "ROP2." Windows defines 16 ROP2 codes that indicate how Windows combines the pen pixels and the destination pixels. In the default device context, the drawing mode is defined as R2_COPYPEN, meaning that Windows simply copies the pixels of the pen to the destination, which is how we normally think about pens. There are 15 other ROP2 codes. 当 Windows 用画笔绘制线时,它实际在画笔的像素和目标显示表面的像素之间(这里的像素决定画笔和显示表面的颜色)执行一个或布尔操作。执行有像素的或布尔操作叫做“光栅操作”或“ROP”。因为绘制线只包含两种像素模式(画笔和目标),布尔操作叫做“二进制光栅操作”或“ROP2”。Windows 定义了 16 种指出 Windows 如何联合画笔像素和目标像素的 ROP2 编码。在缺省的关联设备中,绘制模式定义为 R2_COPYPEN,这意味着 Windows 只是将画笔的像素拷贝到目标,我们通常就是这样看待画笔的。有 15 种其它 ROP2 编码。   Where do these 16 different ROP2 codes come from? For illustrative purposes, let's assume a monochrome system that uses 1 bit per pixel. The destination color (the color of the window's client area) can be either black (which we'll represent by a 0 pixel) or white (represented by a 1 pixel). The pen also can be either black or white. There are four combinations of using a black or white pen to draw on a black or white destination: a white pen on a white destination, a white pen on a black destination, a black pen on a white destination, and a black pen on a black destination. 这 16 种不同的 ROP2 来自于什么地方?为了说明性的设想,让我们假定一个每像素用 1 位的单色系统。目标颜色(窗口的客户区的颜色)可以是黑色(我们用 0 像素代表)或白色(用 1 像素代表)。画笔也可以是黑色或白色的。有四种使用黑色或白色画笔在黑色或白色目标上绘制的组合:白色画笔在白色目标上,白色画笔在黑色目标上,黑色画笔在白色目标上以及黑色画笔在黑色目标上。   What is the color of the destination after you draw with the pen? One possibility is that the line is always drawn as black regardless of the pen color or the destination color. This drawing mode is indicated by the ROP2 code R2_BLACK. Another possibility is that the line is drawn as black except when both the pen and destination are black, in which case the line is drawn as white. Although this might be a little strange, Windows has a name for it. The drawing mode is called R2_NOTMERGEPEN. Windows performs a bitwise OR operation on the destination pixels and the pen pixels and then inverts the result. 在你用该画笔绘制后目标的颜色是什么?一种可能是无论画笔颜色或目标颜色是什么,该线总是绘制为黑色。这种绘制模式由 ROP2 编码 R2_BLACK 指出。另一种可能是除了当画笔和目标都是黑色时该线绘制为黑色之外,在其它情况下该线绘制为白色。尽管这可能有一点不习惯,但是 Windows 已经为它命名。该绘制模式叫做 R2_NOTMERGEPEN。Windows 在目标像素和画笔像素上执行位或操作,然后转换结果。   The table below shows all 16 ROP2 drawing modes. The table indicates how the pen (P) and destination (D) colors are combined for the result. The column labeled "Boolean Operation" uses C notation to show how the destination pixels and pen pixels are combined. 下表显示所有 16 种 ROP2 绘制模式。该标指出画笔(P)和目标(D)颜色是如何组合为该结果的。标有“布尔操作”的列用 C 符号显示目标像素和画笔像素是如何组合的。

Pen (P):

Destination (D):

1 1 0 0

1 0 1 0

Boolean Operation Drawing Mode Results: 0 0 0 0 0 R2_BLACK 0 0 0 1 ~(P ¦ D) R2_NOTMERGEPEN 0 0 1 0 ~P & D R2_MASKNOTPEN 0 0 1 1 ~P R2_NOTCOPYPEN 0 1 0 0 P & ~D R2_MASKPENNOT 0 1 0 1 ~D R2_NOT 0 1 1 0 P ^ D R2_XORPEN 0 1 1 1 ~(P & D) R2_NOTMASKPEN 1 0 0 0 P & D R2_MASKPEN 1 0 0 1 ~(P ^ D) R2_NOTXORPEN 1 0 1 0 D R2_NOP 1 0 1 1 ~P ¦ D R2_MERGENOTPEN 1 1 0 0 P R2_COPYPEN (default) 1 1 0 1 P ¦ ~D R2_MERGEPENNOT 1 1 1 0 P ¦ D R2_MERGEPEN 1 1 1 1 1 R2_WHITE You can set a new drawing mode for the device context by calling 你可以通过调用   SetROP2 (hdc, iDrawMode) ;   The iDrawMode argument is one of the values listed in the "Drawing Mode" column of the table. You can obtain the current drawing mode by using the function: 为关联设备设置新的绘着模式。iDrawMode 参数是在该表的“绘制模式”列中列出的值之一。你可以通过调用下面的函数获得当前绘制模式:   iDrawMode = GetROP2 (hdc) ;   The device context default is R2_COPYPEN, which simply transfers the pen color to the destination. The R2_NOTCOPYPEN mode draws white if the pen color is black and black if the pen color is white. The R2_BLACK mode always draws black, regardless of the color of the pen or the background. Likewise, the R2_WHITE mode always draws white. The R2_NOP mode is a "no operation." It leaves the destination unchanged. 关联设备缺省是 R2_COPYPEN,它只是将画笔颜色转换为目标颜色。如果画笔颜色是黑色,那么 R2_NOTCOPYPEN 模式绘制白色;如果画笔颜色是白色,那么该模式绘制黑色。R2_BLACK 模式总是绘制黑色,不管画笔或背景的颜色。同样地,R2_WHITE 模式总是绘制白色。R2_NOP 模式是“不操作”。它允许目标是未改变的。   We've been examining the drawing mode in the context of a monochrome system. Most systems are color, however. On color systems Windows performs the bitwise operation of the drawing mode for each color bit of the pen and destination pixels and again uses the 16 ROP2 codes described in the previous table. The R2_NOT drawing mode always inverts the destination color to determine the color of the line, regardless of the color of the pen. For example, a line drawn on a cyan destination will appear as magenta. The R2_NOT mode always results in a visible pen except if the pen is drawn on a medium gray background. I'll demonstrate the use of the R2_NOT drawing mode in the BLOKOUT programs in Chapter 7. 我们曾考查过单色系统的内容中的绘制模式。然而,大多数系统是彩色的。在彩色系统上 Windows 执行适合于画笔和目标像素的每个颜色位的绘制模式的位操作,并且再次使用在先前的表中描述的 16 种 ROP2 编码。R2_NOT 绘制模式总是将目标颜色转换为线的确定颜色,无论画笔的颜色如何。例如,在青色目标上绘制的线将显示为洋红色。R2_NOT 模式总是取决于可见的画笔,除了如果画笔在中灰色背景上绘制之外。我将在第 7 章中的 BLOKOUT 程序中演示 R2_NOT 绘制模式的用法。

本文地址:http://com.8s8s.com/it/it26429.htm