Transformations

I l @ ve RuBoard

You might have noticed a couple of commands in the previous example that moved the regions into position by applying a translation to them. There are two basic ways of applying transforms to graphical objects in the Framework. You can use the methods provided, such as Translate or Rotate , or you can specify an explicit transformation matrix to be used.

By far, the easiest way for dealing with these kinds of operations is to use the methods that wrap the underlying matrix manipulations for you.

These operations allow you to do the following:

  • Translate ” Move an object in the x or y plane by an offset.

  • Rotate ” Spin an object about the origin.

  • RotateAt ” Spin an object about a point other than the origin.

  • Scale ” Magnify or reduce an object in the x and y planes.

  • Shear ” Distort an object by changing its bounding rectangle into a bounding parallelogram.

A matrix performs these operations by performing a matrix operation, such as addition, subtraction, or multiplication to the current graphics transform. We mentioned right at the beginning of this chapter that graphical commands need to go through several translations between being drawn into the graphics system and finally being seen onscreen. This is referred to as the graphics pipeline. Think of commands going in one end, being changed according to the plumbing of the pipes, and emerging at the other end in a somewhat different state.

The things a matrix transforms are the individual positional coordinates of a graphical object. Every pixel drawn onscreen will have been transformed in the pipeline several times before it finally appears on the screen or printer.

The matrices used in the framework are a two dimensional 3x3 matrix. The matrix is constructed as shown in Figure 3.5.10.

Figure 3.5.10. The 3x3 Identity Matrix.

graphics/0305fig10.gif

The matrix shown in Figure 3.5.10 is called the Identity Matrix. An identity matrix can be applied as a transformation to an object and have no effect on it. The Identity Matrix is normally the starting point for all transformations. The matrix used by GDI+ has some fixed values in the rightmost column. The part that is always 0,0,1 in the 3x3 matrix is used to allow a compound operation (for example, a linear calculation, such as rotation or scaling) to be followed by a translation in the same multiplication. This is known as an affined matrix calculation. To do an affined calculation on a matrix of n dimensions, the multiplication matrix used must be n+1 by n+1. Therefore, a 2D calculation requires a 3x3 matrix. For this reason, the third column of the matrix is always set to 0,0,1, and you cannot change it.

There are lots of books on graphics but, for the sake of completeness, the operations performed by the matrix on coordinates go as follows . The following example does a simple translation by an X and Y amount.

A coordinate is turned into a vector by adding a third, z column, to it. For example,

[10,5] becomes [10,5,1]

The dX and dY , or the translation part, of the matrix shown is 10,30.

This vector is multiplied by the matrix by working down each column in the following manner.

[10 , 5 , 1 ]

multiply

1 0 0

0 1 0

dX dY 1

equals

1*x 0*x 0*x

+ + +

0*y 1*y 0*y

+ + +

1*dX 1*dY 1*1

equals

dx+x dy+y 1

equals

20 35 1

Take the dummy component out, and you're left with [20,35], which is [10,5] translated by [10,30].

A rotation about the origin is also performed in a similar manner. We'll use 90 degrees, because the sine and cosine of 90 are easy to compute. The matrix for rotation is initialized as follows:

cos Q sin Q

-sin Q cos Q

0 0 1

So, cos(90)=0 and sin(90)=1 so, remembering to work down the columns , the matrix calculation looks like the following:

[ 10 , 5 , 1 ]

multiply

0 1 0

-1 0 0

0 0 1

equals

0*x 1*x 0*x

+ + +

-1*y 0*y 0*y

+ + +

1*0 1*0 1*1

equals

[ -5 , 10 , 1 ]

Chop off the extraneous 1, and you get [ “5,10]

Taking two matrices and adding them or multiplying them together produces a resultant matrix. Successive additions or multiplications create a matrix that is the sum of all the operations performed so far. This means that you can do several operations on a matrix and they all accumulate, and then the transform in the matrix is applied to each and every pixel that the drawing command produces to obtain their resulting positions in the final output.

The order in which operations take place is very important too. For example, Rotate Scale Translate does not mean the same thing as Scale Translate Rotate . This implies that you also need to think about how the API commands apply the matrices you hand them. Do you multiply the current matrix by the one you just supplied or the one you have by the current matrix? Luckily, the calls to Rotate , Scale , and so on, have a flag that you can use to control the way matrices are worked on. MatrixOrder.Prepend , the default, applies the matrix you pass first, and then the current matrix. MatrixOrder.Append applies the requested operation after the current matrix is applied.

The following sequence illustrates the contents of a matrix as it evolves through many operations.

 Matrix m=new Matrix() // Create an identity matrix   1  0  0   0  1  0   0  0  1 m.Rotate(30,MatrixOrder.Append); //rotate 30 degrees 0.8660254 0.5  0  -0.5 0.8660254 0   0  0  1 m.Scale((float)1,(float)3,MatrixOrder.Append); //magnify by 3 in the Y plane 0.8660254 1.5  0  -0.5 2.598076 0   0  0  1 m.Translate((float)100,(float)130,MatrixOrder.Append); //move by 100 X and 130 Y 0.8660254 1.5  0  -0.5 2.598076 0  100 130  1 

As this sequence progresses, you can see how the matrix accumulates the operations with each successive call.

It is important to note that keeping track of the graphics matrix is very important, especially if you want to place many different objects onscreen, each with its own transformation. It is sometimes useful to put the current matrix into a known state, perhaps with the origin in a different place or zoomed in or out by scaling, and then perform other operations. Each operation should behave itself and leave the current matrix as it was found. Saving the state of the Graphics object in a GraphicsState can do this. A GraphicsState is filled with information by the Graphics.Save() method and restored with the Graphics.Restore(state) method. For example,

 GraphicsState gs=theGraphics.Save(); // perform operations here... theGraphics.Restore(gs); //Graphics are back to their original state. 

Listing 3.5.8 demonstrates the simple transformation sequence discussed previously, as well as the use of GraphicsState and other matrix manipulations in the context of some simple graphic shapes .

Listing 3.5.8 MatrixElements.cs: A Scene from the Matrix
 1: using System;  2: using System.Drawing;  3: using System.Drawing.Drawing2D;  4: using System.Drawing.Text;  5: using System.Collections;  6: using System.ComponentModel;  7: using System.Windows.Forms;  8: using System.Data;  9: using System.Text; 10: 11: namespace matrixelements 12: { 13:  public class MatrixElements : System.Windows.Forms.Form 14:  { 15:   void DumpMatrix(Graphics g, Matrix m, Point p) 16:   { 17:    StringBuilder sb=new StringBuilder(); 18:    sb.AppendFormat("{ 0} ,\ t{ 1} ,\ t0\ n{ 2} ,\ t{ 3} ,\ t0\ n{ 4} ,\ t{ 5} ,\ t1", 19:        m.Elements[0],m.Elements[1],m.Elements[2], 20:        m.Elements[3],m.Elements[4],m.Elements[5]); 21:    GraphicsState s=g.Save(); 22:    g.ResetTransform(); 23:    g.DrawString(sb.ToString(),new Font("Courier New",(float)16), 24:        new SolidBrush(Color.Black),p,StringFormat.GenericDefault); 25:    g.Restore(s); 26:   } 27: 28:   void OnSize(object sender,EventArgs e) 29:   { 30:    Invalidate(); 31:   } 32: 33:   void OnPaint(object sender, PaintEventArgs e) 34:   { 35:    GraphicsState gs; 36:    Matrix m=new Matrix(); 37: 38:    //position and draw the axes by translating the whole window 39:    // so that the origin is in the center of the screen 40:    e.Graphics.TranslateTransform((float)this.ClientRectangle.Width/2, 41:                                  (float)this.ClientRectangle.Height/2); 42:    e.Graphics.DrawLine(new Pen(Color.Black,(float)1),0,-1000,0,1000); 43:    e.Graphics.DrawLine(new Pen(Color.Black,(float)1),-100,0,1000,0); 44: 45:    //Draw an ordinary square about the origin. 46:    e.Graphics.DrawRectangle(new Pen(Color.Black,(float)3), 47:                             -50,-50,100,100); 48:    DumpMatrix(e.Graphics,m,new Point(0,0)); 49: 50:    m.Rotate(30,MatrixOrder.Append); 51:    DumpMatrix(e.Graphics,m,new Point(0,100)); 52: 53:    gs=e.Graphics.Save(); 54:    e.Graphics.MultiplyTransform(m); 55:    e.Graphics.DrawRectangle(new Pen(Color.Red,3), 56:                             -50,-50,100,100); 57:    e.Graphics.Restore(gs); 58: 59:    m.Scale(1,3,MatrixOrder.Append); 60:    DumpMatrix(e.Graphics,m,new Point(0,200)); 61: 62:    gs=e.Graphics.Save(); 63:    e.Graphics.MultiplyTransform(m); 64:    e.Graphics.DrawRectangle(new Pen(Color.Green,3), 65:                             -50,-50,100,100); 66:    e.Graphics.Restore(gs); 67: 68:    m.Translate(100,130,MatrixOrder.Append); 69:    DumpMatrix(e.Graphics,m,new Point(0,300)); 70: 71:    gs=e.Graphics.Save(); 72:    e.Graphics.MultiplyTransform(m); 73:    e.Graphics.DrawRectangle(new Pen(Color.Blue,3), 74:                             -50,-50,100,100); 75:    e.Graphics.Restore(gs); 76:   } 77: 78:   public MatrixElements() 79:   { 80:    this.Paint+=new PaintEventHandler(OnPaint); 81:    this.SizeChanged+=new EventHandler(OnSize); 82:   } 83: 84:   static void Main() 85:   { 86:    Application.Run(new MatrixElements()); 87:   } 88:  } 89: } 

Compile the code using the following command line:

 csc /t:winexe matrixelements.cs 

Cutting to the chase, lines 40 and 41 create a matrix that shifts the origin to the center of the window. Lines 42 and 43 draw axis lines just to look nice and give a point of reference.

Lines 46 and 47 draw our reference square. This is the same drawing command used for all the other objects onscreen, and then line 48 dumps our identity matrix.

Line 50 rotates the matrix, which is again dumped to text, and then line 53 saves the graphics state so as not to mess up the origin. Line 54 uses the matrix, lines 55 and 56 draw our newly rotated square, and line 57 restores the world transform back to its origin-in-the-middle state.

Line 59 scales the matrix, making it 3 times taller than it is wide and stretching the output in the Y direction. Lines 62 “66 use the transformation, draw the square, and return the world transform to our chosen default.

Line 68 then translates the matrix moving to the right and down. Line 69 shows the matrix settings and we round-off on lines 71 “75 drawing the last, rotated, scaled, and translated square.

To draw the matrix data itself, lines 15 “26 save the graphics state and reset the transform, place the text onscreen, and then returns the transform back to its original state before returning.

The final output from this program is shown in Figure 3.5.11.

Figure 3.5.11. Matrix transformations of a simple square.

graphics/0305fig11.gif

I l @ ve RuBoard


C# and the .NET Framework. The C++ Perspective
C# and the .NET Framework
ISBN: 067232153X
EAN: 2147483647
Year: 2001
Pages: 204

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net