JavaScript EditorFree JavaScript Editor     Ajax Editor 

Main Page
  Previous Section Next Section

Points, Lines, Polygons, and Circles

Now 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 Point

Drawing 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 Things

Now 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:

  1. Create a pen and select it into the graphics device contexts. All lines will be drawn with this pen.

  2. Set the initial position of the line.

  3. Draw a line from the initial position to the destination position (the destination position becomes the initial position of the next segment).

  4. Go to step 3 and draw more segments if desired.

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);

// restore old pen
SelectObject(hdc, old_pen);

// delete the 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

// draw second leg

// close it up

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 Rectangular

The 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.



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

// 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.


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—Circles

Back 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:


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:


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:


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.


      Previous Section Next Section
    Bitcoin Gambling Site . Most detailed backlink quality analysis.

    JavaScript EditorAjax Editor     JavaScript Editor