
Comparing GDI mapping modeswith GDI+ transforms.Many people ask how the old style GDI mapping modes such as MM_ANISOTROPIC can be used with GDI+ or how the various old style mapping modes equate to graphics operations using GDI+. Really, the answer is that they do not and this can only be a good thing. Given the fact that matrix transformations have been recognized as the only sensible method to manipulate graphics for many decades, GDI mapping modes were a very limited alternative and always a bit of a kludge. Matrix transformations were added to the picture, if you'll pardon the pun, for Windows NT but, in my experience, hardly ever used. Now, at last, the simplicity of matrix transformations is built into GDI+ and it would be wonderful to be able to forget that the old mapping modes ever existed. Legacy code however, has a habit of coming back time after time and people tasked with converting drawing software that was designed to run on GDI to GDI+ often have problems visualizing how the two systems work and how to convert between them. Let's take a moment to review the old mapping mode system in its simplest terms before looking at how to understand these principles in the context of a transformation matrix. GDI mapping modesBasically, the mapping mode system enables you to equate an abstract, logical drawing surface with a concrete and constrained display surface. This is good in principle but GDI had a major drawback inasmuch as the logical drawing area coordinates were based upon signed integers. This meant that creating drawing systems based upon some real-world measurement system such as inches or millimeters required you to use a number of integer values to represent a single unit of measure for example, in the case of MM_LOMETRC mapping there are ten integer values to each linear millimeter and in the case of MM_LOENGLISH there are 100 integer values to each linear inch. The idea behind the old style mapping mode system was to provide this abstract drawing area with a view port, a sort of magnifying glass, which could be used to examine a given area of the drawing surface. Figure one illustrates this concept.
Figure 1. Logical space and the viewport. The viewport covers an area of device space. This is to say that it corresponds to pixels on a screen or printer. Therefore, it has a particular physical size and is constrained by the physical dimensions of the display device. In it's simplest mapping mode, the system will relate one pixel on the device to one unit of measure in the logical space. This is probably the most used mapping and the easiest to understand. The viewport doesn't need to sit in the same place though. Think about a drawing system in which the origin of the graphics needs to be in the centre of the screen such as when drawing an X,Y chart. In this instance, the viewport origin may be offset to cope with the requirements as seen in Figure 2.
Figure 2. Viewport origin offset Consider now the MM_LOENGLISH mapping mode mentioned earlier. This equates 100 logical device units to one inch of linear screen real-estate so, this is a simple ratiometric calculation that takes the declared dots per inch or DPI of the screen into consideration and calculates where a pixel should be on screen from coordinates given in logical units. A popular screen dot-pitch is 96 DPI so the ratio of logical units to device units is 100:96 so, to find a pixel position the system would divide by 100 and multiply by 96 to ascertain the correct pixel position. GDI seems to have two types of mapping mode. The constrained modes which are dedicated to real-world measurements such as inches, millimeters and points and the unconstrained modes such as MM_ANISOTROPIC which lets you specify your own ratiometric mapping. The mapping in these unconstrained modes may be set by using the SetViewportExt and SetWindowExt methods. These enable you to set up a ratio between logical and device coordinates explicitly. In reality, the constrained modes set up the same relationships but use the device resolution for the window extents rather than some arbitrary one so actually, the formula is the same for both types of mapping mode. For the MM_LOENGLISH mode on a 72 DPI screen the system will use 100,100 for the Window Extents and 72,72 for the Viewport Extents. For the same mapping on a printer having 1200X300 DPI non-square pixels the mappings will be 100,100 for window extent and 1200,300 for viewport extents. This gives a ratiometric conversion of 100:72 on screen for both X and Y coordinates and a ratio on the printer of 100:1200 for X and 100:300 for Y coordinates. The values used for these ratio's might also be negative so, to invert the Y axis for example so that more positive Y values go up the screen, mappings of 1:-1 can be used. Setting ViewportExt to 1,1 and WindowExt to 1,-1 will do this nicely. The calculations actually performed to convert each logical X or Y coordinate to a device coordinate are: DeviceCoord=(LogicalCoord-WindowOrg)*(ViewportExt/WindowExt)+ViewportOrg Note that this calculation is only good for placing pixels vertically or horizontally. No complex manipulations such as rotation are provided. Furthermore, the calculations only work for coordinates used for operations such as line-drawing and text placement, not on every pixel. This means for example that inverting the Y axis does not make text come out upside-down. An important point to remember when you read the next section. GDI+ Mapping PipelineGDI+ has three distinct graphical spaces. They are the World, Page and Device spaces. The world space corresponds to the GDI logical space but with one important difference, the coordinate system is based on floating point rather than integer units. This means that real-world measurements can be catered for without the potential for overflowing the logical coordinate space. You can for example specify a measurement of 1.75 inches or 300,000.5 millimeters. The Page space controls the relationship between the abstract world space and the Device space and ensures that pixels placed an inch apart in the world space appear an inch apart on any and all devices. You can specify that the page units are measured in inches, millimeters, pixels and points which are all real-world measurements and an additional couple of pseudo real-world standards called Device and Display which are 1/300th of an inch and 1/75th of an inch respectively. When a pixel is plotted in the world space, it is automatically transformed by the page space to the device space and displayed in the correct position on screen. This type of bucket-brigade calculation system is called a pipeline and may have other steps inserted into the chain to further modify the appearance of the image rendered on the display. The important one from the point of view of this discussion is the current graphics transform which is stored in a Matrix which is a property of the Graphics object used to do drawing in GDI+. The MatrixCoordinates may be considered as 2x1 matrices or vectors with the X and Y values stored in the columns as shown: [X, Y] A 2x1 matrix may be multiplied by a 2x2 matrix to produce another 2x1 matrix that is a linear transform of the original:
The vector-matrix dot-product for the matrix multiplication above is found by the following formula: p1=v1m11 + v2m12 p2=v1m21 + v2m22 A linear transformation matrix such as the one shown below is called an identity matrix and produces no change to a vector which is multiplied by it:
Using the same formula shown above, you can see that the identity matrix produces the same product as the original vector:
p1=10*1 + 5*0 = 10 p2=10*0 + 5*1 = 5 The vector or coordinate may be scaled using a matrix with non-unit values such as:
p1=10*2 + 0*5 = 20 p2=10*0 +5*0.5 = 2.5 The matrix in the example scales the coordinate by 2 in the X direction and 1/2 in the Y direction. Rotation of a point in 2D space may be accomplished by setting up the matrix as shown:
A rotation of 30 degrees about the origin can be made using the following matrix;
The point at 10,10 can be rotated through 30 degrees to produce a point at roughly 13.6, 3.6 p1=10*0.866 + 10*0.5 = 13.66 p2=10*-0.5+10*0.866 = 3.6
Linear calculations may be followed by a non-linear transformation such as a displacement or translation in space along the X or Y axes. This type of calculation is an affine transformation and may be accomplished using a larger matrix such as:
This matrix is larger, contains the linear part outlined in red and the translation part outlined in green. The last column contains dummy values that makes sure that multiplications of this matrix with another work correctly. Using such a matrix, it is possible to create transformations that scale, rotate and displace points on the 2D plane. For example the following matrix rotates a point 45 degrees and displaces it by 10 along the X and 10 along the Y axes:
Going back to the idea of a coordinate as a vector again, we can transform the vector by the matrix and then add the translation to perform the affine matrix transformation using the following formula: p1=v1m11 + v2m12 + m31 p2=v1m21 + v2m22 + m32
Matrices may be added together or multiplied to produce a new matrix. When two matrices of the same size are multiplied they produce a matrix that will perform a transformation that is the same as applying the two transforms sequentially. A number of transformations may be amalgamated in this way to create a complex transform that might for example, rotate an object around the axis, shift it in space and scale it to a different size. The steps to multiply matrices such as the one represented by the matrix class are identical to those used to multiply a vector by a matrix there are just more steps involved. Consider these matrices:
The matrix P can be obtained by multiplying the matrix A by B in the following manner. p[1,1] = a[1,1]b[1,1] +
a[1,2]b[2,1] + a[1,3]b[3,1] Matrices must be multiplied in the correct order, the order in which they are to be applied, because unlike the multiplication of two simple numbers a*b does not equal b*a. The order of matrix multiplication under GDI+ is controlled by the MatrixOrder enumeration. MatrixOrder.Append is the equivalent of a*b and MatrixOrder.Prepend is causes the matrices to be multiplied in b*a order. When a transform described by a matrix is applied to a Graphics object then all pixels drawn are transformed by the matrix and will appear in a different place. This is how GDI+ enables you to draw text at any angle or display images and shapes at any zoom-level. The goal is near.Now that the theory behind the two systems is described it's possible to look in greater detail the comparison between GDI mapping modes and GDI+ transformations. Real-world measurementsUnder GDI, some behind-the-scenes code looks at the declared DPI of the device and sets up a ratiometric conversion between the logical mapping and device mapping. Once it's set-up you're stuck with it and you cannot show a drawing in inches but zoom it to half scale. Using GDI+ you set the PageUnits to suit your real-world measurement. This also maps from logical space to device space behind the scenes but you can also add a transformation matrix such as; 0.5, 0 , 0 0, 0.5, 0 0, 0, 1 This will scale all the coordinates to half size and zoom out by a factor of 2. Scrolling or Moving the origin.In any GDI mapping mode the window origin and viewport origin may be moved. To set the origin of the graphics to the center of the screen, you can set the viewport origin to half the window width and height. To set the origin to the lower left of the screen, perhaps to draw a graph with the Y axis pointing up you can set the viewport origin Y value to the window height. For GDI+ you simply use a transform that shifts the pixels output to the center of the screen such as; Matrix mx=new Matrix(1,0,0,1,ClientSize.Width/2,ClientSize.Height/2); mygraphics.Transform=mx; Scrolling the display area, for example when using a control based on ScrollableControl, is accomplished by translating the display; Matrix mx=new Matrix(1,0,0,1,AutoScrollPosition.X, AutoScrollPosition.Y); myGraphics.Transform=mx; Zooming.Setting up GDI to the MM_ANISOTROPIC mode enables you to modify the ratio between linear measurements. Because the system uses only integer mathematics you can only set a ratio by expressing it as two whole numbers. To zoom in by 2 you set the ratio of the window extent to the viewport extent to 1:2 or 50:100 or 7:14, you get the picture. To do this in GDI+ you create a transform that scales the world space by 2 using a matrix such as; 2, 0, 0 0, 2, 0 0, 0, 1
Reversing the Y axisUnder GDI you would use the window and viewport Y extents so that the ratio between them was 1:-1. You may also need to scale by 2 or some other values so perhaps the ratio would be 13:-27 or something equally tortuous. Under GDI+ you scale the relavent axis by -1 using a matrix such as; 1, 0, 0 0, -1, 0 0, 0, 1 This is a tricky one though because for GDI the only thing that changes is the way coordinates are mapped to the Y axis so text still comes out the right way up. Under GDI+ the reflecting transform will also cause text to come out upside-down as if reflected in a mirror. To overcome this problem you can use the techniques shown in this article. |