The GDI Coordinate Systems


 

The Default Coordinate System

When drawing on Microsoft Windows, the coordinates of the drawing area are located on the upper-left corner of the screen. Everything positioned on the screen takes its reference on that point. That point can be illustrated in a Cartesian coordinate system as (0,0) where the horizontal axis moves from (0,0) to the right and the vertical axis moves from (0,0) down:

 

This starting origin is only the default coordinate system of the operating system. Therefore, if you draw a shape with the following call, Canvas->Ellipse(-100, -100, 100, 100), you would get a circle whose center is positioned on the top-left corner of the screen. In this case, only the lower-right 3/4 of the circle would be seen:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    Canvas->Pen->Color = clBlue;

    Canvas->Ellipse(-100, -100, 100, 100);
}
//---------------------------------------------------------------------------

In the same way, you can draw any geometric or non-geometric figure you want, using one of the TCanvas methods or creating functions of your choice. For example, the following code draws a vertical and a horizontal lines that cross each other in the center middle of the form:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    Canvas->Pen->Color = clBlue;

    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlack;
    Canvas->MoveTo(ClientWidth/2, 0);
    Canvas->LineTo(ClientWidth/2, ClientHeight);
    Canvas->MoveTo(0, ClientHeight/2);
    Canvas->LineTo(ClientWidth, ClientHeight/2);
}
//---------------------------------------------------------------------------

 

 

Changing the Default Coordinate System in the VCL

In some circumstances, you may not want to use the default coordinate system when drawing. This is sometimes necessary for graphics, drawing, and other related assignments. When using Borland C++ Builder (or Delphi), there are at least two ways you can change the position of the origin of the coordinate system of your drawing area.

The Visual Component Library (VCL) provides a valuable function that can be used to move the (0, 0) origin to any location you want with regards to the original and default origin. To do this, you can use the MoveWindowOrg() function. It syntax is:

void __fastcall MoveWindowOrg(HDC DC, int DX, int DY);

The MoveWindowOrg() function takes three arguments. The first, a handle, must be a Win32's HDC handle to the device context. For a form or a paint box, etc, this HDC is the handle to the canvas of the object on which you are drawing. The second argument, DX, is the starting point of the new horizontal axis. The last argument, DY, represents the starting point of the vertical axis.

After calling the MoveWindowOrg() function, you do not have to keep referring to the device context handle because the compiler would know which Handle you are using. Therefore, you can continue your drawing, as long as you keep in mind that the origin is not on the top-left corner of the screen anymore.

After calling the MoveWindowOrg() function, any drawing you perform under its call refers to it for its coordinates. Here is an example:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    HDC hDC = this->Canvas->Handle;
    MoveWindowOrg(hDC, ClientWidth/2, ClientHeight/2);
    
    Canvas->Pen->Color = clBlue;
    
    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlack;
    Canvas->MoveTo(ClientWidth/2, 0);
    Canvas->LineTo(ClientWidth/2, ClientHeight);
    Canvas->MoveTo(0, ClientHeight/2);
    Canvas->LineTo(ClientWidth, ClientHeight/2);
}
//---------------------------------------------------------------------------

As you can see, this function is extremely easy to use. You can also see that our lines have disappeared (normally, you can still see the vertical line on the right side/border of the form). In reality, just as the circle has moved, the lines also have moved, based on the current origin of the coordinate system. As stated already, as long as you keep in mind where your coordinate system is, you can perform your drawing anyway you want. For this example, we can now draw shapes with regard to the center of the canvas.

In the above example, the origin was moved relative to the client dimensions of the form. You can also set  a fixed origin using a constant value. In fact, this can sometimes be faster and safer. In our new version, we will position the origin to the (400, 300) point of the form. We can also now move the lines of our coordinate system:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    // Get a handle to the canvas of the form
    HDC hDC = Canvas->Handle;

    // Change the origin of the coordinate system
    MoveWindowOrg(hDC, 300, 200);

    // Create a blue pen
    Canvas->Pen->Color = clBlue;

    // Draw a blue circle
    Canvas->Ellipse(-100, -100, 100, 100);

    // Change the pen color to Black
    Canvas->Pen->Color = clBlack;

    // Draw the Vertical Axis
    Canvas->MoveTo(0, 300);
    Canvas->LineTo(0, -300);
    // Draw the Horizontal Axis
    Canvas->MoveTo(-400, 0);
    Canvas->LineTo(400, 0);
}
//---------------------------------------------------------------------------

Changing the Win32 Coordinate System 

The Win32 library provides another mechanism of changing the coordinate system used when drawing. The first thing you should do is to get the device context handle to the canvas of the object that owns the drawing area you are using. In short, you get this handle the same way we did above.

The commonly used function, close to the MoveWindowOrg() function above, is the SetViewportOrgEx() function. Its syntax is:

BOOL SetViewportOrgEx(HDC hdc, int X, int Y, LPPOINT lpPoint);

The first argument of this function is a handle to the device context you are using to draw. The X and Y arguments constitute the point used as the new origin of the coordinate system. The last argument is a pointer to the previous point origin. The use of the Win32's SetViewportOrgEx() function is as easy as the VCL's MoveWindowOrg() function. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    SetViewportOrgEx(Canvas->Handle, 300, 200, NULL);

    Canvas->Pen->Color = clRed;
    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clNavy;
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(120, 0);
}
//---------------------------------------------------------------------------

To test it, consider the following example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    SetViewportOrgEx(Canvas->Handle, 300, 200, NULL);

    Canvas->Pen->Color = clRed;
    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clNavy;
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(0, 120);

    Canvas->Pen->Color = clBlue;
    
    // Horizontal axis, from -X to +X
    Canvas->MoveTo(-300, 0);
    Canvas->LineTo(300, 0);
    // Vertical axis, from -Y to +Y
    Canvas->MoveTo(0, -200);
    Canvas->LineTo(0, 200);
}
//---------------------------------------------------------------------------

The SetViewportOrgEx() function changes the position of the origin of the coordinate system. At the same time, it imposes a new orientation of the axis. The horizontal axis moves positively from the (0, 0) point to the right. The vertical axis moves positively from (0, 0) down. This is illustrated as follows:

To experiment with this new orientation, consider drawing a line at 45º from the origin. Such a line has a positive width and a positive height. In other words, the line can be drawn from (0, 0) to (150, 150) and the line should be in the 12 O'clock to 3 O'clock quadrant. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    SetViewportOrgEx(Canvas->Handle, 300, 200, NULL);

    Canvas->Pen->Color = clRed;
    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    Canvas->MoveTo(-300, 0);
    Canvas->LineTo(300, 0);
    Canvas->MoveTo(0, -200);
    Canvas->LineTo(0, 200);

    Canvas->Pen->Color = clFuchsia;
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(150, 150);
}
//---------------------------------------------------------------------------

As you can see, the line is not in the intended quadrant. This is because, as we saw above, the negative Y is oriented from the origin to the top. This is not necessarily an anomaly because the operating system proposes a solution to customize the orientation.

Microsoft Windows provides various alternatives to control the orientation of the coordinate system you want to use for your application. It also helps with the unit system you would prefer to use. To control the coordinates orientation and/or to specify the unit system you want to use, you call the SetMapMode() function. Its syntax is:

int SetMapMode(HDC hdc,int fnMapMode);

The first argument of the function is the handle as mentioned above. the second argument species the unit system to use. The available system units are MM_TEXT, MM_LOENGLISH, MM_HIENGLISH, MM_ANISOTROPIC, MM_HIMETRIC, MM_ISOTROPIC, MM_LOMETRIC, and MM_TWIPS.

The default map mode used is the MM_TEXT. In other words, if you don't specify another, this is the one your application would use. With this map mode, the dimensions or measurements you specify in your functions are respected and kept "as is". Also, the axes are oriented so the horizontal axis moves from (0, 0) to the right and the vertical axis moves from (0, 0) down. For example, the above OnPaint event can be re-written as follows and produce the same result:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    HDC hDC = this->Canvas->Handle;
    SetMapMode(hDC, MM_TEXT);
    SetViewportOrgEx(hDC, 300, 200, NULL);

    Canvas->Pen->Color = clRed;
    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    Canvas->MoveTo(-300, 0);
    Canvas->LineTo(300, 0);
    Canvas->MoveTo(0, -200);
    Canvas->LineTo(0, 200);

    Canvas->Pen->Color = clFuchsia;
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(150, 150);
}
//---------------------------------------------------------------------------

The MM_LOENGLISH, like some of the other map modes (excluding MM_TEXT as seen above), performs two actions. It changes the orientation of the vertical axis: the y axis would move from (0, 0) up:

Also, each unit of measure is multiplied by 0.01 inch, which means the units are reduced from their stated measures. Observe the effect of the MM_LOENGLISH map mode on the above OnPaint event :

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    HDC hDC = this->Canvas->Handle;
    SetMapMode(hDC, MM_LOENGLISH);
    SetViewportOrgEx(hDC, 300, 200, NULL);

    Canvas->Pen->Color = clRed;
    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    Canvas->MoveTo(-300, 0);
    Canvas->LineTo(300, 0);
    Canvas->MoveTo(0, -200);
    Canvas->LineTo(0, 200);

    Canvas->Pen->Color = clFuchsia;
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(150, 150);
}
//---------------------------------------------------------------------------

As you can see, now the lines are drawn respecting the positive and the negative orientations of the axes, fullfilling the requirements of a Cartesian system. At the same time, the lengths we used have been reduced: the circle is smaller and the lines are shorter.

Like the MM_LOENGLISH map mode, the MM_HIENGLISH corrects (normally, I shouldn't use the word "corrects" because I might suggest that the MM_TEXT is an anomaly; the map modes are provided so you can select your desired orientation) the orientation so the vertical axis moves from (0, 0) up. Further than the MM_LOENGLISH, the MM_HIENGLISH map mode reduces each unit by a factor of 0.001 inch which is significant and can change the display of a drawing. Here is its effect:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    HDC hDC = this->Canvas->Handle;
    SetMapMode(hDC, MM_HIENGLISH);
    SetViewportOrgEx(hDC, 300, 200, NULL);

    Canvas->Pen->Color = clRed;
    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    Canvas->MoveTo(-300, 0);
    Canvas->LineTo(300, 0);
    Canvas->MoveTo(0, -200);
    Canvas->LineTo(0, 200);

    Canvas->Pen->Color = clFuchsia;
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(150, 150);
}
//---------------------------------------------------------------------------

Notice that we are still using the same dimensions for our lines and circle.

The MM_LOMETRIC map mode uses the same axes orientation as the previous two modes. On the other hand, the MM_LOMETRIC multiplies each unit by 0.1 millimeter. This means that each unit is reduced by 10%. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    HDC hDC = this->Canvas->Handle;
    SetMapMode(hDC, MM_LOMETRIC);
    SetViewportOrgEx(hDC, 300, 200, NULL);

    Canvas->Pen->Color = clRed;
    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    Canvas->MoveTo(-300, 0);
    Canvas->LineTo(300, 0);
    Canvas->MoveTo(0, -200);
    Canvas->LineTo(0, 200);

    Canvas->Pen->Color = clFuchsia;
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(150, 150);
}
//---------------------------------------------------------------------------

The MM_HIMETRIC map mode uses the same axes orientation as the above three modes. Its units are gotten by multiplying each of the given units by 0.01 millimeter. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    HDC hDC = this->Canvas->Handle;
    SetMapMode(hDC, MM_HIMETRIC);
    SetViewportOrgEx(hDC, 300, 200, NULL);

    Canvas->Pen->Color = clRed;
    Canvas->Ellipse(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    Canvas->MoveTo(-300, 0);
    Canvas->LineTo(300, 0);
    Canvas->MoveTo(0, -200);
    Canvas->LineTo(0, 200);

    Canvas->Pen->Color = clFuchsia;
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(150, 150);
}
//---------------------------------------------------------------------------

Customizing the Unit and Coordinate Systems

The map modes we have used so far allowed us to select the orientation of the axes, especially the y axis. Furthermore, we couldn't influence any conversion unit for the dimensions we specified on our drawings. This is because each one of these mapping modes (MM_TEXT, MM_HIENGLISH, MM_LOENGLISH, MM_HIMETRIC, MM_LOMETRIC, and MM_TWIPS) has a fixed set of attributes such as the orientation of its axes and how it converts the dimensions given to it. What if you want to control the orientation of axes and/or the conversion applied on the dimensions you provide in your drawing (have you ever used AutoCAD?).

Consider the following event. It is drawing a 200x200 pixels square with a red border and an aqua background. The square starts at 100 pixels and 100 pixels on the negative sides of both axes and it continues 100 pixels and 100 pixels on the positive sides of both axes. For better illustration, the event also draws a diagonal line at 45º starting at the origin (0, 0):

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    // Draw a square with a red border and an aqua background
    Canvas->Pen->Color = clRed;
    Canvas->Brush->Color = clAqua;
    Canvas->Rectangle(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    // diagonal line at 45 degrees starting at the origin (0, 0)
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(200, 200);
}
//---------------------------------------------------------------------------

This would produce:

Drawing with default coordinates and units

As you can see, we get only the the lower-right 3/4 portion of the square and the line is pointing in the 3 to 6 O'clock quadrant.

Imagine that you want the origin (0, 0) to be positioned in the center middle of the form, or to be more precise, to position the origin at (340, 220) (I had set the form I am using to Height=474 and Width=688, which was only my preference for this exercise). We saw already that you can use the SetViewportOrgEx() to specify the origin. Here is an example (we are not specifying the map mode because the MM_TEXT can be used for us as the default):

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

    SetViewportOrgEx(hDC, 340, 220, NULL);

    // Draw a square with a red border and an aqua background
    Canvas->Pen->Color = clRed;
    Canvas->Brush->Color = clAqua;
    Canvas->Rectangle(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    // diagonal line at 45 degrees starting at the origin (0, 0)
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(200, 200);
}
//---------------------------------------------------------------------------

This would produce:

To control your own unit system, the orientation of the axes, how the application converts the units used on your application, use either the MM_ISOTROPIC or the MM_ANISOTROPIC map modes. The first thing you should do is to call the SetMapMode() function and specify one of these two constants. Here is an example:

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

    SetMapMode(hDC, MM_ISOTROPIC);
    SetViewportOrgEx(hDC, 340, 220, NULL);

    // Draw a square with a red border and an aqua background
    Canvas->Pen->Color = clRed;
    Canvas->Brush->Color = clAqua;
    Canvas->Rectangle(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    // diagonal line at 45 degrees starting at the origin (0, 0)
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(200, 200);
}
//---------------------------------------------------------------------------

Don't rely on the above picture, after calling the SetMapMode() function with MM_ISOTROPIC (or MM_ANISOTROPIC) as argument, you are not supposed to stop there. The purpose of these two map modes is to let you control the orientation of the axes and the conversion of the units. Therefore, after calling SetMapMode() and specifying the MM_ISOTROPIC (or MM_ANISOTROPIC),  you must call the SetWindowExtEx() function. This function specifies how much each new unit will be multiplied by the old or default unit system. The syntax of the SetWindowExtEx() function:

BOOL SetWindowExtEx(HDC hdc, int nXExtent, int nYExtent, LPSIZE lpSize);

Once again, the first argument to this function is the handle mentioned earlier. The second argument, nXExtent, specifies the maximum logical value of the horizontal axis. The third argument, nYExtent, specifies the maximum logical value of the vertical axis. The last argument, lpSize, is a pointer to a SIZE structure that holds the previous x and y values. If this argument's value is NULL, the argument is ignored. Here is an example:

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

    SetMapMode(hDC, MM_ISOTROPIC);
    SetViewportOrgEx(hDC, 340, 220, NULL);
    SetWindowExtEx(hDC, 480, 480, NULL);

    // Draw a square with a red border and an aqua background
    Canvas->Pen->Color = clRed;
    Canvas->Brush->Color = clAqua;
    Canvas->Rectangle(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    // diagonal line at 45 degrees starting at the origin (0, 0)
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(120, 120);
}
//---------------------------------------------------------------------------

This would produce:

Custom Coordinates and Units

After calling the SetWindowExtEx() function, you should call the SetViewportExtEx() function. Its job is to specify the horizontal and vertical units of the device context being used. It syntax is:

BOOL SetViewportExtEx(HDC hdc, int nXExtent, int nYExtent, LPSIZE lpSize);

The arguments are the same as for the SetWindowExtEx() function except that these apply to the viewport. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
    HDC hDC = Canvas->Handle;
    // This rectangle represents the client area of the form
    TRect Recto = GetClientRect();

    SetMapMode(hDC, MM_ISOTROPIC);
    SetViewportOrgEx(hDC, 340, 220, NULL);
    SetWindowExtEx(hDC, 440, 440, NULL);
    SetViewportExtEx( hDC,
                      440,
                     -680,
                      NULL );

    // Draw a square with a red border and an aqua background
    Canvas->Pen->Color = clRed;
    Canvas->Brush->Color = clAqua;
    Canvas->Rectangle(-100, -100, 100, 100);

    Canvas->Pen->Color = clBlue;
    // diagonal line at 45 degrees starting at the origin (0, 0)
    Canvas->MoveTo(0, 0);
    Canvas->LineTo(120, 120);
}
//---------------------------------------------------------------------------

This would produce:

 


Copyright © 2004-2016, FunctionX, Inc.