Moving the Origin
By default, a device context's origin is in the upper left corner of the display surface. Even if you change the mapping mode, the origin remains in the upper left corner. But just as you can change the mapping mode, you can also move the origin. MFC's CDC class provides two functions for moving the origin. CDC::SetWindowOrg moves the window origin, and CDC::SetViewportOrg moves the viewport origin. You'll normally use one but not both. Using both can be very confusing.
Suppose you'd like to move the origin to the center of the window so that you can center what you draw by centering your output around the point (0,0). Assuming that dc is a device context object, here's one way to do it:
CRect rect;
GetClientRect (&rect);
dc.SetViewportOrg (rect.Width () / 2, rect.Height () / 2);
Here's another way to accomplish the same thing, assuming that you're working in the MM_LOENGLISH mapping mode:
CRect rect;
GetClientRect (&rect);
CPoint point (rect.Width () / 2, rect.Height () / 2);
dc.SetMapMode (MM_LOENGLISH);
dc.DPtoLP (&point);
dc.SetWindowOrg (-point.x, -point.y);
It's easy to get SetViewportOrg and SetWindowOrg confused, but the distinction between them is actually quite clear. Changing the viewport origin to (x,y) with SetViewportOrg tells Windows to map the logical point (0,0) to the device point (x,y). Changing the window origin to (x,y) with SetWindowOrg does essentially the reverse, telling Windows to map the logical point (x,y) to the device point (0,0)—the upper left corner of the display surface. In the MM_TEXT mapping mode, the only real difference between the two functions is the signs of x and y. In other mapping modes, there's more to it than that because SetViewportOrg deals in device coordinates and SetWindowOrg deals in logical coordinates. You'll see examples of how both functions are used later in this chapter.
As a final example, suppose you're drawing in the MM_HIMETRIC mapping mode, where 1 unit equals 1/100 of a millimeter, positive x points to the right, and positive y points upward, and you'd like to move the origin to the lower left corner of the window. Here's an easy way to do it:
CRect rect;
GetClientRect (&rect);
dc.SetViewportOrg (0, rect.Height ());
Now you can draw with positive x and y values using coordinates relative to the window's lower left corner.
A Final Word on Coordinate Systems
When you talk about mapping modes, window origins, viewport origins, and other idioms related to the GDI's handling of coordinates, it's easy to get tangled up in the terminology. Understanding the difference between the device coordinate system and the logical coordinate system might help clear some of the cobwebs.
In the device coordinate system, distances are measured in pixels. The device point (0,0) is always in the upper left corner of the display surface, and the positive x and y axes always point right and downward. The logical coordinate system is altogether different. The origin can be placed anywhere, and both the orientation of the x and y axes and the scaling factor (the number of pixels that correspond to 1 logical unit) vary with the mapping mode. To be precise, they vary with the window extents and the viewport extents. You can change these extents in the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes but not in the other mapping modes.
You'll sometimes hear Windows programmers talk about "client coordinates" and "screen coordinates." Client coordinates are simply device coordinates relative to the upper left corner of a window's client area. Screen coordinates are device coordinates relative to the upper left corner of the screen. You can convert from client coordinates to screen coordinates and vice versa using the CWnd::ClientToScreen and CWnd::ScreenToClient functions. Why these functions are useful will become apparent to you the first time you call a Windows function that returns screen coordinates and you pass them to a function that requires client coordinates, or vice versa.
Getting Information About a Device
Sometimes it's helpful to get information about a device before you send output to it. The CDC::GetDeviceCaps function lets you retrieve all kinds of information about a device, from the number of colors it supports to the number of pixels it can display horizontally and vertically. The following code initializes cx and cy to the width and height of the screen, in pixels:
CClientDC dc (this);
int cx = dc.GetDeviceCaps (HORZRES);
int cy = dc.GetDeviceCaps (VERTRES);
If the screen resolution is 1,024 by 768, cx and cy will be set to 1,024 and 768, respectively.
The table below lists some of the parameters you can pass to GetDeviceCaps to acquire information about the physical output device associated with a device context. How you interpret the results depends somewhat on the device type. For example, calling GetDeviceCaps with a HORZRES parameter for a screen DC returns the screen width in pixels. Make the same call to a printer DC and you get back the width of the printable page, once more in pixels. As a rule, values that imply any kind of scaling (for example, LOGPIXELSX and LOGPIXELSY) return physically correct values for printers and other hardcopy devices but not for screens. For a 600 dpi laser printer, both LOGPIXELSX and LOGPIXELSY return 600. For a screen, both will probably return 96, regardless of the physical screen size or resolution.
Interpreting the color information returned by the NUMCOLORS, BITSPIXEL, and PLANES parameters of GetDeviceCaps is a bit tricky. For a printer or a plotter, you can usually find out how many colors the device is capable of displaying from the NUMCOLORS parameter. For a monochrome printer, NUMCOLORS returns 2.
Useful GetDeviceCaps Parameters
Parameter
Returns
HORZRES
Width of the display surface in pixels
VERTRES
Height of the display surface in pixels
HORZSIZE
Width of the display surface in millimeters
VERTSIZE
Height of the display surface in millimeters
LOGPIXELSX
Number of pixels per logical inch horizontally
LOGPIXELSY
Number of pixels per logical inch vertically
NUMCOLORS
For a display device, the number of static colors; for a printer or plotter, the number of colors supported
BITSPIXEL
Number of bits per pixel
PLANES
Number of bit planes
RASTERCAPS
Bit flags detailing certain characteristics of the device, such as whether it is palettized and whether it can display bitmapped images
TECHNOLOGY
Bit flags identifying the device type—screen, printer, plotter, and so on
However, the color resolution of the screen (the number of colors that can be displayed onscreen simultaneously) is computed by multiplying BITSPIXEL and PLANES and raising 2 to the power of the result, as demonstrated here:
CClientDC dc (this);
int nPlanes = dc.GetDeviceCaps (PLANES);
int nBPP = dc.GetDeviceCaps (BITSPIXEL);
int nColors = 1 << (nPlanes * nBPP);
If this code is executed on a PC equipped with a 256-color video adapter, nColors equals 256. Calling GetDeviceCaps with a NUMCOLORS parameter, meanwhile, returns not 256 but 20—the number of "static colors" that Windows programs into the video adapter's color palette. I'll have more to say about the color characteristics of screens and video adapters and also about static colors in Chapter 15.
I'll use GetDeviceCaps several times in this book to adapt the sample programs' output to the physical attributes of the output device. The first use will come later in this chapter, when the screen's LOGPIXELSX and LOGPIXELSY parameters are used to draw rectangles 1 logical inch long and 1/4 logical inch tall in the MM_TEXT mapping mode.