CS-360
Fall, 2000
Class 10
R. Eckert

THE DEVICE CONTEXT--PRIVATE DCs, MAPPING MODES

PRIVATE DCs--

Recall, a Device Context (DC) provides a connection between Windows
graphics functions and hardware devices. Programs use the same graphical
functions to output to any device context; Windows translates function
calls into commands that a printer, plotter, video board, etc., understand.

To date, we've created a "common" DC each time the program needed to output
to its window's client area. e.g.,

HDC hDC;
HPEN hPen, hOldPen;
hDc = GetDC (hWnd);                   /* get a DC */
hPen = CreatePen (PS_SOLID, 3, RGB(255,0,0)); /* red pen */
hOldPen = SelectObject (hDC, hPen);   /* select into DC  */
Ellipse (hDC, 10, 10, 100, 80);       /* draw an ellipse */
hPen = SelectObject (hDC, hOldPen);   /* Select old pen...*/
                                      /* displacing new pen */
DeleteObject (hPen);                  /* delete new pen  */
ReleaseDC (hWnd, hDC);                /* free the DC */


This is OK for simple graphical output, especially when default attribute
settings are used. but it's cumbersome if you're changing many DC settings.
Each time GetDC() is called, default attributes are loaded in. This is
slow since Windows must set up the DC each time graphics output is needed.

TO SPEED THINGS UP: CREATE A PRIVATE DC FOR THE WINDOW--

  -"Belongs" to the window (a specific window).

  -Stores all DC settings while the window exists.

  -Exists for the life of the window.

  -Attribute settings (pens, brushes, etc.) remain in effect until the
   application changes them.

  -We don't have to recreate objects each time they are needed.

  -Use GetDC() to retrieve the DC handle, as usual...

     BUT we don't need to use ReleaseDC().

  -A private DC is destroyed automatically when the owner window is destroyed.

  -The tradeoff is an increase in memory usage since a DC uses 800 bytes +
   the memory consumed by its objects (pens, etc.).

  -Use private DCs with discretion.

  -They are most appropriate for applications that require many changes be
   made to default settings before (or as) they are used.

  -They are also useful if an application has many child windows derived
   from the same class; each child can have have the same pen, brush, etc.,
   which gives a consistent appearance. We don't have to recreate pens,
   brushes, etc., each time the child window paints.

SETTING UP A PRIVATE DC--

  -Add CS_OWNDC to the window class style prior to registering the window
   class. This sets aside a memory area to hold the DC for each window
   created from this class. (Alternative: Use CS_CLASSDC if many windows
   are to share the same DC; specifically allocated for the sole use of
   windows based on that class).

  -Then use CS_PARENTDC so all child windows use parent's DC. (Assumes
   the parent was created with CS_OWNDC or CS_CLASSDC).


USING A PRIVATE DC--

  -Create the window as usual.

  -Get a DC as usual [GetDC() or BeginPaint()].

  -Save as many initial settings into the DC as desired. This is usually done
   when the window is created (in response to WM_CREATE).

  -Any object handles created should be placed in static variables. Values
   can change, and we want to "remember" the last value.

  -A private DC belongs to the window.

  -A private DC is destroyed automatically when the owner window is destroyed.

  -ReleaseDC() does nothing to a private DC, so no ReleaseDC() is needed
   when the DC was obtained with GetDC().

  -We still need EndPaint() (when BeginPaint() was used)to stop the painting
   process.

  -Objects (pens, etc.) still have to be deleted.


MAPPING MODES--

For DC coordinates, up to now we've used the default system "Device
Units." For a video screen these are pixels measured from the upper
lefthand corner of the screen. For a printer these are printer dots
measured from upper lefthand corner of the printed page.

The problem here is that not all pixels and dots are the same size. For
example, a nice picture on a screen output to a laser printer could yield a
postage stamp-sized image! (Laser printer dots are much smaller and closer
together than screen pixels).

Mapping modes allow us to create a logical system of units for text and
graphics. Windows then maps the output to any real device. For example, if
we plot at (100,100) "logical millimeters" Windows will try to figure out
where that lies on your physical screen. (The result will not be exact, but
it will be close--and for printers, it will be very close.)

MAPPING MODE   LOGICAL UNIT           X-AXIS    Y_AXIS
------------------------------------------------------
MM_TEXT        Pixel (Default)         Right     Down
MM_HIENGLISH   .001 inch               Right     Up
MM_LOENGLISH   .01 inch                Right     Up
MM_HIMETRIC    .01 mm                  Right     Up
MM_LOMETRIC    .1 mm                   Right     Up
MM_TWIPS       1/20 point=1/1440 inch  Right     Up
MM_ISOTROPIC   Arbitrary (x==y)          Selectable
MM_ANISOTROPIC Arbitrary (x!=y)          Selectable

Change mapping mode with: SetMapMode(hDC, MODE);

Windows maps logical coordinates to device coordinates (units=pixels,
+x: toward right, +y toward bottom).

Device coordinate systems:

 (1) Screen Coordinates (used by CreateWindow(), MoveWindow(), and
     other functions that don't have anything to do with writing on the
     client area of a window). The origin is the upper lefthand corner
     of the screen.

 (2) Client-area Coordinates [for DCs obtained with GetDC() or
     BeginPaint().] The origin is the upper lefthand corner of the
     window's client area. Logical coordinates are mapped to these.

ClientToScreen() and ScreenToClient() are used to convert between device
and client coordinate systems.

Text & Mapping Modes--Changing the mapping mode changes text location, but
not size. (Create a different font and select it into the DC if character
sizes are to be changed.)

Mapping Modes and messages--Windows continues to use device coordinates
for all messages not associated with GDI functions (e.g., for WM_MOVE,
WM_SIZE, WM_MOUSEMOVE, etc.)

Since mapping mode is an attribute of the DC, it only comes into play
when GDI functions that require a handle to the DC as a parameter are used.
GetSystemMetrics() and GetDeviceCaps() both use device units; but the
TEXTMETRIC structure obtained from GetTextMetrics() uses logical
units. GDI drawing primitives use logical coordinates.

VIEWPORT--A rectangular area of the screen specified in terms of
device coordinates (most often the client area).

WINDOW--A rectangular area specified in logical coordinates.

MAPPING--Windows converts logical ("window") coordinates to device
("viewport") coordinates using the following transformation. (In computer
graphics, this is known as the "window to viewport transformation.):

xV = (xVExt/xWExt) * (xW - xWOrg) + xVOrg

yV = (yVExt/yWext) * (yW - yWOrg) + yVOrg

where point (xW,yW) is translated and scaled to (xV,yV). The x scaling
factor is (xVExt/xWext), and the y scaling factor is (yVExt/yWExt). These
are determined by the window and vieoport "extents that are maintained by
Windows. Their values can be obtained with GetWindowExtEx(hDC,&size) or
GetViewportExtEx(hDC,&size). Their values can be changed with 
SetWindowExtEx(hDC,xWExt,yWExt,&size) or SetViewportExtEx(hDC,xVExt,yVExt,&size).
(See online help.)

(xWOrg,yWOrg) and (xVOrg,yVOrg) are the origins of the window and
viewport, respectively, and both are (0,0) in the default device context.
(xWOrg,yWOrg) maps to (xVOrg,yVOrg).

Moving the Origin--Since all modes have the initial origin at (0,0), when
we change modes, the picture may appear to be gone because of the fact that
the logical and physical y-axes point in the opposite direction. In other
words, if we are in a mapping mode that has its y-axis pointing upward, a
call to any function that tries to display  something at a positive value
for y will "appear" above the upper border of the window's client area
(and, of course, be clipped or off the screen). We could use negative y
values; but it is better to move the origin. This can be done in one of two
different ways:

  SetWindowOrgEx(hDC,x,y,NULL); /* mapping mode logical units  used */
  (For x,y positive, think of this as moving the upper left-hand
  corner of the screen/paper up and right by (x,y) logical units.)

  SetVieportOrgEx(hDC,x,y,NULL); /* device units (pixels) used */
  (For x,y positive, think of this as moving the lower left-hand
  corner of the logical window down and right by (x,y) device units.)

Both move the coordinate system origin to (x,y), but the units of (x,y) are
different.

The following is an example of the sequence of calls needed to set the 
Mapping Mode to MM_LOENGLISH, move the origin to bottom left corner of the 
client area, and draw using those units:

SIZE size;
hDC = GetDC (hWnd);
GetWindowExtEx (hDC, &size);  // Returns x,y in a SIZE structure
   // Since we're in MM_TEXT mode, the "logical" units are pixels
SetViewportOrgEx (hDC, 0, size.cy));
   // Move the window down by size.cy pixels
SetMapMode (hDC, MM_LOENGLISH);
   // Now we can draw using 0.01 inch logical coordinates
   // The Origin is at the lower lefthand corner of the client area
ReleaseDC (hDc);

The MAPMODE1 Example Application--

This program demonstrates three mappings modes and moving of the origin.
It displays a rectangle and a line of text, using the same logical
coordinates, but 3 different mapping modes. The logical origin is also drawn
as a cross in each mapping mode. Note that the size and position of the
rectangle vary. The position (but not the size of the text string varies
also.

The user selects the mapping mode from the main menu. The WM_COMMAND
message processing loads an appropriate window caption string from the
resources, sets the nMapMode variable to the appropriate value, and then
forces a repaint of the client area (a WM_PAINT message) by calling
InvalidateRect().

WM_PAINT processing sets the mapping mode according to the value of the
nMapMode variable by calling SetMapMode(), and then moves the logical
window origin down and to the right by (100,100) pixels with a call to
SetViewportOrgEx(). MoveToEx() and LineTo() are used to draw a cross at the
logical origin, Rectangle() is used to draw a rectangle whose diagonally
opposite corners are at logical coordinates (50,50), (200,100), and the
string "Text at 50,50" is loaded from the string table and displayed at
logical coordinates (50,50). Since the text reference point is at its
default point (the upper lefthand corner of an imaginary rectangle
embedding the text), the upper lefthand corner of the "T" in the string
will be at logical coordinates (50,50).

Note that, depending on the mapping mode chosen, the rectangle and text are
at different locations in the window. But if you measure with a ruler, you
will find that in the MM_LOENGLISH and MM_LOMETRIC modes, the position of
the upper lefthand corner of the text and the lower lefthand corner of the
rectangle are almost exactly 50 logical units (0.5 inch in the first case
and 5 mm in the second) up and to the right from the cross that marks the
origin. If you had a measuring instrument that measured in pixels, you
would find that, for the MM_TEXT mapping mode, that same point would be 50
pixels down and to the right from the origin--to be expected since the y-
axis in the MM_TEXT mode points downward. Note that the position of the
rectangle relative to the text is different in this mode. That is because
of the fact that in the call to Rectangle(), the y value of the first point
is less than that of the second. So that in MM_TEXT mapping mode (y-axis
pointing down) the first point is the upper lefthand corner of the
rectangle, while for the other two mapping modes (y-axis pointing up), it
is the lower lefthand corner of the rectangle.

MM_ISOTROPIC, MM_ANISOTROPIC MAPPING MODES

In these modes, the coordinate axes can use any unit size, the x axis can
be directed to either the left or right, and the y-axis either up or down.

MM_ISOTROPIC-- The x and y units must be the same size.

MM_ANISOTROPIC-- Different sizes are used for the x and y units.

We effectively determine the units to be used by setting X and Y scaling
factors with calls to:

SetWindowExtEx (hDC, xWExt, yWExt, NULL);
SetViewportExtEx (hDC, xVExt, yVExt, NULL);

The X scaling factor appears in the X transformation equation that Windows
uses in going from Logical Coordinates to Device Coordinates; its value is:
xVExt/xWExt.

The Y scaling factor does the same thing for the y equation; its value is:
yVExt/yWExt.

To reverse either axis, set one of the Ext values to a negative number.

EXAMPLE 1--Create a coordinate system where each logical unit is two
pixels (twice the default device unit coordinates)--

hDC = GetDC (hWnd);
SetMapMode (hDC, MM_ISOTROPIC);
SetWindowExtEx (hDC, 1, 1, NULL);
SetViewportExtEx (hDC, 2, 2, NULL);  // x-factor = y-factor = 2/1

EXAMPLE 2--Create a logical coordinate system with the y-axis up and in
which each y-unit = 1/4 pixel; the x-axis is to be unchanged--

hDC = GetDC (hWnd);
SetMapMode (hDC, MM_ANISOTROPIC);
SetWindowExtEx (hDC, 1, -4, NULL);
SetViewportExtEx (hDC, 1, 1, NULL);  // x-factor = 1/1, y-factor = 1/(-4)

A common use of MM_ANISOTROPIC mode is to create a coordinate system in
which the window's client area has same logical width and height,
regardless of how it is resized.

EXAMPLE 3--Create a coordinate system where client area is always 1000
units high and wide--

SIZE size;
hDC = GetDC (hWnd);
GetWindowExtEx (hDC, &size); /* get client area size */
   /* returns size in default MM_TEXT Mapping Mode units--here pixels */
SetMapMode (hDC, MM_ANISOTROPIC);
SetWindowExtEx (hDC, 1000, 1000);         // x-factor = size.cx/1000
SetViewportExtEx (hDC, size.cx, size.cy);  // y-factor = size.cy/1000

Recall that in the transformation equations, the x scaling factor
multiplies the logical x coordinate and the y scaling factor the logical y
coordinate. So that now, when x=1000, the x equation will give a value of
size.cx, which corresponds to the right edge of the client area. When
y=1000, the result will be size.cy, which corresponds to its lower edge.
This would be independent of how the window was resized. Note that the
y-axis still points downward in this example. 

The MAPMODE2 Example Application--

This program draws a rectangle and an ellipse that always fit exactly
inside the client area. When the user changes the size of the window, a
WM_SIZE message is generated. The low/high word of the lParam accompanying
that message contains the x/y extent of the window's client area in device
units. These values are extracted with the LOWORD() and HIWORD() macros and
saved in the variables nX and nY. The mapping mode is then set to
MM_ANISOTROPIC by calling SetMapMode(). A call to SetViewportExtEx() using
xWExt=nX and yWext=nY, followed by a call to SetWindowExtEx() using xVExt=100
and yVExt=100 effectively set the scaling factors to 100/nX and 100/nY.
Thus the logical coordinate system is always scaled to 100 X 100 logical
units. The ellipse is drawn with: Ellipse(ps.hdc, 0, 0, 100, 100), which
means that, after the transformation, the ellipse will lie inside the
rectangle (0,0), (nX,nY); i.e., it just fits inside the client area. The
rectangle is drawn with: Rectangle(ps.hdc, 20, 20, 80, 80), which, after
the transformation, becomes (0.2*nX,0.2*nY), (0.8*nX,0.8*nY). This means that
its borders are 1/5 of the way into the window, regardless of how the
window is resized. Note that in this example, the y-axis still points
downward, so we don't have to move the origin.