Home

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, 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 CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	CPen PenBlue;

	// Blue solid pen width = 1
	PenBlue.CreatePen(PS_SOLID, 1, RGB(0, 12, 255));

	dc.SelectObject(&pPen);
	dc.Ellipse(-100, -100, 100, 100);
}

In the same way, you can draw any geometric or non-geometric figure you want, using one of the CPaintDC methods or creating methods 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 CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	CRect Recto;
	CPen PenBlue;

	PenBlue.CreatePen(PS_SOLID, 1, RGB(0, 12, 255));
	dc.SelectObject(&PenBlue);
	dc.Ellipse(-100, -100, 100, 100);

	CPen PenBlack;
	PenBlack.CreatePen(PS_SOLID, 1, BLACK_PEN);
	dc.SelectObject(&PenBlack);

	// Retrive the size of the drawing area
	GetClientRect(&Recto);

	dc.MoveTo(Recto.Width() / 2, 0);
	dc.LineTo(Recto.Width() / 2, Recto.Height());
	dc.MoveTo(0, Recto.Height() / 2);
	dc.LineTo(Recto.Width(), Recto.Height() / 2);
}

 

 

Changing the Coordinate System 

As seen above, the default coordinate system has its origin set on the top-left section of the screen. The horizontal axis moves positively from the origin to the right direction. The vertical axis moves from the origin to the bottom direction. To illustrate this, let's draw a circle with a radius whose center is at the origin (0, 0) with a radius of 50 units. Let's also draw a line from the origin (0, 0)  to (100, 100):

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	// A circle whose center is at the origin (0, 0)
	dc.Ellipse(-50, -50, 50, 50);

	// A line that starts at (0, 0) and ends at (100, 100)
	dc.MoveTo(0, 0);
	dc.LineTo(100, 100);
}

This default origin is fine for most, if not all regular, operations performed on graphics applications. For example, most graphics application, including Paint Shop Pro use this origin. Sometimes, you will need to control the position of the origin of the coordinate system. For example, most CAD applications, including AutoCAD, allow the user to set this origin.

The MFC provides various functions to deal with the coordinates positions and extents of the drawing area, including functions used to set the origin of the coordinate system anywhere you want on the screen. Since you are drawing on a device context, all you need to do is simply call the CDC::SetViewportOrg() method. It is overloaded with two versions, which allow you to use either the X and the Y coordinates or a defined point. The syntaxes of this method are:

SetViewportOrg(int X, int Y);
SetViewportOrg(CPoint Pt);

When calling this function, simply specify where you want the new origin to be. If using the second version, the argument can be a Win32 POINT structure or an MFC TPoint class. To see the effect of this function, let's move the origin 200 units in the positive direction of the X axis and 150 units in the positive direction of the vertical axis without changing the circle and the line. Our OnPaint event would look like this:

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	dc.SetViewportOrg(200, 150);

	// A circle whose center is at the origin (0, 0)
	dc.Ellipse(-50, -50, 50, 50);

	// A line that starts at (0, 0) and ends at (100, 100)
	dc.MoveTo(0, 0);
	dc.LineTo(100, 100);
}

Note that you can also position the origin relative to the size of the client area. Here is an example:

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	CRect Recto;

	// Retrieve the size of the drawing area
	GetClientRect(&Recto);

	dc.SetViewportOrg(Recto.Width() / 2, Recto.Height() / 2);

	// A circle whose center is at the origin (0, 0)
	dc.Ellipse(-50, -50, 50, 50);

	// A line that starts at (0, 0) and ends at (100, 100)
	dc.MoveTo(0, 0);
	dc.LineTo(100, 100);
}

Now that we know how to control the origin, we will position it at a fixed point, 380 units to the right and 220 units down. We can also easily draw the (Cartesian) axes now:

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	CRect Recto;

	dc.SetViewportOrg(380, 220);

	// Use a red pen
	CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
	dc.SelectObject(PenRed);
	// A circle whose center is at the origin (0, 0)
	dc.Ellipse(-100, -100, 100, 100);

	// Use a blue pen
	CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
	dc.SelectObject(PenBlue);
	// Horizontal axis
	dc.MoveTo(-380, 0);
	dc.LineTo(380, 0);
	// Vertical axis
	dc.MoveTo(0, -220);
	dc.LineTo(0, 220);
}

As seen already, the SetViewportOrg() method can be used to change the origin of the device context. It also uses an orientation of axes so that the horizontal axis moves positively from (0, 0) to the right. The vertical axis moves positively from (0, 0) down. 

To illustrate this, let's draw an orange line at 45 from the origin:

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	dc.SetViewportOrg(380, 220);

	// Use a red pen
	CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
	dc.SelectObject(PenRed);
	// A circle whose center is at the origin (0, 0)
	dc.Ellipse(-100, -100, 100, 100);

	// Use a blue pen
	CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
	dc.SelectObject(PenBlue);
	// Horizontal axis
	dc.MoveTo(-380, 0);
	dc.LineTo(380, 0);
	// Vertical axis
	dc.MoveTo(0, -220);
	dc.LineTo(0, 220);

	// An orange pen
	CPen PenOrange(PS_SOLID, 1, RGB(255, 128, 0));
	dc.SelectObject(PenOrange);
	// A diagonal line at 45 degrees
	dc.MoveTo(0, 0);
	dc.LineTo(120, 120);
}

As you can see, our line is not at 45. Instead of being in the first quadrant, it is in the fourth. This is due to the default orientation of the coordinate system.

The Map Modes 

To control the orientation of the axes of the device context, you use a method of the CDC class called SetMapMode(). Its syntax is:

int SetMapMode(int nMapMode);

As you are about to see, this method can be used to do two things, depending on the value of the argument. It can 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. 

The argument of this method is a constant integer that species the mapping mode used. The possible values 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 CDC methods 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 would produce the same result:

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	dc.SetMapMode(MM_TEXT);
	dc.SetViewportOrg(380, 220);

	// Use a red pen
	CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
	dc.SelectObject(PenRed);
	// A circle whose center is at the origin (0, 0)
	dc.Ellipse(-100, -100, 100, 100);

	// Use a blue pen
	CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
	dc.SelectObject(PenBlue);
	// Horizontal axis
	dc.MoveTo(-380, 0);
	dc.LineTo(380, 0);
	// Vertical axis
	dc.MoveTo(0, -220);
	dc.LineTo(0, 220);

	// An orange pen
	CPen PenOrange(PS_SOLID, 1, RGB(255, 128, 0));
	dc.SelectObject(PenOrange);
	// A diagonal line at 45 degrees
	dc.MoveTo(0, 0);
	dc.LineTo(120, 120);
}

The result is the same, as if no map mode was specified:

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 positive y axis would move from (0, 0) up:

Also, each unit of measure is multiplied by 0.01 inch, which means each unit you provide is divided by 100 (unit/100). This also means that the units are reduced from their stated measures by a 100th. Observe the effect of the MM_LOENGLISH map mode on the above OnPaint() event :

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	dc.SetMapMode(MM_LOENGLISH);
	dc.SetViewportOrg(380, 220);

	. . .
}

As you can see, now the lines are drawn respecting the positive and the negative orientations of the axes, fulfilling the requirements of a Cartesian coordinate 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 sets the orientation so the vertical axis moves from (0, 0) up. Unlike the MM_LOENGLISH, the MM_HIENGLISH map mode reduces each unit by a factor of 0.001 inch. This means that each unit is divided by 1000 (1/1000 = 1000th) which is significant and can change the display of a drawing. Here is its effect:

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	dc.SetMapMode(MM_HIENGLISH);
	dc.SetViewportOrg(380, 220);

	. . . Same as previous
}

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. By contrast, the MM_LOMETRIC multiplies each unit by 0.1 millimeter. This means that each unit is reduced by 10%. Here is an example:

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	dc.SetMapMode(MM_LOMETRIC);
	dc.SetViewportOrg(380, 220);

	. . .
}

The MM_HIMETRIC map mode uses the same axes of 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 CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

	dc.SetMapMode(MM_HIMETRIC);
	dc.SetViewportOrg(380, 220);

	. . . Same as previous
}

The MM_TWIPS map mode divides each logical unit by 20. Actually a twip is equivalent to 1/1440 inch. Besides this unit conversion, the axes are oriented so the horizontal axis moves from the origin (0, 0) to the right while the vertical axis moves from the origin (0, 0) up. Here is an example:

void CExoDraw1View::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	CRect Recto;

	dc.SetMapMode(MM_TWIPS);

	dc.SetViewportOrg(380, 220);

	. . .
}

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 the conversion used on the provided dimensions. 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 OnPaint() event. It draws a 200x200 pixels square with a red border and an aqua background. The square starts at 100x100 pixels on the negative sides of both axes and it continues 100x100 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 CExoDraw1View::OnPaint() 
{
    CPaintDC dc(this); // device context for painting
	
    CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
    CBrush BrushAqua(RGB(0, 255, 255));

    dc.SelectObject(PenRed);
    dc.SelectObject(BrushAqua);
    // Draw a square with a red border and an aqua background
    dc.Rectangle(-100, -100, 100, 100);

    CPen BluePen(PS_SOLID, 1, RGB(0, 0, 255));
    dc.SelectObject(BluePen);
    // Diagonal line at 45 degrees starting at the origin (0, 0)
    dc.MoveTo(0, 0);
    dc.LineTo(200, 200);
}

This would produce:

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 quadrant of a clock .

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). We saw already that you could use the CDC::SetViewportOrg() method (keep in mind that this method only changes the origin of the coordinate system; it doesn't influence the orientation of axes nor does it control the units or dimensions) to specify the origin. Here is an example (we are not specifying the map mode because MM_TEXT can be used for us as the default):

void CExoDraw1View::OnPaint() 
{
    CPaintDC dc(this); // device context for painting
	
    dc.SetViewportOrg(340, 220);

    CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
    CBrush BrushAqua(RGB(0, 255, 255));

    dc.SelectObject(PenRed);
    dc.SelectObject(BrushAqua);
    // Draw a square with a red border and an aqua background
    dc.Rectangle(-100, -100, 100, 100);

    CPen BluePen(PS_SOLID, 1, RGB(0, 0, 255));
    dc.SelectObject(BluePen);
    // Diagonal line at 45 degrees starting at the origin (0, 0)
    dc.MoveTo(0, 0);
    dc.LineTo(200, 200);
}

This would produce:

To control your own unit system, the orientation of the axes or 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 CDC::SetMapMode() method and specify one of these two constants (either MM_ISOTROPIC or MM_ANISOTROPIC). Here is an example:

void CExoDraw1View::OnPaint() 
{
    CPaintDC dc(this); // device context for painting
	
    dc.SetMapMode(MM_ISOTROPIC);
    dc.SetViewportOrg(340, 220);

    CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
    CBrush BrushAqua(RGB(0, 255, 255));

    dc.SelectObject(PenRed);
    dc.SelectObject(BrushAqua);
    // Draw a square with a red border and an aqua background
    dc.Rectangle(-100, -100, 100, 100);

    CPen BluePen(PS_SOLID, 1, RGB(0, 0, 255));
    dc.SelectObject(BluePen);
    // Diagonal line at 45 degrees starting at the origin (0, 0)
    dc.MoveTo(0, 0);
    dc.LineTo(200, 200);
}

Don't rely on the above picture, after calling the CDC::SetMapMode() method 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.

The difference between both map modes is that, when using the MM_ISOTROPIC map mode, one unit in the horizontal axis is equivalent to one unit in the vertical axis. This is not the case for the MM_ANISOTROPIC map mode which allows you to control however the units should be converted on each individual axis.

Therefore, after calling SetMapMode() and specifying the MM_ISOTROPIC (or MM_ANISOTROPIC),  you must call the CDC:SetWindowExt() method. This method specifies how much each new unit will be multiplied by the old or default unit system. The CDC::SetWindowExt() method comes in two versions with the following syntaxes:

CSize SetWindowExt(int cx, int cy);
CSize SetWindowExt(SIZE size);

If using the first version, the first argument to this method, cx, specifies the logical conversion multiplier used for each unit on the horizontal axis. The second argument, cy, specifies the logical conversion multiplier used for each unit on the vertical axis.

The second version of the method can be used if you know the desired logical width and height as a SIZE object. Here is an example:

void CExoDraw1View::OnPaint() 
{
    CPaintDC dc(this); // device context for painting
	
    dc.SetMapMode(MM_ISOTROPIC);
    dc.SetViewportOrg(340, 220);
    dc.SetWindowExt(480, 480);

    CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
    CBrush BrushAqua(RGB(0, 255, 255));

    dc.SelectObject(PenRed);
    dc.SelectObject(BrushAqua);
    // Draw a square with a red border and an aqua background
    dc.Rectangle(-100, -100, 100, 100);

    CPen BluePen(PS_SOLID, 1, RGB(0, 0, 255));
    dc.SelectObject(BluePen);
    // Diagonal line at 45 degrees starting at the origin (0, 0)
    dc.MoveTo(0, 0);
    dc.LineTo(200, 200);
}

This would produce:

After calling the SetWindowExt() function, you should call the SetViewportExt() function. Its job is to specify the horizontal and vertical units of the device context being used. It comes in two flavors with the following syntaxes:

CSize SetViewportExt(int cx, int cy);
CSize SetViewportExt(SIZE size);

To use the first version of this function, you must provide the units of device conversion as cx for the horizontal axis and as cy for the vertical axis.

If you know the size as a width/height combination of the device unit conversion, you can use the second version of the method and supply this size argument.

Here is an example:

void CExoDraw1View::OnPaint() 
{
    CPaintDC dc(this); // device context for painting
	
    dc.SetMapMode(MM_ISOTROPIC);
    dc.SetViewportOrg(340, 220);
    dc.SetWindowExt(480, 480);
    dc.SetViewportExt(440, -680);

    CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
    CBrush BrushAqua(RGB(0, 255, 255));

    dc.SelectObject(PenRed);
    dc.SelectObject(BrushAqua);
    // Draw a square with a red border and an aqua background
    dc.Rectangle(-100, -100, 100, 100);

    CPen BluePen(PS_SOLID, 1, RGB(0, 0, 255));
    dc.SelectObject(BluePen);
    // Diagonal line at 45 degrees starting at the origin (0, 0)
    dc.MoveTo(0, 0);
    dc.LineTo(200, 200);
}

This would produce:


Example drawing axes orientation arrows:

void CExoDraw1View::OnPaint() 
{
    CPaintDC dc(this); // device context for painting

    CBrush bgBrush(BLACK_BRUSH);
    dc.SelectObject(bgBrush);
    dc.Rectangle(Recto);

    dc.SetMapMode(MM_ISOTROPIC);
    dc.SetViewportOrg(0, 440);
    dc.SetWindowExt(480, 480);
    dc.SetViewportExt(440, -680);

    CPen PenWhite(PS_SOLID, 1, RGB(255, 255, 255));
    dc.SelectObject(PenWhite);

    dc.MoveTo(21, 20);
    dc.LineTo(21, 75);
    // Up arrow
    dc.MoveTo(16, 75);
    dc.LineTo(21, 90);
    dc.LineTo(26, 75);
    dc.LineTo(16, 75);

    dc.MoveTo(21, 22);
    dc.LineTo(75, 22);
    // Right arrow
    dc.MoveTo(75, 17);								
    dc.LineTo(90, 22);
    dc.LineTo(75, 27);
    dc.LineTo(75, 17);

    dc.SetBkMode(TRANSPARENT);
    dc.SetTextColor(RGB(255, 255, 255));
    dc.TextOut(16, 114, 'Y');
    dc.TextOut(100, 32, 'X');
    dc.Rectangle(15, 15, 30, 30);
}


Line Grid

void CExoDraw1View::OnPaint() 
{
    CPaintDC dc(this); // device context for painting
    CRect Recto;

    GetClientRect(&Recto);

    CBrush bgBrush(BLACK_BRUSH);
    dc.SelectObject(bgBrush);
    dc.Rectangle(Recto);

    CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
    dc.SelectObject(PenBlue);

    for(int x = 0; x < Recto.Width(); x += 20)
    {
        dc.MoveTo(x, 0);
        dc.LineTo(x, Recto.Height());
    }

    for(int y = 0; y < Recto.Height(); y += 20)
    {
        dc.MoveTo(0, y);
        dc.LineTo(Recto.Width(), y);
    }
}


Point Grid

void CExoDraw1View::OnPaint() 
{
    CPaintDC dc(this); // device context for painting
    CRect Recto;

    GetClientRect(&Recto);

    CBrush bgBrush(BLACK_BRUSH);
    dc.SelectObject(bgBrush);
    dc.Rectangle(Recto);

    for(int x = 0; x < Recto.Width(); x += 20)
    {
        for(int y = 0; y < Recto.Height(); y += 20)
        {
		dc.SetPixel(x, y, RGB(255, 255, 255));
        }
    }
}

Sinus

void CExoView::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	// TODO: Add your message handler code here
	dc.SetMapMode(MM_ANISOTROPIC);
	dc.SetViewportOrg(340, 220);
	dc.SetWindowExt(1440, 1440);
	dc.SetViewportExt(-1440, -220);

	CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
	dc.SelectObject(PenBlue);

	// Axes
	dc.MoveTo(-300,     0);
	dc.LineTo( 300,     0);
	dc.MoveTo(   0, -1400);
	dc.LineTo(   0,  1400);

	// I am exaggerating with the PI value here but why not?
	const double PI = 3.141592653589793238462643383279;
	
	// The following two values were chosen randomly by me.
	// You can chose other values you like
	const int MultiplyEachUnitOnX = 50;
	const int MultiplyEachUnitOnY = 250;

	for(double i = -280; i < 280; i += 0.01)
	{
	    double j = sin(PI / MultiplyEachUnitOnX * i) * MultiplyEachUnitOnY;
	    dc.SetPixel(i, j, RGB(255, 0, 0));
	}
	// Do not call CView::OnPaint() for painting messages
}


Copyright © 2003-2005 FunctionX, Inc.