Home

The Graphical Device Interface

 

The Device Context

 

Introduction

A device context is an ensemble of the tools needed to draw lines, shapes, and other graphics. It includes the platform you draw on, the dimensioning of the platform, the orientation and other variations of your drawing, the tools you need to draw on the platform, the colors, and various other accessories that can complete your imagination. To provide support for drawing on the Windows operating system, Microsoft created the Graphical Device Interface, abbreviated as GDI. It is a set of classes, functions, variables, and constants that group all or most of everything you need to draw on an application. The GDI is provided as a library called Gdi.dll and is already installed on your computer.

 

The Canvas

In a Win32 application, in order to draw, you must create a device context. This can be taken care of by declaring a variable of type HDC. To keep track of the various drawings, the device context uses a coordinate system that has its origin (0, 0) on the top-left corner of the desktop:

Anything that is positioned on the screen is based on this origin. This coordinate system can get the location of an object using a horizontal and a vertical measurement. The horizontal measures are based on an x axis that moves from the origin to the right direction. The vertical measures use a y axis that moves from the origin to the bottom direction:

This means that, if you start drawing something such as a line, it would start on the origin and continue where you want it to stop. To significantly simplify drawing and make it compatible with the VCL, Borland created a class called TCanvas. TCanvas is based on TPersistent, which is derived from TObject. To further make it easy to draw, every control that needs drawing already has a TCanvas variable available. This means that you usually will not have to declare a TCanvas variable before drawing.

In a VCL application, a canvas is the object on which you draw but the TCanvas class actually encompasses everything that can be used to draw. This includes the platform and the tools. Because TCanvas does not implement everything that is possibly available on Win32 drawing, this class provides a handle that allows TCanvas to use an HDC variable to.

Line-Based Shapes

 

Lines

A line is a junction of two points. This means that a line has a beginning and an end:

The beginning and the end are two distinct points. In real life, before drawing, you should define where you would start. To help with this, the TCanvas class provides the MoveTo() method. Its syntax is:

void __fastcall MoveTo(int X, int Y);

The X argument represents the horizontal distance of the line beginning from the (0, 0) origin.

The Y value is the vertical distance from the (0, 0) origin.

To end the line, you use the TCanvas::LineTo() method. Its syntax is:

void __fastcall LineTo(int X, int Y);

The X argument represents the horizontal end of the line from the (0, 0) origin.

The Y value is the vertical end of the line from the (0, 0) origin.

Here is an example that draws a line starting at a point defined as (20, 15) coordinates and ending at (255, 82):

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->MoveTo(20, 15);
	Canvas->LineTo(255, 82);
}
//---------------------------------------------------------------------------

We have mentioned that the TCanvas::MoveTo() method is used to set the starting position of a line. When using LineTo(), the line would start from the MoveTo() point to the LineTo() end. As long as you do not call MoveTo(), any subsequent call to LineTo() would draw a line from the previous LineTo() to the new LineTo() point. You can use this property of the LineTo() method to draw various lines. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->MoveTo(60, 20);
	Canvas->LineTo(60, 122);
	Canvas->LineTo(264, 122);
	Canvas->LineTo(60, 20);
}
//---------------------------------------------------------------------------
 

Polylines

A polyline is a series of connected lines. The lines are stored in an array of TPoint values. To draw a polyline, you use the TCanvas::Polyline() method. Its syntax is:

void __fastcall Polyline(const Types::TPoint* Points, const int Points_Size);

The Points argument is an array of TPoint values. The Points_Size argument specifies the number of members of the array. When executing, the compiler moves the starting point to Points[0]. The first line is drawn from Points[0] to Points[1] as in:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[] = { Point(60, 20), Point(60, 122) };

	Canvas->MoveTo(Pt[0].x, Pt[0].y);
	Canvas->LineTo(Pt[1].x, Pt[1].y);
}
//---------------------------------------------------------------------------

To draw a polyline, you must have at least two points. If you define more than two points, each line after the first would be drawn from the previous point to the next point until all points have been included. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[7];

	Pt[0] = Point(20, 50);
	Pt[1] = Point(180, 50);
	Pt[2] = Point(180, 20);
	Pt[3] = Point(230, 70);
	Pt[4] = Point(180, 120);
	Pt[5] = Point(180, 90);
	Pt[6] = Point(20, 90);

	Canvas->Polyline(Pt, 7);
}
//---------------------------------------------------------------------------

Besides the Polyline() method, the Win32 API provides the PolylineTo() function. Its syntax is:

BOOL PolylineTo(HDC hdc, CONST POINT *lppt, DWORD cCount);

The hdc argument is a handle to the canvas on which you are drawing.

The lppt argument is the name of an array of POINT or TPoint objects.

The cCount argument specifies the number of points that would be included in the figure. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[7];

	Pt[0] = Point(20, 50);
	Pt[1] = Point(180, 50);
	Pt[2] = Point(180, 20);
	Pt[3] = Point(230, 70);
	Pt[4] = Point(180, 120);
	Pt[5] = Point(180, 90);
	Pt[6] = Point(20, 90);

	HDC hDC = Canvas->Handle;
	PolylineTo(hDC, Pt, 7);
}
//---------------------------------------------------------------------------

While the Polyline() method starts the first line at lppt[0], the PolylineTo() function does not control the beginning of the first line. Like the LineTo() method, it simply starts drawing, which would mean it could starts at the origin (0, 0). For this reason, if you want to control the starting point of the PolylineTo() drawing, you can use the MoveTo() method:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[7];

	Pt[0] = Point(20, 50);
	Pt[1] = Point(180, 50);
	Pt[2] = Point(180, 20);
	Pt[3] = Point(230, 70);
	Pt[4] = Point(180, 120);
	Pt[5] = Point(180, 90);
	Pt[6] = Point(20, 90);

	HDC hDC = Canvas->Handle;

	Canvas->MoveTo(20, 30);
	PolylineTo(hDC, Pt, 7);
	Canvas->LineTo(20, 110);
}
//---------------------------------------------------------------------------
 

Multiple Polylines

The above polylines were used each as a single entity. That is, a polyline is a combination of lines. If you want to draw various polylines in one step, you can use the Win32 API's PolyPolyline() function. By definition, PolyPolyline() is used to draw a series of polylines. Its syntax is:

BOOL PolyPolyline(HDC hdc, CONST POINT *lppt, CONST DWORD *lpdwPolyPoints, DWORD cCount);

The hdc argument is a handle to the canvas on which you want to draw.

Like the Polyline() method, the lppt argument is an array of POINT or TPoint values. The PolyPolyline() function needs to know how many polylines you want to draw. Each polyline will use the points of the lppt value but when creating the array of points, the values must be incremental. This means that PolyPolyline() will not access their values at random. It will retrieve the first point, followed by the second, followed by the third, etc. Therefore, your first responsibility is to decide where one polyline starts and where it ends. The good news (of course depending on how you see it) is that a polyline does not start where the previous line ended. Each polyline has its own beginning and its own ending point.

The lpdwPolyPoints argument is an array or positive integers (unsigned long). Each member of this array specifies the number of vertices (lines) that its corresponding polyline will have. For example, imagine you want to draw M, followed by L, followed by Z. The letter M has 4 lines but you need 5 points to draw it. The letter L has 2 lines and you need 3 points to draw it. The letter Z has 3 lines so 4 points are necessary to draw it. You can store this combination of lines in an array defined as { 5, 3, 4 }.

Unlike Polyline(), here, the cCount argument is actually the number of shapes you want to draw and not the number of points (remember that each polyline "knows" or controls its beginning and end).

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[15];
	DWORD lpPts[] = { 4, 4, 7 };

	// Left Triangle
	Pt[0] = Pt[3] = Point(50, 20);
	Pt[1] = Point(20, 60);
	Pt[2] = Point(80, 60);

	// Second Triangle
	Pt[4] = Pt[7] = Point(70, 20);
	Pt[5] = Point(100, 60);
	Pt[6] = Point(130, 20);

	// Hexagon
	Pt[8] = Pt[14] = Point(145, 20);
	Pt[9] = Point(130, 40);
	Pt[10] = Point(145, 60);
	Pt[11] = Point(165, 60);
	Pt[12] = Point(180, 40);
	Pt[13] = Point(165, 20);

	HDC hDC = Canvas->Handle;
	PolyPolyline(hDC, Pt, lpPts, 3);
}
//---------------------------------------------------------------------------
 

Polygons

The polylines we have used so far were drawn by defining the starting point of the first line and the end point of the last line. There was no relationship or connection between these two extreme points. A polygon is a closed polyline. In other words, it is a polyline defined so that the end point of the last line is connected to the start point of the first line.

To draw a polygon, you can use the TCanvas::Polygon() method. Its syntax is:

void __fastcall Polygon(const TPoint * Points, const int Points_Size);

This member function uses the same types of arguments as the Polyline() method. The only difference is on the drawing of the line combination. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[7];

	Pt[0] = Point(20, 50);
	Pt[1] = Point(180, 50);
	Pt[2] = Point(180, 20);
	Pt[3] = Point(230, 70);
	Pt[4] = Point(180, 120);
	Pt[5] = Point(180, 90);
	Pt[6] = Point(20, 90);

	Canvas->Polygon(Pt, 7);
}
//---------------------------------------------------------------------------
 

Multiple Polygons

If you want to draw a series of polygons, you can use the PolyPolygon() function whose syntax is:

BOOL PolyPolygon(HDC hdc,
                 CONST POINT *lpPoints,
                 CONST INT *lpPolyCounts,
                 int nCount);

The hdc argument is a handle to the canvas on which you want to draw.

Like the Polygon() method, the lpPoints argument is an array of POINT or TPoint values. The PolyPolygon() function needs to know the number of polygons you would be drawing. Each polygon uses the points of the lpPoints value but when creating the array of points, the values must be incremental: each polygon has its own set of points.

The lpPolyCounts argument is an array or integers. Each member of this array specifies the number of vertices (lines) that its polygon will have..

Unlike Polygon(), the nCount argument of PolyPolygon() is the number of polygons you want to draw and not the number of points.

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[12];
	int lpPts[] = { 3, 3, 3, 3 };

	// Top Triangle
	Pt[0] = Point(125, 10);
	Pt[1] = Point( 95, 70);
	Pt[2] = Point(155, 70);

	// Left Triangle
	Pt[3] = Point( 80, 80);
	Pt[4] = Point( 20, 110);
	Pt[5] = Point( 80, 140);

	// Bottom Triangle
	Pt[6] = Point( 95, 155);
	Pt[7] = Point(125, 215);
	Pt[8] = Point(155, 155);

	// Right Triangle
	Pt[9] = Point(170, 80);
	Pt[10] = Point(170, 140);
	Pt[11] = Point(230, 110);

	HDC hDC = Canvas->Handle;
	PolyPolygon(hDC, Pt, lpPts, 4);
}
//---------------------------------------------------------------------------
 

Rectangles and Squares

A rectangle is a geometric figure made of four sides that compose four right angles. Like the line, to draw a rectangle, you must define where it starts and where it ends. This can be illustrated as follows:

The drawing of a rectangle typically starts from a point defined as (X1, Y1) and ends at another point (X2, Y2). To draw a rectangle, you can use the TCanvas::Rectangle() method. Its syntax is:

void __fastcall Rectangle(int X1, int Y1, int X2, int Y2);

As seen on the figure and the formula, a rectangle spans from coordinates (x1, y1) to (x2, y2). Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->Rectangle(20, 20, 226, 144);
}
//---------------------------------------------------------------------------

When drawing a rectangle, if the value of x2 is less than that of x1, then the x2 coordinate would mark the left beginning of the figure. This scenario would also apply if the y2 coordinate were lower than y1. To draw a rectangle, you can also use a RECT or a TRect object. The syntax you would use is:

void __fastcall Rectangle(TRect Rect);

In this case, you must have defined a RECT or a TRect value and pass it as a pointer to the Rectangle() method. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	RECT Recto;

	Recto.left = 328;
	Recto.top = 125;
	Recto.right = 48;
	Recto.bottom = 25;

	Canvas->Rectangle(Recto);
}
//---------------------------------------------------------------------------

A square is a rectangle whose sides are all equal. Therefore, to draw a square, when specifying the arguments of the Rectangle() method, make sure that |x1 - x2| = |y1 - y2|.

A Rectangle With Edges

The Win32 library provides another function you can use to draw a rectangle. This time you can control how the edges of the rectangle would be drawn. The function used is called DrawEdge and its syntax is:

BOOL DrawEdge(HDC hdc, LPRECT qrc, UINT edge, UINT grfFlags);

The hdc argument represents a handle of the canvas on which you want to draw.

The qrc argument is passed as a pointer to a RECT or TRect, which is the rectangle that would be drawn.

The edge value specifies how the interior and the exterior of the edges of the rectangle would be drawn. It can be a combination of the following constants:

Value Description
BDR_RAISEDINNER The interior edge will be raised
BDR_SUNKENINNER The interior edge will be sunken
BDR_RAISEDOUTER The exterior edge will be raised
BDR_SUNKENOUTER The exterior edge will be sunken

These values can be combined using the bitwise OR operator. On the other hand, you can use the following constants instead:

Value Used For
EDGE_DUMP BDR_RAISEDOUTER | BDR_SUNKENINNER
EDGE_ETCHED BDR_SUNKENOUTER | BDR_RAISEDINNER
EDGE_RAISED BDR_RAISEDOUTER | BDR_RAISEDINNER
EDGE_SUNKEN BDR_SUNKENOUTER | BDR_SUNKENINNER

The grfFlags value specifies what edge(s) would be drawn. It can have one of the following values:

Value Description
BF_RECT The entire rectangle will be drawn
BF_TOP Only the top side will be drawn
BF_LEFT Only the left side will be drawn
BF_BOTTOM Only the bottom side will be drawn
BF_RIGHT Only the right side will be drawn
BF_TOPLEFT Both the top and the left sides will be drawn
BF_BOTTOMLEFT Both the bottom and the left sides will be drawn
BF_TOPRIGHT Both the top and the right sides will be drawn
BF_BOTTOMRIGHT Both the bottom and the right sides will be drawn
BF_DIAGONAL_ENDBOTTOMLEFT A diagonal line will be drawn from the top-right to the bottom-left corners
BF_DIAGONAL_ENDBOTTOMRIGHT A diagonal line will be drawn from the top-left to the bottom-right corners
BF_DIAGONAL_ENDTOPLEFT A diagonal line will be drawn from the bottom-right to the top-left corners
BF_DIAGONAL_ENDTOPRIGHT A diagonal line will be drawn from the bottom-left to the top-right corners

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TRect Recto(20, 20, 225, 115);

	HDC hDC = Canvas->Handle;
	DrawEdge(hDC, &Recto, BDR_RAISEDOUTER | BDR_SUNKENINNER, BF_RECT);
}
//---------------------------------------------------------------------------
 

Circle-Based Shapes

 

Ellipses and Circles

An ellipse is a closed continuous line whose points are positioned so that two points exactly opposite each other have the exact same distant from a point called the center. It can be illustrated as follows:

Because an ellipse can fit in a rectangle, in GDI programming, an ellipse is defined with regards to a rectangle it would fit in. Therefore, to draw an ellipse, you specify its rectangular corners. The syntax used to do this is:

void __fastcall Ellipse(int X1, int Y1, int X2, int Y2);

The arguments of this method play the same roll as those of the Rectangle() method:

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->Ellipse(20, 20, 226, 144);
}
//---------------------------------------------------------------------------

Like the rectangle, you can draw an ellipse using a RECT or TRect object it would fit in. The syntax you would use is:

void __fastcall Ellipse(TRect Rect);

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TRect Recto(328, 125, 28, 8);
	Canvas->Ellipse(Recto);
}
//---------------------------------------------------------------------------
Ellipse

A circle is an ellipse whose all points have the same distance with regards to a central point.

Round Rectangles and Round Squares

A rectangle qualifies as round if its corners do not form straight angles but rounded corners. It can be illustrated as follows:

To draw such a rectangle, you can use the TCanvas::RoundRect() method. Its syntax is:

void __fastcall RoundRect(int X1, int Y1, int X2, int Y2, int X3, int Y3);

When this member function executes, the rectangle is drawn from the (x1, y1) to the (x2, y2) points. The corners are rounded by an ellipse whose width would be x3 and the ellipse's height would be x3. 

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->RoundRect(20, 20, 275, 188, 42, 38);
}
//---------------------------------------------------------------------------

A round square is a square whose corners are rounded.

Pies

 

A pie is a fraction of an ellipse delimited by two lines that span from the center of the ellipse to one side each. It can be illustrated as follows:

To draw a pie, you can use the TCanvas::Pie() method whose syntax is:

void __fastcall Pie(int X1, int Y1, int X2, int Y2, int X3, int Y3, int X4, int Y4);

The (X1, Y1) point determines the upper-left corner of the rectangle in which the ellipse that represents the pie fits. The (X2, Y2) point is the bottom-right corner of the rectangle.

The (X3, Y3) point specifies the starting corner of the pie in a default counterclockwise direction.

The (X4, Y4) point species the end point of the pie.

To complete the pie, a line is drawn from (X3, Y3) to the center and from the center to the (X4, Y4) points.

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->Pie(40, 20, 226, 144, 155, 32, 202, 115);
}
//---------------------------------------------------------------------------
 

Arcs

An arc is a portion or segment of an ellipse. This means that an arc is a non-complete ellipse. Because an arc must confirm to the shape of an ellipse, it is defined as it fits in a rectangle and can be illustrated as follows:

To draw an arc, you can use the TCanvas::Arc() method whose syntax is:

void __fastcall Arc(int X1, int Y1, int X2, int Y2, int X3, int Y3, int X4, int Y4);

Besides the left (X1, Y1) and the right (X2, Y2) borders of the rectangle in which the arc would fit, an arc must specify where it starts and where it ends. The additional points are set as the (X3, Y3) and (X4, Y4) points of the figure. Based on this, the above arc can be illustrated as follows:

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->Arc(20, 20, 226, 144, 202, 115, 105, 32);
}
//---------------------------------------------------------------------------

Besides the Arc() method, the Win32 library provides the ArcTo() function used to draw an arc. Its syntax is as follows:

BOOL ArcTo(HDC hdc,
           int nLeftRect, int nTopRect, int nRightRect, int nBottomRect,
           int nXRadial1, int nYRadial1, int nXRadial2, int nYRadial2);

The hdc argument is a handle to the canvas on which you want to draw.

This method uses the same arguments as Arc(). The difference is that while Arc() starts drawing at (x3, y3), ArcTo() does not inherently control the drawing starting point. It refers to the current point, exactly like the LineTo() (and the PolylineTo(). methods. Therefore, if you want to specify where the drawing should start, you can call TCanvas::MoveTo() before ArcTo(). Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	HDC hDC = Canvas->Handle;

	TRect Recto(20, 20, 226, 144);
	TPoint Pt1(202, 115);
	TPoint Pt2(105, 32);

	Canvas->MoveTo(207, 155);
	ArcTo(hDC,
	Recto.Left, Recto.Top, Recto.Width(), Recto.Height(),
	Pt1.x, Pt1.y, Pt2.x, Pt2.y);
}
//---------------------------------------------------------------------------
 

The Arc's Direction

Here is and arc we drew earlier with a call to Arc():

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->Arc(20, 20, 226, 144, 202, 115, 105, 32);
}
//---------------------------------------------------------------------------

You may wonder why the arc is drawn to the right side of a vertical line that would cross the center of the ellipse instead of the left. This is because the drawing of an arc is performed from right to left or from bottom to up, in the opposite direction of the clock. This is known as the counterclockwise direction. To control this orientation, the Win32 library provides the SetArcDirection() function. Its syntax is:

int SetArcDirection(HDC hdc, int ArcDirection);

This function specifies the direction the TCanvas::Arc() method should follow from the starting to the end points. The argument passed as ArcDirection controls this orientation. It can have the following values:

Value Orientation
AD_CLOCKWISE The figure is drawn clockwise
AD_COUNTERCLOCKWISE The figure is drawn counterclockwise

The default value of the direction is AD_COUNTERCLOCKWISE. Therefore, this would be used if you do not specify a direction. Here is an example that uses the same values as above with a different orientation:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	HDC hDC = Canvas->Handle;

	SetArcDirection(hDC, AD_CLOCKWISE);

	Canvas->Arc(20, 20, 226, 144, 202, 115, 105, 32);
	Canvas->Arc(20, 20, 226, 144, 202, 115, 105, 32);
}
//---------------------------------------------------------------------------

After calling SetArcDirection() and changing the previous direction, all drawings would use the new direction to draw arcs using Arc() or ArcTo() and other figures (such as chords, ellipses, pies, and rectangles). Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	HDC hDC = Canvas->Handle;

	SetArcDirection(hDC, AD_COUNTERCLOCKWISE);

	Canvas->Arc(20, 20, 226, 144, 202, 115, 105, 32);
	Canvas->Arc(10, 10, 250, 155, 240, 85, 24, 48);
}
//---------------------------------------------------------------------------

If you want to change the direction, you must call SetArcDirection() with the desired value. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	HDC hDC = Canvas->Handle;

	SetArcDirection(hDC, AD_COUNTERCLOCKWISE);
	Canvas->Arc(20, 20, 226, 144, 202, 115, 105, 32);

	SetArcDirection(hDC, AD_CLOCKWISE);
	Canvas->Arc(10, 10, 250, 155, 240, 85, 24, 48);
}
//---------------------------------------------------------------------------

At any time, you can find out the current direction used. This is done by calling the GetArcDirection() function. Its syntax is:

int GetArcDirection(HDC hdc);

This function returns the current arc direction as AD_CLOCKWISE or AD_COUNTERCLOCKWISE.

Angular Arcs

You can (also) draw an arc using the AngleArc() function. Its syntax is:

BOOL AngleArc(HDC hdc, int X, int Y, DWORD dwRadius,
              FLOAT eStartAngle, FLOAT eSweepAngle);

The hdc argument represents a handle to the canvas on which you want to draw. This function draws a line and an arc connected. The arc is based on a circle and not an ellipse. This implies that the arc fits inside a square and not a rectangle. The circle that would be the base of the arc is defined by its center located at C(X, Y) with a radius of dwRadius. The arc starts at an angle of eStartAngle. The angle is based on the x axis and must be positive. That is, it must range from 0° to 360°. If you want to specify an angle that is below the x axis, such as -15°, use 360º-15°=345°. The last argument, eSweepAngle, is the angular area covered by the arc.

The AngleArc() function does not control where it starts drawing. This means that it may start at the origin, unless a previous call to MoveTo() specified the beginning of the drawing.

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	HDC hDC = Canvas->Handle;

	Canvas->MoveTo(52, 28);
	AngleArc(hDC, 120, 45, 142, 345, -65);
}
//---------------------------------------------------------------------------
 

Chords

The arcs we have drawn so far are considered open figures because they are made of a line that has a beginning and an end (unlike a circle or a rectangle that do not). A chord is an arc whose two ends are connected by a straight line. In other words, a chord is an ellipse that is divided by a straight line from one side to another:

To draw a chord, you can use the TCanvas::Chord() method. It is defined as follows:

void __fastcall Chord(int X1, int Y1, int X2, int Y2, int X3, int Y3, int X4, int Y4);

The X1, Y1, X2, and Y2 are the coordinates of the rectangle in which the chord of the circle would fit.

The X3 and Y3 coordinates specify where the arc that holds the chord starts.

The X4 and Y4 arguments specify the end of the arc.

To complete the chord, a line is drawn from (X3, Y3) to (X4, Y4).

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->Chord(20, 20, 226, 144, 202, 115, 105, 32);
}
//---------------------------------------------------------------------------
 

Curve-Based Shapes

 

Bézier Curves

A bézier line is an arc that is strictly based on a set number of points instead of on an ellipse. A bézier curve uses at least four points to draw on. A bézier line with four points can be illustrated as follows:

To draw this line (with four points), the compiler would draw a curve from the first to the fourth points. Then it would bend the curve by bringing each middle (half-center) side close to the second and the third points respectively, of course without touching those second and third points. For example, the above bézier curve could have been drawn using the following four points:

PolyBezier(): To draw a bézier curve, the TCanvas provides the PolyBezier() method. Its syntax is:

void __fastcall PolyBezier(const TPoint* Points, const int Points_Size);

The Points argument is an array of POINT or TPoint values. The Points_Size argument specifies the number of points that will be used to draw the line minus 1. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[4] = { Point(20, 12), Point(88, 246),
	Point(364, 192), Point(250, 48) };

	Canvas->PolyBezier(Pt, 3);
}
//---------------------------------------------------------------------------

In the same way, you can draw a series of complicated subsequent lines. This is done by adding reference points to the array. To do this, you must add points in increments of three. After drawing the first curve based on the first four points, to draw the next line, the function would use the fourth point as the starting point. Since the bézier line requires 4 points, you must add three more. You can continue adding points by three to draw the bézier curve. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[] = { Point(20, 12), Point(88, 246),
	Point(364, 192), Point(250, 48),
	Point(175, 38), Point(388, 192), Point(145, 125) };

	Canvas->PolyBezier(Pt, 6);
}
//---------------------------------------------------------------------------

PolyBezierTo(): The TCanvas::PolyBezier() method requires at least four points to draw its curve. This is because it needs to know where to start drawing. Another way you can control where the curve would start is by using the TCanvas::PolyBezierTo() method. Its syntax is:

void __fastcall PolyBezierTo(const TPoint * Points, const int Points_Size);

The PolyBezierTo() method draws a bézier curve. Its first argument, Points, is a pointer to an array of POINT or TPoint values. This member function requires at least three points. It starts drawing from the current line to the third point. If you do not specify the current line, it would start at the origin (0, 0). The first and the second lines are used to control the curve. The Points_Size argument is the number of points that would be considered minus 1. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	TPoint Pt[] = { Point(320, 120), Point(88, 246), Point(364, 122) };

	Canvas->PolyBezierTo(Pt, 2);
}
//---------------------------------------------------------------------------
 

Text Drawing Techniques

 

Text Outing

To write text on a canvas, you can call the TCanvas::TextOut() method. Its syntax is:

void __fastcall TextOut(int X, int Y, const AnsiString Text);

The TextOut() method is used to create an display a piece of text on the screen. The X and Y arguments are the point coordinates of the top-left corner of the string being displayed. The Text argument is the text that needs to be displayed. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->TextOut(10, 10, "Walter Bells");
}
//---------------------------------------------------------------------------

Text Confined to a Rectangle

To create text that must only fit inside or a rectangle, you can call the TCanvas::TextRect() method

Error! Bookmark not defined.

Its syntax is:

void __fastcall TextRect(const Types::TRect &Rect, int X, int Y, const AnsiString Text);

The TextRect() method draws text in a rectangle. A portion of the text that is larger than the allocated rectangle would be hidden. The Rect argument is the rectangle that will contain the text. The X and Y argument are the coordinates of the text, in screen coordinates. This means that, to show the beginning of the text, the value of X must be greater than or equal to the Left member variable of the Rect argument. If you want to show the top section of the text, the value of Y must be greater than or equal to the Top member variable of the Rect argument. The Text argument is the string that needs to be displayed. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->TextRect(Rect(40, 20, 120, 60), 40, 20, "Walter Bells");
}
//---------------------------------------------------------------------------

The Dimensions of a Drawn String

Many drawing functions require you to know the dimensions of the string that is being drawn or the string that needs to be drawn. To support this, the VCL provides various functions. To know the width occupied by a string, you can call the TCanvas::TextWidth() method. Its syntax is:

int __fastcall TextWidth(const AnsiString Text);

The Text argument is the string that is displaying or needs to be shown. After this method has executed, it returns the width of the string. On the other hand, if you want to find out the height of text that is drawn or needs to be drawn, you can call the TCanvas::TextHeight() method whose syntax is:

int __fastcall TextHeight(const AnsiString Text);

Like TextWidth(), the TextHeight() method takes as argument the string that is displaying or needs to be displayed. TextHeight() returns the height of the string.

If you need to find both the width and the height occupied by a string, you can use a single TCanvas method called TextExtent. Its syntax is:

TSize __fastcall TextExtent(const AnsiString Text);

Like the previous two methods, TextExtent() takes as argument the string that needs to be considered. After this method has executed, it return both the width and the height of the Text string stored in a TSize variable.

Text Drawing and Alignment

The Win32 library provides a function that can be used to draw text that is proportionately centered with regards to either the width or the height of the canvas on which it is positioned. The function used is DrawText() and its syntax is:

int DrawText(HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);

The lpString argument is the string that needs to be drawn. The nCount is the number of characters that compose the string. The string will be positioned in the lpRect rectangle. The uFormat argument specifies how the text will be formatted.

 

Previous Copyright © 2005 Yevol Next