Get a site

VC++ 6.0 ebook chapter index
homepage aranna.altervista.org
Free counters!

The GDI Mapping Mode

Up until now, all the sample programs have been drawing in units of pixels relative to the upper left corner of the client area. This is the default, but it's not your only choice. One device context attribute that affects virtually all the drawing you do on the client area is the "mapping mode." Four other device context attributes—the window origin, the viewport origin, the window extents, and the viewport extents—are closely related to the mapping mode attribute.

Most of the GDI drawing functions require coordinate values or sizes. For instance, this is the TextOut function:

TextOut (hdc, x, y, psText, iLength) ;

The x and y arguments indicate the starting position of the text. The x argument is the position on the horizontal axis, and the y argument is the position on the vertical axis. Often the notation (x,y) is used to indicate this point.

In TextOut, as in virtually all GDI functions, these coordinate values are "logical units." Windows must translate the logical units into "device units," or pixels. This translation is governed by the mapping mode, the window and viewport origins, and the window and viewport extents. The mapping mode also implies an orientation of the x-axis and the y-axis; that is, it determines whether values of x increase as you move toward the left or right side of the display and whether values of y increase as you move up or down the display.

Windows defines eight mapping modes. These are listed in the following table using the identifiers defined in WINGDI.H.

Increasing Value
Mapping Mode Logical Unit x-axis y-axis
MM_TEXT Pixel Right Down
MM_LOMETRIC 0.1 mm Right Up
MM_HIMETRIC 0.01 mm Right Up
MM_LOENGLISH 0.01 in. Right Up
MM_HIENGLISH 0.001 in. Right Up
MM_TWIPS 1/1440 in. Right Up
MM_ISOTROPIC Arbitrary (x = y) Selectable Selectable
MM_ANISOTROPIC Arbitrary (x !=y) Selectable Selectable

The words METRIC and ENGLISH refer to popular systems of measurement; LO and HI are "low" and "high" and refer to precision. "Twip" is a fabricated word meaning "twentieth of a point." I mentioned earlier that a point is a unit of measurement in typography that is approximately 1/72 inch but that is often assumed in graphics programming to be exactly 1/72 inch. A "twip" is 1/20 point and hence 1/1440 inch. "Isotropic" and "anisotropic" are actually real words, meaning "identical in all directions" and "not isotropic," respectively.

You can set the mapping mode by using

SetMapMode (hdc, iMapMode) ;

where iMapMode is one of the eight mapping mode identifiers. You can obtain the current mapping mode by calling

iMapMode = GetMapMode (hdc) ;

The default mapping mode is MM_TEXT. In this mapping mode, logical units are the same as physical units, which allows us (or, depending on your perspective, forces us) to work directly in units of pixels. In a TextOut call that looks like this:

TextOut (hdc, 8, 16, TEXT ("Hello"), 5) ;

the text begins 8 pixels from the left of the client area and 16 pixels from the top.

If the mapping mode is set to MM_LOENGLISH like so,

SetMapMode (hdc, MM_LOENGLISH) ;

logical units are in terms of hundredths of an inch. Now the TextOut call might look like this:

TextOut (hdc, 50, -100, TEXT ("Hello"), 5) ;

The text begins 0.5 inch from the left and 1 inch from the top of the client area. (The reason for the negative sign in front of the y-coordinate will soon become clear when I discuss the mapping modes in more detail.) Other mapping modes allow programs to specify coordinates in terms of millimeters, a point size, or an arbitrarily scaled axis.

If you feel comfortable working in units of pixels, you don't need to use any mapping modes except the default MM_TEXT mode. If you need to display an image in inch or millimeter dimensions, you can obtain the information you need from GetDeviceCaps and do your own scaling. The other mapping modes are simply a convenient way to avoid doing your own scaling.

Although the coordinates you specify in GDI functions are 32-bit values, only Windows NT can handle all 32 bits. In Windows 98, coordinates are limited to 16 bits and thus may range only from -32,768 to 32,767. Some Windows functions that use coordinates for the starting point and ending point of a rectangle also require that the width and height of the rectangle be 32,767 or less.

Device Coordinates and Logical Coordinates

You may ask: if I use the MM_LOENGLISH mapping mode, will I start getting WM_SIZE messages in terms of hundredths of an inch? Absolutely not. Windows continues to use device coordinates for all messages (such as WM_MOVE, WM_SIZE, and WM_MOUSEMOVE), for all non-GDI functions, and even for some GDI functions. Think of it this way: the mapping mode is an attribute of the device context, so the only time the mapping mode comes into play is when you use GDI functions that require a handle to the device context as one of the arguments. GetSystemMetrics is not a GDI function, so it will continue to return sizes in device units, which are pixels. And although GetDeviceCaps is a GDI function that requires a handle to a device context, Windows continues to return device units for the HORZRES and VERTRES indexes, because one of the purposes of this function is to provide a program with the size of the device in pixels.

However, the values in the TEXTMETRIC structure that you obtain from the GetTextMetrics call are in terms of logical units. If the mapping mode is MM_LOENGLISH at the time the call is made, GetTextMetrics provides character widths and heights in terms of hundredths of an inch. To make things easy on yourself, when you call GetTextMetrics for information about the height and width of characters, the mapping mode should be set to the same mapping mode that you'll be using when you draw text based on these sizes.

The Device Coordinate Systems

Windows maps logical coordinates that are specified in GDI functions to device coordinates. Before we discuss the logical coordinate system used with the various mapping modes, let's examine the different device coordinate systems that Windows defines for the video display. Although we have been working mostly within the client area of our window, Windows uses two other device coordinate systems at various times. In all device coordinate systems, units are expressed in terms of pixels. Values on the horizontal x-axis increase from left to right, and values on the vertical y-axis increase from top to bottom.

When we use the entire screen, we are working in terms of "screen coordinates." The upper left corner of the screen is the point (0, 0). Screen coordinates are used in the WM_MOVE message (for nonchild windows) and in the following Windows functions: CreateWindow and MoveWindow (for nonchild windows), GetMessagePos, GetCursorPos, SetCursorPos, GetWindowRect, and WindowFromPoint. (This is not a complete list.) These are generally either functions that don't have a window associated with them (such as the two cursor functions) or functions that must move or find a window based on a screen point. If you use CreateDC with a "DISPLAY" argument to obtain a device context for the entire screen, logical coordinates in GDI calls will be mapped to screen coordinates by default.

"Whole-window coordinates" refer to a program's entire application window, including the title bar, menu, scroll bars, and border. For a common application window, the point (0, 0) is the upper left corner of the sizing border. Whole-window coordinates are rare in Windows, but if you obtain a device context from GetWindowDC, logical coordinates in GDI functions will be mapped to whole-window coordinates by default.

The third device coordinate system—the one we've been working with the most—uses "client area coordinates." The point (0, 0) is the upper left corner of the client area. When you obtain a device context using GetDC or BeginPaint, logical coordinates in GDI functions will be translated to client-area coordinates by default.

You can convert client-area coordinates to screen coordinates and vice versa using the functions ClientToScreen and ScreenToClient. You can also obtain the position and size of the whole window in terms of screen coordinates using the GetWindowRect functions. These three functions provide enough information to translate from any one device coordinate system to the other.

The Viewport and the Window

The mapping mode defines how Windows maps logical coordinates that are specified in GDI functions to device coordinates, where the particular device coordinate system depends on the function you use to obtain the device context. To continue this discussion of the mapping mode, we need some additional terminology. The mapping mode is said to define the mapping of the "window" (logical coordinates) to the "viewport" (device coordinates).

The use of these two terms is unfortunate. In other graphics interface systems, the viewport often implies a clipping region. And in Windows, the term "window" has a very specific meaning to describe the area that a program occupies on the screen. We'll have to put aside our preconceptions of these terms during this discussion.

The viewport is specified in terms of device coordinates (pixels). Most often the viewport is the same as the client area, but it can also refer to whole-window coordinates or screen coordinates if you've obtained a device context from GetWindowDC or CreateDC. The point (0, 0) is the upper left corner of the client area (or the whole window or the screen). Values of x increase to the right, and values of y increase going down.

The window is specified in terms of logical coordinates, which might be pixels, millimeters, inches, or any other unit you want. You specify logical window coordinates in the GDI drawing functions.

But in a very real sense, the viewport and the window are just mathematical constructs. For all mapping modes, Windows translates window (logical) coordinates to viewport (device) coordinates by the use of two formulas,

                                  xViewExt
xViewport = (xWindow - xWinOrg) × ________ + xViewOrg
                                  xWinExt
     

                                  yViewExt
yViewport = (yWindow - yWinOrg) × ________  + yViewOrg
                                  yWinExt

where (xWindow, yWindow) is a logical point to be translated and (xViewport, yViewport) is the translated point in device coordinates, most likely client-area coordinates.

These formulas use two points that specify an "origin" of the window and the viewport. The point (xWinOrg, yWinOrg) is the window origin in logical coordinates; the point (xViewOrg, yViewOrg) is the viewport origin in device coordinates. By default, these two points are set to (0, 0), but you can change them. The formulas imply that the logical point (xWinOrg, yWinOrg) is always mapped to the device point (xViewOrg, yViewOrg). If the window and viewport origins are left at their default (0, 0) values, the formulas simplify to

                      xViewExt
xViewport = xWindow × ________
                      xWinExt

                      yViewExt
yViewport = yWindow × ________
                      yWinExt

The formulas also include two points that specify "extents": the point (xWinExt, yWinExt) is the window extent in logical coordinates; (xViewExt, yViewExt) is the viewport extent in device coordinates. In most mapping modes, the extents are implied by the mapping mode and cannot be changed. Each extent means nothing by itself, but the ratio of the viewport extent to the window extent is a scaling factor for converting logical units to device units.

For example, when you set the MM_LOENGLISH mapping mode, Windows sets xViewExt to be a certain number of pixels and xWinExt to be the length in hundredths of an inch occupied by xViewExt pixels. The ratio gives you pixels per hundredths of an inch. The scaling factors are expressed as ratios of integers rather than floating point values for performance reasons.

The extents can be negative. This implies that values on the logical x-axis don't necessarily have to increase to the right and that values on the logical y-axis don't necessarily have to increase going down.

Windows can also translate from viewport (device) coordinates to window (logical) coordinates:

                                   xWinExt
xWindow = (xViewport - xViewOrg) × ________ + xWinOrg
                                   xViewExt

                                   yWinExt
yWindow = (yViewport - yViewOrg) × ________ + yWinOrg
                                   yViewExt

Windows provides two functions that let you convert between device points to logical points in a program. The following function converts device points to logical points:

DPtoLP (hdc, pPoints, iNumber) ;

The variable pPoints is a pointer to an array of POINT structures, and iNumber is the number of points to be converted. For example, you'll find this function useful for converting the size of the client area obtained from GetClientRect (which is always in terms of device units) to logical coordinates:

GetClientRect (hwnd, &rect) ;
DPtoLP (hdc, (PPOINT) &rect, 2) ;

This function converts logical points to device points:

LPtoDP (hdc, pPoints, iNumber) ;

Working with MM_TEXT

For the MM_TEXT mapping mode, the default origins and extents are shown below.
Window origin: (0, 0) Can be changed
Viewport origin: (0, 0) Can be changed
Window extent: (1, 1) Cannot be changed
Viewport extent: (1, 1) Cannot be changed

The ratio of the viewport extent to the window extent is 1, so no scaling is performed between logical coordinates and device coordinates. The formulas to convert from window coordinates to viewport coordinates shown earlier reduce to these:

xViewport = xWindow - xWinOrg + xViewOrg
yViewport = yWindow - yWinOrg + yViewOrg

This is a "text" mapping mode not because it is most suitable for text but because of the orientation of the axes. In most languages, text is read from left to right and top to bottom, and MM_TEXT defines values on the axes to increase the same way:

Windows provides the functions SetViewportOrgEx and SetWindowOrgEx for changing the viewport and window origins. These functions have the effect of shifting the axes so that the logical point (0, 0) no longer refers to the upper left corner. Generally, you'll use either SetViewportOrgEx or SetWindowOrgEx but not both.

Here's how the functions work: If you change the viewport origin to (xViewOrg, yViewOrg), the logical point (0, 0) will be mapped to the device point (xViewOrg, yViewOrg). If you change the window origin to (xWinOrg, yWinOrg), the logical point (xWinOrg, yWinOrg) will be mapped to the device point (0, 0), which is the upper left corner. Regardless of any changes you make to the window and viewport origins, the device point (0, 0) is always the upper left corner of the client area.

For instance, suppose your client area is cxClient pixels wide and cyClient pixels high. If you want to define the logical point (0, 0) to be the center of the client area, you can do so by calling

SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;

The arguments to SetViewportOrgEx are always in terms of device units. The logical point (0, 0) will now be mapped to the device point (cxClient / 2, cyClient / 2). Now you can use your client area as if it had the coordinate system shown below.

The logical x-axis ranges from -cxClient/2 to +cxClient/2, and the logical y-axis ranges from -cyClient/2 to +cyClient/2. The lower right corner of the client area is the logical point (cxClient/2, cyClient/2). If you want to display text starting at the upper left corner of the client area, which is the device point (0, 0), you need to use negative coordinates:

TextOut (hdc, -cxClient / 2, -cyClient / 2, "Hello", 5) ;

You can achieve the same result with SetWindowOrgEx as you did when you used SetViewportOrgEx:

SetWindowOrgEx (hdc, -cxClient / 2, -cyClient / 2, NULL) ;

The arguments to SetWindowOrgEx are always in terms of logical units. After this call, the logical point (-cxClient / 2, -cyClient / 2) is mapped to the device point (0, 0), the upper left corner of the client area.

What you probably don't want to do (unless you know what's going to happen) is to use both function calls together:

SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;
SetWindowOrgEx (hdc, -cxClient / 2, -cyClient / 2, NULL) ;

This means that the logical point (-cxClient/2, -cyClient/2) is mapped to the device point (cxClient/2, cyClient/2), giving you a coordinate system that looks like this:

You can obtain the current viewport and window origins from these functions:

GetViewportOrgEx (hdc, &pt) ;
GetWindowOrgEx (hdc, &pt) ;

where pt is a POINT structure. The values returned from GetViewportOrgEx are in device coordinates; the values returned from GetWindowOrgEx are in logical coordinates.

You might want to change the viewport or window origin to shift display output within the client area of your window—for instance, in response to scroll bar input from the user. For example, in the SYSMETS2 program in Chapter 4, we used the iVscrollPos value (the current position of the vertical scroll bar) to adjust the y-coordinates of the display output:

case WM_PAINT:
     hdc = BeginPaint (hwnd, &ps) ;
     
     for (i = 0 ; i < NUMLINES ; i++)
     {
          y = cyChar * (i - iVscrollPos) ;
          [display text]     
     }
     EndPaint (hwnd, &ps) ;
     return 0 ;

We can achieve the same result using SetWindowOrgEx:

case WM_PAINT:
     hdc = BeginPaint (hwnd, &ps) ;

     SetWindowOrgEx (hdc, 0, cyChar * iVscrollPos) ;

     for (i = 0 ; i < NUMLINES ; i++)
     {
          y = cyChar * i ;
          [display text]
     }
     EndPaint (hwnd, &ps) ;
     return 0 ;

Now the calculation of the y-coordinate for the TextOut functions doesn't require the iVscrollPos value. This means that you can put the text output calls in a separate function and not have to pass the iVscrollPos value to the function, because the display is adjusted by changing the window origin.

If you have some experience working with rectangular (or Cartesian) coordinate systems, moving the logical point (0, 0) to the center of the client area as we did earlier may have seemed a reasonable action. However, there's a slight problem with the MM_TEXT mapping mode. Usually a Cartesian coordinate system defines values on the y-axis as increasing as you move up the axis, whereas MM_TEXT defines the values to increase as you move down the axis. In this sense, MM_TEXT is an oddity, and the next five mapping modes do it correctly.

The Metric Mapping Modes

Windows includes five mapping modes that express logical coordinates in physical measurements. Because logical coordinates on the x-axis and y-axis are mapped to identical physical units, these mapping modes help you to draw round circles and square squares, even on a device that does not feature square pixels.

The five metric mapping modes are arranged below in order of lowest precision to highest precision. The two columns at the right show the size of the logical units in terms of inches (in.) and millimeters (mm.) for comparison.

Mapping Mode Logical Unit Inch Millimeter
MM_LOENGLISH 0.01 in. 0.01 0.254
MM_LOMETRIC 0.1 mm. 0.00394 0.1
MM_HIENGLISH 0.001 in. 0.001 0.0254
MM_TWIPS 1/1400 in. 0.000694 0.0176
MM_HIMETRIC 0.01 mm. 0.000394 0.01

The default window and viewport origins and extents are

Window origin: (0, 0) Can be changed
Viewport origin: (0, 0) Can be changed
Window extent: (?, ?) Cannot be changed
Viewport extent: (?, ?) Cannot be changed

The question marks indicate that the window and viewport extents depend on the mapping mode and the resolution of the device. As I mentioned earlier, the extents aren't important by themselves but take on meaning when expressed as ratios. Here are the translation formulas again:

                                  xViewExt
xViewport = (xWindow - xWinOrg) × ________ + xViewOrg 
                                  xWinExt

                                  yViewExt
yViewport = (yWindow - yWinOrg) × ________ + yViewOrg
                                  yWinExt

For MM_LOENGLISH, for example, Windows calculates the extents to be the following:

     
xViewExt/xWinExt = number of horizontal pixels in 0.01 in.

-yViewExt/yWinExt = negative number of vertical pixels in 0.01 in.

Windows uses information available from GetDeviceCaps to set these extents. This is somewhat different in Windows 98 and Windows NT.

First, here's how it works in Windows 98: Suppose you have used the Display applet of the Control Panel to select a 96 dpi system font. GetDeviceCaps will return a value of 96 for both the LOGPIXELSX and LOGPIXELSY indexes. Windows uses these values for the viewport extents and sets the viewport and window extents as shown in the following table.

Mapping Mode Viewport Extents (x, y) Window Extents (x, y)
MM_LOMETRIC (96, 96) (254, -254)
MM_HIMETRIC (96, 96) (2540, -2540)
MM_LOENGLISH (96, 96) (100, -100)
MM_HIENGLISH (96, 96) (1000, -1000)
MM_TWIPS (96, 96) (1440, -1440)

Thus, for MM_LOENGLISH, the ratio 96 divided by 100 is the number of pixels in 0.01 inches. For MM_LOMETRIC, the ratio 96 divided by 254 is the number of pixels in 0.1 millimeters.

Windows NT uses a different approach to set the viewport and window extents (an approach actually consistent with earlier 16-bit versions of Windows). The viewport extents are based on the pixel dimensions of the screen. This is information obtained from GetDeviceCaps using the HORZRES and VERTRES indexes. The window extents are based on the assumed size of the display, which GetDeviceCaps returns when you use the HORZSIZE and VERTSIZE indexes. As I mentioned earlier, these values are commonly 320 and 240 millimeters. If you've set the pixel dimensions of your display to 1024 by 768, here are the values of the viewport and window extents that Windows NT reports.

Mapping Mode Viewport Extents (x, y) Window Extents (x, y)
MM_LOMETRIC (1024, -768) (3200, 2400)
MM_HIMETRIC (1024, -768) (32000, 24000)
MM_LOENGLISH (1024, -768) (1260, 945)
MM_HIENGLISH (1024, -768) (12598, 9449)
MM_TWIPS (1024, -768) (18142, 13606)

These window extents represent the number of logical units encompassing the full width and height of the display. A 320-millimeters wide screen is also 1260 MM_LOENGLISH units or 12.6 inches (320 divided by 25.4 millimeters per inch).

Those negative signs in front of the y extents change the orientation of the axis. For these five mapping modes, y values increase as you move up the device. However, notice that the default window and viewport origins are both (0, 0). This has an interesting implication. When you first change to one of these five mapping modes, the coordinate system looks like the graph below.

The only way you can display anything in the client area is to use negative values of y. For instance, this code,

SetMapMode (hdc, MM_LOENGLISH) ;
TextOut (hdc, 100, -100, "Hello", 5) ;

displays the text one inch from the top and left edges of the client area.

To preserve your sanity, you'll probably want to avoid this. One solution is to set the logical (0, 0) point to the lower left corner of the client area. Assuming that cyClient is the height of the client area in pixels, you can do this by calling SetViewportOrgEx:

SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

Now the coordinate system looks like this:

This is the upper right quadrant of a rectangular coordinate system.

Alternatively, you can set the logical (0, 0) point to the center of the client area:

SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;

The coordinate system looks like this:

Now we have a real four-quadrant Cartesian coordinate system with equal logical units on the x-axis and y-axis in terms of inches, millimeters, or twips.

You can also use the SetWindowOrgEx function to change the logical (0, 0) point, but the task is a little more difficult because the arguments to SetWindowOrgEx have to be in logical coordinates. You would first need to convert (cxClient, cyClient) to a logical coordinate using the DPtoLP function. Assuming that the variable pt is a structure of type POINT, this code changes the logical (0, 0) point to the center of the client area:

pt.x = cxClient ;
pt.y = cyClient ;
DptoLP (hdc, &pt, 1) ;
SetWindowOrgEx (hdc, -pt.x / 2, -pt.y / 2, NULL) ;

The "Roll Your Own" Mapping Modes

The two remaining mapping modes are named MM_ISOTROPIC and MM_ANISOTROPIC. These are the only two mapping modes for which Windows lets you change the viewport and window extents, which means that you can change the scaling factor that Windows uses to translate logical and device coordinates. The word isotropic means "equal in all directions"; anisotropic is the opposite—"not equal." Like the metric mapping modes shown earlier, MM_ISOTROPIC uses equally scaled axes. Logical units on the x-axis have the same physical dimensions as logical units on the y-axis. This helps when you need to create images that retain the correct aspect ratio regardless of the aspect ratio of the display device.

The difference between MM_ISOTROPIC and the metric mapping modes is that with MM_ISOTROPIC you can control the physical size of the logical unit. If you want, you can adjust the size of the logical unit based on the client area. This lets you draw images that are always contained within the client area, shrinking and expanding appropriately. The two clock programs in Chapter 8 have isotropic images. As you size the window, the clocks are resized appropriately.

A Windows program can handle the resizing of an image entirely through adjusting the window and viewport extents. The program can then use the same logical units in the drawing functions regardless of the size of the window.

Sometimes MM_TEXT and the metric mapping modes are called "fully constrained" mapping modes. This means that you cannot change the window and viewport extents and the way Windows scales logical coordinates to device coordinates. MM_ISOTROPIC is a "partly constrained" mapping mode. Windows allows you to change the window and viewport extents, but it adjusts them so that x and y logical units represent the same physical dimensions. The MM_ANISOTROPIC mapping mode is "unconstrained." You can change the window and viewport extents, and Windows doesn't adjust the values.

The MM_ISOTROPIC Mapping Mode

The MM_ISOTROPIC mapping mode is ideal for using arbitrarily scaled axes while preserving equal logical units on the two axes. Rectangles with equal logical widths and heights are displayed as squares, and ellipses with equal logical widths and heights are displayed as circles.

When you first set the mapping mode to MM_ISOTROPIC, Windows uses the same window and viewport extents that it uses with MM_LOMETRIC. (Don't rely on this fact, however.) The difference is that you can now change the extents to suit your preferences by calling SetWindowExtEx and SetViewportExtEx. Windows will then adjust the extents so that the logical units on both axes represent equal physical distances.

Generally, you'll use arguments to SetWindowExtEx with the desired logical size of the logical windows, and arguments to SetViewportExtEx with the actual height and width of the client area. When Windows adjusts these extents, it has to fit the logical window within the physical viewport, which can result in a section of the client area falling outside the logical window. You should call SetWindowExtEx before you call SetViewportExtEx to make the most efficient use of space in the client area.

For example, suppose you want a traditional one-quadrant virtual coordinate system where (0, 0) is at the lower left corner of the client area and the logical width and height ranges from 0 to 32,767. You want the x and y units to have the same physical dimensions. Here's what you need to do:

SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 32767, 32767, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

If you then obtain the window and viewport extents using GetWindowExtEx and GetViewportExtEx, you'll find that they are not the values you specified. Windows has adjusted the extents based on the aspect ratio of the display device so that logical units on the two axes represent the same physical dimensions.

If the client area is wider than it is high (in physical dimensions), Windows adjusts the x extents so that the logical window is narrower than the client-area viewport. The logical window will be positioned at the left of the client area:

Windows 98 will actually not allow you to display anything in the right side of the client area because it is limited to 16-bit signed coordinates. Windows NT uses a full 32-bits for coordinates, and you would be able to display something over in the right side.

If the client area is higher than it is wide (in physical dimensions), Windows adjust the y extents. The logical window will be positioned at the bottom of the client area:

Windows 98 will not allow you to display anything at the top of the client area.

If you prefer that the logical window always be positioned at the left and top of the client area, you can change the code to the following:

SetMapMode (MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 32767, 32767, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetWindowOrgEx (hdc, 0, 32767, NULL) ;

In the SetWindowOrgEx call, we're saying that we want the logical point (0, 32767) to be mapped to the device point (0, 0). Now, if the client area is higher than it is wide, the coordinates are arranged like this:

For a clock program, you might want to use a four-quadrant Cartesian coordinate system with arbitrarily scaled axes in four directions in which the logical point (0, 0) is in the center of the client area. If you want each axis to range from 0 to 1000 (for instance), you use this code:

SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 1000, 1000, NULL) ;
SetViewportExtEx (hdc, cxClient / 2, -cyClient / 2, NULL) ;
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;

The logical coordinates look like this if the client area is wider than it is high:

The logical coordinates are also centered if the client area is higher than it is wide, as shown below.

Keep in mind that no clipping is implied in window or viewport extents. When calling GDI functions, you are still free to use logical x and y values less than -1000 and greater than +1000. Depending on the shape of the client area, these points might or might not be visible.

With the MM_ISOTROPIC mapping mode, you can make logical units larger than pixels. For instance, suppose you want a mapping mode with the point (0, 0) at the upper left corner of the display and values of y increasing as you move down (like MM_TEXT) but with logical coordinates in sixteenths of an inch. Here's one way to do it:

SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 16, 16, NULL) ;
SetViewportExtEx (hdc, GetDeviceCaps (hdc, LOGPIXELSX),
                       GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;

The arguments to the SetWindowExtEx function indicate the number of logical units in one inch. The arguments to the SetViewportExtEx function indicate the number of physical units (pixels) in one inch.

However, this approach would not be consistent with the metric mapping modes in Windows NT. These mapping modes use the pixel size and metric size of the display. To be consistent with the metric mapping modes, you can use this code:

SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 160 * GetDeviceCaps (hdc, HORZSIZE) / 254,
                     160 * GetDeviceCaps (hdc, VERTSIZE) / 254, NULL) ;
SetViewportExtEx (hdc, GetDeviceCaps (hdc, HORZRES),
                       GetDeviceCaps (hdc, VERTRES), NULL) ;

In this code, the viewport extents are set to the pixel dimensions of the entire screen. The window extents are set to the assumed dimension of the screen in units of sixteenths of an inch. GetDeviceCaps with the HORZRES and VERTRES indexes return the dimensions of the device in millimeters. If we were working with floating-point numbers, we would convert the millimeters to inches by dividing by 25.4 and then convert inches to sixteenths of an inch by multiplying by 16. However, because we're working with integers, we must multiply by 160 and divide by 254.

Of course, such a coordinate system makes logical units much larger than physical units. Everything you draw on the device will have coordinate values that map to an increment of 1/16 inch. You cannot draw two horizontal lines that are 1/32 inch apart because that would require a fractional logical coordinate.

MM_ANISOTROPIC: Stretching the Image to Fit

When you set the viewport and window extents in the MM_ISOTROPIC mapping mode, Windows adjusts the values so that logical units on the two axes have the same physical dimensions. In the MM_ANISOTROPIC mapping mode, Windows makes no adjustments to the values you set. This means that MM_ANISOTROPIC does not necessarily maintain the correct aspect ratio.

One way you can use MM_ANISOTROPIC is to have arbitrary coordinates for the client area, as we did with MM_ISOTROPIC. This code sets the point (0, 0) at the lower left corner of the client area with the x and y axes ranging from 0 to 32,767:

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 32767, 32767, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

With MM_ISOTROPIC, similar code caused part of the client area to be beyond the range of the axes. With MM_ANISOTROPIC, the upper right corner of the client area is always the point (32767, 32767), regardless of its dimensions. If the client area is not square, logical x and y units will have different physical dimensions.

In the previous section on the MM_ISOTROPIC mapping mode, I discussed how you might draw a round clock in the client area where the x and y axes ranged from -1000 to 1000. You can do something similar with MM_ANISOTROPIC:

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 1000, 1000, NULL) ;
SetViewportExtEx (hdc, cxClient / 2, -cyClient / 2, NULL) ;
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;

The difference with MM_ANISOTROPIC is that in general the clock would be drawn as an ellipse rather than a circle.

Another way to use MM_ANISOTROPIC is to set x and y units to fixed but unequal values. For instance, if you have a program that displays only text, you may want to set coarse coordinates based on the height and width of a single character:

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 1, 1, NULL) ;
SetViewportExtEx (hdc, cxChar, cyChar, NULL) ;

Of course, I've assumed that cxChar and cyChar are the width and height of characters in that font. Now you can specify coordinates in terms of character rows and columns. For instance, the following statement displays text three characters from the left and two character rows from the top of the client area:

TextOut (hdc, 3, 2, TEXT ("Hello"), 5) ;

This might be more appropriate if you're using a fixed-point font, as in the upcoming WHATSIZE program.

When you first set the MM_ANISOTROPIC mapping mode, it always inherits the extents of the previously set mapping mode. This can be very convenient. One way of thinking about MM_ANISTROPIC is that it "unlocks" the extents; that is, it allows you to change the extents of an otherwise fully-constrained mapping mode. For instance, suppose you want to use the MM_LOENGLISH mapping mode because you want logical units to be 0.01 inch. But you don't want the values along the y-axis to increase as you move up the screen—you prefer the MM_TEXT orientation, where y values increase moving down. Here's the code:

SIZE size ;
[other program lines]
SetMapMode (hdc, MM_LOENGLISH) ;
SetMapMode (hdc, MM_ANISOTROPIC) ;
GetViewportExtEx (hdc, &size) ;
SetViewportExtEx (hdc, size.cx, -size.cy, NULL) ;

We first set the mapping mode to MM_LOENGLISH. Then we liberate the extents by setting the mapping mode to MM_ANISOTROPIC. The GetViewportExtEx function obtains the viewport extents in a SIZE structure. Then we call SetViewportExtEx with the extents, except that the y extent is made negative.

The WHATSIZE Program

A little Windows history: The first how-to-program-for-Windows article appeared in the December 1986 issue of Microsoft Systems Journal. The sample program in that article was called WSZ ("what size"), and it displayed the size of a client area in pixels, inches, and millimeters. A simplified version of that program is WHATSIZE, shown in Figure 5-24. The program shows the dimensions of the window's client area in terms of the five metric mapping modes.

Figure 5-24. The WHATSIZE program.

WHATSIZE.C

/*-----------------------------------------
   WHATSIZE.C -- What Size is the Window?
                 (c) Charles Petzold, 1998
  -----------------------------------------*/

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("WhatSize") ;
     HWND         hwnd ;
     MSG          msg ;
     WNDCLASS     wndclass ;
     
     wndclass.style         = CS_HREDRAW | CS_VREDRAW;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = NULL ;
     wndclass.lpszClassName = szAppName ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("What Size is the Window?"),
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;
     
     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;
     
     while (GetMessage (&msg, NULL, 0, 0))
     {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
     }
     return msg.wParam ;
}
void Show (HWND hwnd, HDC hdc, int xText, int yText, int iMapMode,
           TCHAR * szMapMode)
{
     TCHAR szBuffer [60] ;
     RECT  rect ;
     
     SaveDC (hdc) ;
     
     SetMapMode (hdc, iMapMode) ;
     GetClientRect (hwnd, &rect) ;
     DPtoLP (hdc, (PPOINT) &rect, 2) ;
     
     RestoreDC (hdc, -1) ;
     
     TextOut (hdc, xText, yText, szBuffer,
              wsprintf (szBuffer, TEXT ("%-20s %7d %7d %7d %7d"), szMapMode,
              rect.left, rect.right, rect.top, rect.bottom)) ;
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static TCHAR szHeading [] =
          TEXT ("Mapping Mode            Left   Right     Top  Bottom") ;
     static TCHAR szUndLine [] = 
          TEXT ("------------            ----   -----     ---  ------") ;
     static int   cxChar, cyChar ;
     HDC          hdc ;
     PAINTSTRUCT  ps ;
     TEXTMETRIC   tm ;
     
     switch (message)
     {
     case WM_CREATE:
          hdc = GetDC (hwnd) ;
          SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
          
          GetTextMetrics (hdc, &tm) ;
          cxChar = tm.tmAveCharWidth ;
          cyChar = tm.tmHeight + tm.tmExternalLeading ;
          
          ReleaseDC (hwnd, hdc) ;
          return 0 ;
          
     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;
          SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

          SetMapMode (hdc, MM_ANISOTROPIC) ;
          SetWindowExtEx (hdc, 1, 1, NULL) ;
          SetViewportExtEx (hdc, cxChar, cyChar, NULL) ;
          
          TextOut (hdc, 1, 1, szHeading, lstrlen (szHeading)) ;
          TextOut (hdc, 1, 2, szUndLine, lstrlen (szUndLine)) ;
          
          Show (hwnd, hdc, 1, 3, MM_TEXT,      TEXT ("TEXT (pixels)")) ;
          Show (hwnd, hdc, 1, 4, MM_LOMETRIC,  TEXT ("LOMETRIC (.1 mm)")) ;
          Show (hwnd, hdc, 1, 5, MM_HIMETRIC,  TEXT ("HIMETRIC (.01 mm)")) ;
          Show (hwnd, hdc, 1, 6, MM_LOENGLISH, TEXT ("LOENGLISH (.01 in)")) ;
          Show (hwnd, hdc, 1, 7, MM_HIENGLISH, TEXT ("HIENGLISH (.001 in)")) ;
          Show (hwnd, hdc, 1, 8, MM_TWIPS,     TEXT ("TWIPS (1/1440 in)")) ;
          
          EndPaint (hwnd, &ps) ;
          return 0 ;
          
     case WM_DESTROY:
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

For ease in displaying the information using the TextOut function, WHATSIZE uses a fixed-pitch font. Switching to a fixed-pitch font (which was the default prior to Windows 3.0) involves this simple statement:

SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

These are the same two functions used for selecting stock pens and brushes. WHATSIZE also uses the MM_ANISTROPIC mapping mode with logical units set to character dimensions, as shown earlier.

When WHATSIZE needs to obtain the size of the client area for one of the six mapping modes, it saves the current device context, sets a new mapping mode, obtains the client-area coordinates, converts them to logical coordinates, and then restores the original mapping mode before displaying the information. This code is in WHATSIZE's Show function:

SaveDC (hdc) ;
SetMapMode (hdc, iMapMode) ;
GetClientRect (hwnd, &rect) ;
DptoLP (hdc, (PPOINT) &rect, 2) ;
RestoreDC (hdc, -1) ;

Figure 5-25 shows a typical display from WHATSIZE.

Click to view at full size.

Figure 5-25. A typical WHATSIZE display.