The "Roll Your Own" Mapping Modes

The two remaining mapping modes are called 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 physical size of the logical unit based on the size of the client area so that the images you draw are always contained within the client area, shrinking and expanding appropriately. For instance, the CLOCK and REVERSI programs included with Windows are examples of isotropic images. The clock is always round, and the Reversi playing board is always square. As you size the window, the image is 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 the 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 that 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 arbitrary axes while preserving equal logical units on the two axes. Rectangles with equal logical widths and heights are displayed as squares. 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 SetWindowExt and SetViewportExt. Windows will then adjust the extents so that the logical units on both axes represent equal physical distances.

Generally, you'll use parameters to SetWindowExt with the desired logical size of the logical window, and parameters to SetViewportExt 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 SetWindowExt before you call SetViewportExt to make the most efficient use of space in the client area.

For instance, 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 width ranges from 0 to 32,767 and the height 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) ;

SetWindowExt (hdc, 32767, 32767) ;

SetViewportExt (hdc, cxClient, -cyClient) ;

SetViewportOrg (hdc, 0, cyClient) ;

If you then obtain the window and viewport extents using GetWindowExt and GetViewportExt, you'll find that they are not the values you specified. Windows adjusts 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:

You can't display anything starting on the right side of the client area beyond the range of the x-axis, because that requires a logical x-coordinate greater than 32,767.

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

Now you can't display anything at the top of the client area, because you need a logical y-coordinate greater than 32,767.

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

SetMapMode (hdc, MM_ISOTROPIC) ;

SetWindowExt (hdc, 32767, 32767) ;

SetViewportExt (hdc, cxClient, -cyClient) ;

SetWindowOrg (hdc, 0, 32767) ;

In the SetWindowOrg call we're saying that we want the logical point (0, 32,767) 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-like image, you might want to use a four-quadrant Cartesian coordinate system with arbitrarily scaled axes in four directions where 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) ;

SetWindowExt (hdc, 1000, 1000) ;

SetViewportExt (hdc, cxClient / 2, -cyClient / 2) ;

SetViewportOrg (hdc, cxClient / 2, cyClient / 2) ;

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:

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 may or may 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. This mapping mode would let you draw a ruler starting at the top and left side of the client area with divisions of sixteenths of an inch:

SetMapMode (hdc, MM_ISOTROPIC) ;

SetWindowExt (hdc,

(short) (160L * GetDeviceCaps (hdc, HORZSIZE) / 254),

(short) (160L * GetDeviceCaps (hdc, VERTSIZE) / 254)) ;

SetViewportExt (hdc, GetDeviceCaps (hdc, HORZRES),

GetDeviceCaps (hdc, VERTRES)) ;

In this code, the viewport extents are set to the pixel dimensions of the entire screen. The window extents must be set to the dimensions of the entire screen in units of sixteenths of an inch. The HORZSIZE and VERTSIZE indexes to GetDeviceCaps 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. The calculation is done in long integers to prevent overflow.

For most output devices, this code makes the logical unit much larger than the physical unit. 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, however, 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 both the x- and y-axes ranging from 0 to 32,767:

SetMapMode (hdc, MM_ANISOTROPIC) ;

SetWindowExt (hdc, 32767, 32767) ;

SetViewportExt (hdc, cxClient, -cyClient) ;

SetViewportOrg (hdc, 0, cyClient) ;

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, then logical x and y units will be different physical dimensions.

In the previous section on the MM_ISOTROPIC mapping mode, I discussed drawing a CLOCK-like image in the client area where both the x- and y-axes ranged from -1000 to 1000. You can do something similar with MM_ANISOTROPIC:

SetMapMode (hdc, MM_ANISOTROPIC) ;

SetWindowExt (hdc, 1000, 1000) ;

SetViewportExt (hdc, cxClient / 2, -cyClient / 2) ;

SetViewportOrg (hdc, cxClient / 2, cyClient / 2) ;

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) ;

SetWindowExt (hdc, 1, 1) ;

SetViewportExt (hdc, cxChar, cyChar) ;

(This assumes that cxChar and cyChar are the width and height of a character in pixels, for a fixed-pitch font.) Now you can specify character row and column coordinates in the TextOut call rather than pixel coordinates. For instance, the following statement displays the text Hello three character spaces from the left and two character rows from the top:

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

This is almost like working in text mode in the non-Windows MS-DOS environment!

When you first set the MM_ANISOTROPIC mapping mode, it always inherits the extents of the previously set mapping mode, which can be very convenient. One way of thinking about MM_ANISOTROPIC 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:

DWORD dwExtent ;

[other program lines]

SetMapMode (hdc, MM_LOENGLISH) ;

SetMapMode (hdc, MM_ANISOTROPIC) ;

dwExtent = GetViewportExt (hdc) ;

SetViewportExt (hdc, LOWORD (dwExtent), -HIWORD (dwExtent)) ;

We first set the mapping mode to MM_LOENGLISH. Then we liberate the extents by setting the mapping mode to MM_ANISOTROPIC. The GetViewportExt obtains the viewport extents encoded in a DWORD variable. Then we call SetViewportExt with the extents extracted from the DWORD using the LOWORD and HIWORD macros, except that we make the y extent negative.