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.