Brad Pirtle
QuickLogic
Article Rev D: 11/8/94
Automatic Scaling in Windows
Many Windows applications need the ability to arbitrarily scale, or zoom, the
contents of a window. An example of this is print preview, where you can zoom in
and out on a portion of the printed page. Programming information on this subject
is minimal and very obfuscated. Not any more! In this article I explain how to easily
use the built it scaling capabilities of Windows, and apply it to a C++ MFC extension
class that I call CZoomView.
Learning To Scale
Proper scaling in Microsoft Windows requires you to set the map mode,
window extent, window origin, viewport extent, and viewport origin for a window.
Are you confused yet? Try reading the manual entries for any corresponding API -
SetWindowExt: Sets the x- and y-extents of the window.... So it wasnt just a
clever name.
A key phrase, however, comes next: defines how GDI maps points in the
logical coordinate system to points in the device coordinate system. Although this
information tells you nothing on how to use it, it is the clue to how it works -- mapping
from one coordinate system to another. To make scaling easy, we want Windows
to do as much of this conversion work as possible. Luckily for us, it can be done
easily if you arm yourself with just a little bit of API knowledge..
For a drawing page to be automatically scaled in a window, two concepts
must be understood. The first is how to draw the page, and the second is how to
view the page. The Windows operating system uses both pieces of information
together so that a user can view your drawing in a window at any arbitrary zooming
scale. As a programmer, the easiest way to understand and implement scaling is to
divide and conquer," handling each concept separately.
Step One: Drawing the Page
The first thing that you need to do is choose the logical units for your
drawing. Think of this as the number of virtual horizontal and vertical units on a page
you will draw on. This is what Windows calls the Window Extent," and is set by the
SetWindowExt API. A logical page size of x by y units would simply have a window
extent of {x, y}. Keep in mind that units is an arbitrary amount of measure that you
pick, so do not be stuck thinking that it has to be pixels, inches, or any other
measure.
Once you choose a logical coordinate system, all GDI drawing will be done
using it. For example, lets say that you want to draw a line from the top left corner to
the bottom right corner of a page that has a window extent of 100 by 100 logical
units. Using the MoveTo and LineTo API functions, you would pass {0, 0} as the
starting logical coordinate to MoveTo, and {100, 100} as the ending logical
coordinate to LineTo. Any other GDI function (Rectangle, Arc, CreateFont, TextOut,
etc.) will also use logical units. // ADD DRAWING ONE HERE
Using logical units allows your program display device independence.
Drawing objects coordinates can all be stored in logical units, and you will use those
coordinates when painting your windows without needing to do any conversions for
scaling, different size screens, or different size windows. Doing any device
dependent conversions, and determining which part of the drawing to actually scale
and display to the user in a window, will be handled automatically by Windows after
you follow the next step.
Step 2: Viewing the Page
Now that you understand logical drawing on your page, the second part of
adding automatic scaling to your program is actually viewing it in a window.
Viewing functionality uses device coordinates, in pixel units.
With this in mind, you need to tell Windows the viewing size, in pixels, of your
entire drawing page. This is what Windows calls the Viewport Extent," and is set by
the SetViewportExt API. This does not confine a real window to that size, but rather
describes the mapping of your logical units into pixels. By setting a window extent
to {100, 100} and a viewport extent of {200, 200}, you are telling Windows that 100
by 100 logical units is exactly equal to 200 by 200 pixels. Note: to help in translating
back and forth from logical to device units, the Windows API supplies the LPtoDP
function to convert logical to device units, and the DPtoLP function to convert back.
When you set window and viewport extents you are required to change the
mapping mode, set by the SetMapMode API. When using the SetWindowExtent
and SetViewportExtent APIs, only two mapping modes are allowed --
MM_ISOTROPIC and MM_ANISOTROPIC. The MM_ISOTROPIC mode is used
when you want identical logical units for both the x and y axes, where
MM_ANISOTROPIC is used for arbitrary logical x and y axis units. For our generic
scaling requirements, we will use the MM_ANISOTROPIC map mode.
The final puzzle piece that Windows needs to do automatic scaling is the
size of the client area of the physical window that will display your drawing (also in
pixels). Because this information is inherent to the window, no API call is
necessary. The pixel size of the window client area is how many pixels of the
viewport will be shown. For example, setting the viewport extent to be the same
number of pixels as the client area of the window would scale the entire logical
drawing to fit the window. Setting the viewport extent to twice the size of the
window's client area would make the user see one forth (half the width and half the
height) of the page, and the drawing will appear larger (or zoomed in). // ADD
DRAWING TWO HERE
The viewport extent is where you control the scaling factor for a window. To
zoom in, increase its size. To zoom out, decrease its size. It really is that easy!
Keep in mind that in order to maintain the original width to height ratio of your logical
drawing, always set the viewport extent to the same ratio as the window extent, and
scale it the same in both the x and y direction.
A Quick Scrolling Primer
Once a window has the ability to scale, zooming in shows only a portion of
the entire drawing. By adding scrolling to the window, the entire drawing can be
viewed without requiring zooming back out. If you are using the MFC C++ class
library, a scrolling class called CScrollView is supplied, which encapsulates all the
necessary Windows scrolling code. For SDK users and those of you that dont
want to read the 700+ lines of code in CScrollView, I will briefly explain the scrolling
concepts needed when using automatic scaling.
To set up scrolling, you need to manipulate the viewport origin using the
SetViewportOrg Windows API. This is the viewports origin in relation to your
windows origin. Because we are scaling by manipulating the viewport only, the
window origin, set with the SetWindowOrg API, should remain {0, 0}.
A good way to visualize origin manipulation for scrolling is to think of your
drawing as a transparency, and the window to display it in as an overhead projector.
The upper left corner of the transparency is the viewport origin, and the upper left
corner of the projector is your windows origin (always {0, 0}). If the transparency is
too big to see all at once (zoomed in), you need to slide it around with your hand
(scroll) to see all of it. To see more to the right, you slide the paper to the left. To
see more down, you slide the paper up.
Applying this back to Windows, to scroll n pixels in a given direction, change
the viewport origin n pixels in the opposite direction. For example, if you started
with both origins at {0, 0} and wanted to scroll 10 pixels to the right and 10 pixels
down, simply set the viewport origin to {-10, -10}.
Putting the Concept to Work in MFC
To demonstrate the above scaling concepts, I have encapsulated it all in an
MFC extension class that I call CZ