Points, Lines, Polygons, and CirclesNow that you have the concept of pens and brushes under your belt, it's time to see how these entities are used in real programs to draw objects. Let's start with the simplest of all graphic objects—the point. Straight to the PointDrawing points with GDI is trivial and doesn't require a pen or a brush. That's because a point is a single pixel and selecting a pen or brush wouldn't have much of an effect. To draw a point within the client area of your window, you need the HDC to your window along with the coordinates and the color you wish to draw it with. However, you don't need to select the color or anything like that—you simply make a function call to SetPixel() with all this information. Take a look: COLORREF SetPixel(HDC hdc, // the graphics context int x, // x-coordinate int y, // y-coordinate COLORREF crColor); // color of pixel The function takes the HDC to the window along with the (x,y) coordinate and the color. The function then plots the pixel and returns the color actually plotted. You see, if you are in a 256 color mode and request an RGB color that doesn't exist, GDI will plot a closest match to the color for you, and either way return the RGB color that was actually plotted. If you're a little uneasy about the exact meaning of the (x,y) coordinates that you send the function, take a look at Figure 4.2. The figure depicts a window and the coordinate system that Windows GDI uses, which is an inverted Quadrant I Cartesian system—meaning that the x increases from left to right and y increases from top to bottom. Figure 4.2. Windows coordinates in relation to standard Cartesian coordinates.Technically, GDI has other mapping modes, but this is the default and the one to use for all GDI and DirectX. Notice that the origin (0,0) is in the upper-left corner of the window's client area. It's possible to get an HDC for the entire window with GetWindowDC() rather than GetDC(). The difference is that if you use GetWindowDC() to retrieve an HDC, the graphics device context is for the whole window. With an HDC retrieved with GetWindowDC(), you can draw over everything including the window controls, not just the client area. Here's an example of drawing 1000 randomly positioned and colored pixels on a window that we know is 400x400: HWND hwnd; // assume this is valid HDC hdc; // used to access window // get the dc for the window hdc = GetDC(hwnd); for (int index=0; index<1000; index++) { // get random position int x = rand()%400; int y = rand()%400; COLORREF color = RGB(rand()%255,rand()%255,rand()%255); SetPixel(hdc, x,y, color); }// end for index As an example of plotting pixels, take a look at DEMO4_1.CPP and DEMO4_1.EXE. They illustrate the preceding code, but in a continuous loop. Figure 4.3 is a screen shot of the program running. Figure 4.3. Demo of pixel-plotting program DEMO4_1.EXE.Getting a Line on ThingsNow let's draw the next most primitive complex—the line. To draw a line, we need to create the pen, and then make a call to the line-drawing function. Under GDI, lines are little more complex than that. GDI likes to draw lines in a three-step process:
In essence, GDI has a little invisible cursor that tracks the current starting position of a line to be drawn. This position must be set by you if you want to draw a line, but once it's set, GDI will update it with every segment you draw, facilitating drawing complex objects like polygons. The function to set the initial position of the line cursor is called MoveToEx(): BOOL MoveToEx(HDC hdc, // handle of device context int X, // x-coordinate of new current position int Y, // y-coordinate of new current position LPPOINT lpPoint ); // address of old current position Suppose you wanted to draw a line from (10,10) to (50,60). You would first make a call to MoveToEx() like this: // set current position MoveToEx(hdc, 10,10,NULL); Notice the NULL for the last position parameter. If you wanted to save the last position, do this: POINT last_pos; // used to store last position // set current position, but save last MoveToEx(hdc, 10,10, &last_pos); By the way, here's a POINT structure again just in case you forgot: typedef struct tagPOINT { // pt LONG x; LONG y; } POINT; Okay, once you have set the initial position of the line, you can draw a segment with a call to LineTo(): BOOL LineTo(HDC hdc, // device context handle int xEnd, // destination x-coordinate int yEnd);// destination y-coordinate As a complete example of drawing a line, here's how you would draw a solid green line from (10,10) to (50,60): HWND hwnd; // assume this is valid // get the dc first HDC hdc = GetDc(hwnd); // create the green pen HPEN green_pen = CreatePen(PS_SOLID, 1, RGB(0,255,0)); // select the pen into the context HPEN old_pen = SelectObject(hdc, green_pen); // draw the line MoveToEx(hdc, 10,10, NULL); LineTo(hdc,50,60); // restore old pen SelectObject(hdc, old_pen); // delete the green pen DeleteObject(green_pen); // release the dc ReleaseDC(hwnd, hdc); If you wanted to draw a triangle with the vertices (20,10), (30,20), (10,20), here's the line drawing code: // start the triangle MoveToEx(hdc, 20,10, NULL); // draw first leg LineTo(hdc,30,20); // draw second leg LineTo(hdc,10,20); // close it up LineTo(hdc,20,10); You can see why using the MoveToEx()—LineTo() technique is useful. As a working example of drawing lines, take a look at DEMO4_2.CPP. It draws randomly positioned lines at high speed. Its output is shown in Figure 4.4. Figure 4.4. Line-drawing program DEMO4_2.EXE.Getting RectangularThe next step up in the food chain of GDI is rectangles. Rectangles are drawn with both a pen and a brush (if the interior is filled). Therefore, rectangles are the most complex GDI primitives thus far. To draw a rectangle, use the Rectangle() function that follows: BOOL Rectangle(HDC hdc, // handle of device context int nLeftRect, // x-coord. of bounding // rectangle's upper-left corner int nTopRect, // y-coord. of bounding // rectangle's upper-left corner int nRightRect, // x-coord. of bounding // rectangle's lower-right corner int nBottomRect); // y-coord. of bounding // rectangle's lower-right corner Rectangle() draws a rectangle with the current pen and brush as shown in Figure 4.5. Figure 4.5. Using the DrawRectangle() function.NOTE I want to bring a very important detail to your attention. The coordinates you send Rectangle() are for the bounding box of the rectangle. This means that if the line style is NULL and you have a solid rectangle, it will be 1 pixel smaller on all four sides. There are also two other more specific functions to draw rectangles FillRect() and FrameRect(), shown here: int FillRect(HDC hDC, // handle to device context CONST RECT *lprc, // pointer to structure with rectangle HBRUSH hbr); // handle to brush int FrameRect( HDC hDC,// handle to device context CONST RECT *lprc, // pointer to rectangle coordinates HBRUSH hbr); // handle to brush FillRect() draws a filled rectangle without a border pen and includes the upper-left corner, but not the lower-right corner. Therefore, if you want a rectangle to fill in (10,10) to (20,20) you must send (10,10) to (21,21) in the RECT structure. FrameRect() on the other hand, just draws a hollow rectangle with a border. Surprisingly, FrameRect() uses a brush rather than a pen. Any ideas? In any case, here's an example of drawing a solid filled rectangle with the Rectangle() function: // create the pen and brush HPEN blue_pen = CreatePen(PS_SOLID, 1, RGB(0,0,255)); HBRUSH red_brush = CreateSolidBrush(RGB(255,0,0)); // select the pen and brush into context SelectObject(blue_pen); SelectObject(red_brush); // draw the rectangle Rectangle(hdc, 10,10, 20,20); // do house keeping... Here's a similar example using the FillRect() function instead: // define rectangle RECT rect {10,10,20,20}; // draw rectangle FillRect(hdc, &rect, CreateSolidBrush(RGB(255,0,0)); Notice the slickness here! I defined the RECT on-the-fly as well as the brush. The brush doesn't need to be deleted because it was never selected into context; hence, it's transient. WARNING I'm being fairly loose about the HDC and other details in these examples, so I hope you're awake! Obviously, for any of these examples to work you must have a window, an HDC, and perform the appropriate prolog and epilog code to each segment. As the book continues, I will assume that you know this already. As an example of using the Rectangle() function, take a look at DEMO4_3.CPP; it draws a slew of random rectangles in different sizes and colors on the window surface. However, as a change, I retrieved the handle to the entire window rather than just the client area, so the window looks like it's getting destroyed—cool, huh? Take a look at Figure 4.6 to see the output the program creates. Figure 4.6. Rectangle program DEMO4_3.EXE.Round and Round She Goes—CirclesBack in the '80s if you could make your computer draw a circle, you were a mastermind. There were a number of ways to do it—with the explicit formula: (x-x0)2 + (y-y0)2 = r2 Or maybe with the sine and cosine functions: x=r*cos(angle) y=r*sin(angle) Or maybe with lookup tables! The point is that circles aren't the fastest things in the world to draw. This dilemma is no longer important since Pentium IIs, but it used to be. In any case, GDI has a circle-drawing function—well, sort of… GDI likes ellipses rather than circles. If you recall from geometry, an ellipse is like a squished circle on either axis. An ellipse has both a major axis and a minor axis, as shown in the figure. The equation of an ellipse centered at (x0,y0) is shown in Figure 4.7. Figure 4.7. The mathematics of circles and ellipses.You would think that GDI would use some of the same concepts—the major axis and minor axis to define an ellipse—but GDI took a slightly different approach to defining an ellipse. With GDI, you simply give a bounding rectangle and GDI draws the ellipse that's bounded by it. In essence, you're defining the origin of the ellipse while at the same time the major and minor axes—whatever! The function that draws an ellipse is called Ellipse() and it draws with the current pen and brush. Here's the prototype: BOOL Ellipse( HDC hdc,// handle to device context int nLeftRect, // x-coord. of bounding // rectangle's upper-left corner int nTopRect, // y-coord. of bounding // rectangle's upper-left corner int nRightRect, // x-coord. of bounding // rectangle's lower-right corner int nBottomRect ); // y-coord. bounding // rectangle's f lower-right corner So to draw a circle you would make sure that the bounding rectangle was square. For example, to draw a circle that had center (20,20) with a radius of 10, you would do this: Ellipse(hdc,10,10,30,30); Get it? And if you wanted to draw a real-life ellipse with major axis 100, minor axis 50, with an origin of (300,200), you would do this: Ellipse(hdc,250,175,350,225); For a working example of drawing ellipses, take a look at DEMO4_4.CPP on the CD and the associated executable. The program draws a moving ellipse in a simple animation loop of erase, move, draw. This type of animation loop is very similar to the technique we'll use later called double buffering or page flipping, but with those techniques we won't be able to see the update as shown in the demo, and hence there won't be a flicker! For fun, try messing with the demo and changing things around. See if you can figure out how to add more ellipses. Polygon, Polygon, Wherefore Art Thou, Polygon?The last little primitive I want to show you is the polygon primitive. Its purpose is to draw open or closed polygonal objects very quickly. The function that draws a polygon is called Polygon() and is shown here: BOOL Polygon(HDC hdc, // handle to device context CONST POINT *lpPoints, // pointer to polygon's vertices int nCount ); // count of polygon's vertices You simply send Polygon() a list of POINTs along with the number of them and it will draw a closed polygon with the current pen and brush. Take a look at Figure 4.8 to see this graphically. Figure 4.8. Using the Polygon() function.Here's an example: // create the polygon shown in the figure POINT poly[7] = {p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, p5x, p5y, p6x, p6y }; // assume hdc is valid, and pen and brush are selected into // graphics device context Polygon(hdc, poly,7); That was easy! Of course, if you send points that make a degenerate polygon, or a polygon that closes on itself, GDI will do its best to draw it, but no promises! As an example of drawing filled polygons, DEMO4_5.CPP draws a collection of random 3–10 point polygons all over the screen with a little delay between each, so you can see the weird results that occur with degenerate polygon vertex lists. Figure 4.9 shows the output of the program in action. Notice that because the points are random, the polygons are almost always degenerate due to overlapping geometry. Can you find a way to make sure that all the points exist within a convex hull? Figure 4.9. Output of polygon program DEMO4_5.EXE. |