Contents

1.0 - Introduction
2.0 - Transformations
3.0 - The user interface for testing
4.0 - Support functions
5.0 - Basic transformations with Matrix
5.1 - Translate
5.2 - Rotate
5.3 - Scale
5.4 - Skew
5.5 - Flip
6.0 - Combining transformations
6.1 - TransformGroup
6.2 - Matrix
7.0 - Matrix properties M11, M12, M21, M22, offsetX, offsetY
8.0 - Matrix from LayoutTransform or RenderTransform
9.0 - Summary
10.0 - Notes/Links


Download source code (Visual Studio 2008, C#): Solution_WPFMatrixTransform_05.zip [33 kB].
Download test files (XAML, PNG): Testfiles_WPFMatrixTransform_05.zip [156 kB].


Environment: WPF, Framework 3.5.


1.0 - Introduction

Transformation, moving and/or resizing, is a practical extension to an object's properties especially from a vector graphics point of view (XAML). It allows us to make a basic object and reshape/reposition it afterwards depending on the circumstances.
The System.Windows.Media namespace has several classes for transformations. Some specialist classes are RotateTransform, ScaleTransform, SkewTransform and TranslateTransform. A more universal class is MatixTransform which uses the Matrix structure.
In the following chapters a practical summary how to use the MatrixTransform class and the Matrix structure for transforming objects.


2.0 - Transformations

Transformation in a 2-D plane is for rotation, scaling, skewing and translation. It can be applied to shapes and other elements (UIElement, FrameworkElement). The transformation classes for WPF are in the System.Windows.Media namespace.


  Table 1 - 2D transformation classes (WPF).
Class name Description
RotateTransformRotates an object about a specified point in a 2-D x-y coordinate system
ScaleTransformScales an object in the 2-D x-y coordinate system
SkewTransformRepresents a 2-D skew
TranslateTransformTranslates (moves) an object in the 2-D x-y coordinate system
MatrixTransformTransforms an object in the 2-D x-y coordinate system


The MatrixTransform class is a special case by which transformations are carried out on a more basic level using the Matrix structure.


  Table 2 - Matrix - Some 'standard' methods.
Matrix Method Description
RotateApplies a rotation of the specified angle about the origin of the Matrix structure
RotateAtRotates the matrix about the specified point
ScaleAppends the specified scale vector to the Matrix structure.
ScaleAtScales the Matrix by the specified amount about the specified point
SkewAppends a skew to the Matrix structure
TranslateAppends a translation to the Matrix structure

  Table 3 - Matrix - Directly working with matrices.
Matrix Method Description
Append Appends the specified Matrix structure to this Matrix structure
Equals Determines whether the two specified Matrix structures have the same values
Invert Inverts this Matrix structure
Multiply Multiplies a Matrix structure by another Matrix structure
Prepend Prepends the specified Matrix structure onto this Matrix structure
SetIdentity Changes this Matrix structure into an identity matrix

  Table 4 - Matrix - Some properties.
Matrix Property Description
M11 Gets or sets the value of the first row and first column of this Matrix structure
M12 Gets or sets the value of the first row and second column of this Matrix structure
M21 Gets or sets the value of the second row and first column of this Matrix structure
M22 Gets or sets the value of the second row and second column of this Matrix structure
offsetX Gets or sets the value of the third row and first column of this Matrix structure
offsetY Gets or sets the value of the third row and second column of this Matrix structure


The Matrix structure has methods for rotation, scaling, skewing and translation (Table 2) with the same function as the RotateTransform, ScaleTransform, SkewTransform and TranslateTransform classes but it has also some extra methods and properties for detailed manipulation (Table 3, Table 4). This is only a selection from all the possibilities.

A transformation is applied to an object (UIElement, FrameworkElement) with it's RenderTransform or LayoutTransform property.


  C# - Rotating a Polyline with MatrixTransform.

 using System;
 using System.Windows;
 using System.Windows.Media;
 using System.Windows.Shapes;
 [...]

 Polyline oP = new Polyline();
 [...]

 Matrix m = new Matrix();
 m.Rotate(45.0);
 MatrixTransform mT = new MatrixTransform(m);
 oP.RenderTransform = mT;


3.0 - The user interface for testing

The methods from Table 3 and the properties from Table 4 were tested with a function for each of them. A user interface was made (Fig. 3.1) with buttons for selecting a transformation and showing the result on screen.

The user interface has two panels, a left panel for the object to transform and a right panel for the transformed object. The original object and the transformed object can be saved with menu File, as XAML and as PNG bitmap. A XAML file can be opened again to view a previously saved transformation.




Fig. 3.1 - Testing transformations on an object.

The test-object is a Canvas with a random pattern (red Line shapes) and a border (gray Rectangle). Each of the transformations activated by the buttons is discussed in the chapters below.
The user interface is available in the download (see top of page), together with the XAML and PNG files generated with it.


4.0 - Support functions

Before the transformation functions are discussed first an overview of the support functions they use, to prevent excess code in the actual descriptions.


4.1 - Creating a test object

Transformations are applied to a test object, a Canvas with random lines. The object is created in a separate function NewRandLines. See Fig. 3.1 for an example.


  C# - A new object to transform.

  using System.Windows;
  using System.Windows.Controls;
  using System.Windows.Media;
  using System.Windows.Shapes;
  using System.Security.Cryptography;
  [...]

  private const int SIZE_PAN = 160;       // Width and height of L and R panel.
  private const double SIZE_OBJ = 100.0;  // Width and height of object to transform.
  [...]

  // ---------------------------------------------------------------
  // Date       151010
  // Purpose    A Canvas with random lines.
  // Entry      None
  // Return     The Canvas.
  // Comments   This is the object to transform.
  // ---------------------------------------------------------------
  private Canvas NewRandLines()
  {
      int iN = 4;  // Number of lines.

      // Background for the lines.
      Canvas oC = new Canvas();
      oC.Width = SIZE_OBJ;
      oC.Height = SIZE_OBJ;
      oC.Background = Brushes.FloralWhite;
      oC.SnapsToDevicePixels = true;
      Canvas.SetLeft(oC, (SIZE_PAN / 2) - (SIZE_OBJ / 2));
      Canvas.SetTop(oC, (SIZE_PAN / 2) - (SIZE_OBJ / 2));

      // Border.
      Rectangle oR = new Rectangle();
      oR.Width = SIZE_OBJ;
      oR.Height = SIZE_OBJ;
      oR.Stroke = Brushes.DarkGray;
      oR.Fill = Brushes.Transparent;
      oR.RadiusX = 0.0;
      oR.RadiusY = 0.0;
      Canvas.SetLeft(oR, 0);
      Canvas.SetTop(oR, 0);

      // Random x-y coordinates for lines (0..255).
      RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider();
      byte[] byte1 = new byte[iN];    // Top.
      csp.GetBytes(byte1);
      byte[] byte2 = new byte[iN];    // Bottom.
      csp.GetBytes(byte2);
      byte[] byte3 = new byte[iN];    // Right.
      csp.GetBytes(byte3);
      byte[] byte4 = new byte[iN];    // Left.
      csp.GetBytes(byte4);

      // Add border to canvas.
      oC.Children.Add(oR);

      // Add lines to canvas.
      for (int i = 0; i < iN; i++)
      {
          // From top to bottom.
          Line oL1 = new Line();
          oL1.Stroke = Brushes.Tomato;
          oL1.StrokeThickness = 1.0;
          oL1.X1 = (byte1[i] / 256.0) * SIZE_OBJ;
          oL1.Y1 = 0.0;
          oL1.X2 = (byte2[i] / 256.0) * SIZE_OBJ;
          oL1.Y2 = SIZE_OBJ;
          oC.Children.Add(oL1);
          // From right to left.
          Line oL2 = new Line();
          oL2.Stroke = Brushes.Tomato;
          oL2.StrokeThickness = 1.0;
          oL2.X1 = SIZE_OBJ;
          oL2.Y1 = (byte3[i] / 256.0) * SIZE_OBJ;
          oL2.X2 = 0.0;
          oL2.Y2 = (byte4[i] / 256.0) * SIZE_OBJ;
          oC.Children.Add(oL2);
      }
      return oC;
  }


The Canvas background for the object (oC) has a border (oR) and two sets of random lines drawn on it (oL1, oL2). The first set of 4 lines is drawn from top to bottom, the second set from right to left. The positions where the lines touch the border are calculated with the RNGCryptoServiceProvider GetBytes method which returns array of random bytes.
The line-pattern serves as a visual aid for the comparison of the original and the transformed object.


4.2 - Cloning an object

The original object is shown in the left panel and the transformed object in the right panel. It is not allowed to add the original object to two different panels, transformed or not. The following exception is thrown:

"Specified element is already the logical child of another element. Disconnect it first."

To prevent this, a clone is made from the original object, transformed, and then added to the second panel.


  C# - Clone an object.

  using System;
  using System.Windows;
  using System.Windows.Markup;  // XamlWriter, XamlReader.
  [...]

  // ---------------------------------------------------------------
  // Date      090409
  // Purpose   Clone a XAML object (disconnect).
  // Entry     obj - The object to clone.
  // Return    A disconnected copy of the object.
  // Comments  
  // ---------------------------------------------------------------
  public Object CloneObject(Object obj)
  {
      try
      {
          // Write to string.
          string sXaml = XamlWriter.Save(obj);
          // Read from string.
          return XamlReader.Parse(sXaml);
      }
      catch
      {
          return null;
      }
  }


  C# - A transformed clone.

  private Canvas canvasBackL;   // Background for the object to transform (left panel).
  private Canvas canvasBackR;   // Background for the transformed object (right panel).
  Polygon oP1 = new Polygon();  // The object to clone.
  [...]

  // Add oP1 to the left panel.
  canvasBackL.Children.Add(oP1);  
  // Make a clone.
  Polygon oP2 = (Polyline)CloneObject(oP1);
  // Transform oP2.
  // ...
  // Add oP2 to the right panel.
  canvasBackR.Children.Add(oP2);  


4.3 - Clearing the canvas

A new object (a Canvas with a random pattern of lines) is created for each test. To prevent stacking old and new images on top of each other, the left and right panels (Fig. 3.1) are cleared before a new object and it's transformation are added to it.


  C# - Clearing the panels in the user interface.

  private Canvas canvasBackL;   // Background for the object to transform (left panel).
  private Canvas canvasBackR;   // Background for the transformed object (right panel).
  private Rectangle oBorderL;   // Border for canvasBackL.
  private Rectangle oBorderR;   // Border for canvasBackR.
  [...]

  // Clear canvas.
  private void ClearCanvasBack()
  {
      canvasBackL.Children.Clear();
      canvasBackL.Children.Add(oBorderL);
      canvasBackR.Children.Clear();
      canvasBackR.Children.Add(oBorderR);
  }



5.0 - Basic transformations with Matrix

The following Matrix transformations are tested: Translate, Rotate, RotateAt, Scale, Skew, Flip-X and Flip-Y. The latter two are variations of Scale.

Each of the transformations has it's own test-function and they all have the same structure:
  1. Remove previously added objects from left and right panel (Canvas) in the user interface with function ClearCanvasBack
  2. Make a new object with function NewRandLines
  3. Add the returned object to the left panel as child
  4. Make a copy of this object by cloning, with function CloneObject
  5. Transform the copy
  6. Add the transformed object to the right panel as child
The original object and the transformed object can be saved to file as XAML and as PNG bitmap with menu File. The filenames for each test (transformation-type) are fixed and set in the test-function. The PNG files are used as illustration in the sections below. The XAML and PNG files are also available as separate download.


5.1 - Translate

The Matrix Translate method moves an object to a new position without changing it's size. Directions can be positive or negative.


  C# - Translate.

  // Test 01 - Translate.
  private void Test_01()
  {
      sFileNameL = "Test_01L";
      sFileNameR = "Test_01R";
      ClearCanvasBack();
      
      // Original.
      Canvas oC = NewRandLines();
      canvasBackL.Children.Add(oC);

      // Transformed.
      Canvas oC2 = (Canvas)CloneObject(oC);
      Matrix mMatrix = new Matrix();
      mMatrix.Translate(20.0, 20.0);
      MatrixTransform mT = new MatrixTransform(mMatrix);
      oC2.RenderTransform = mT;
      // oC2.LayoutTransform = mT;

      canvasBackR.Children.Add(oC2);
  }





Fig. 5.1 - Translation.
Test_01L.xaml, Test_01R.xaml.


5.2 - Rotate

An object is rotated with the Matrix Rotate method. The rotation center for this method is (0,0). Alternatively use the RotateAt method to rotate at another rotation center.


  C# - Rotate.

  // Test 02 - Rotate.
  // RotateAt has no effect with LayoutTransform.
  private void Test_02()
  {
      sFileNameL = "Test_02L";
      sFileNameR = "Test_02R";
      ClearCanvasBack();
      
      // Original.
      Canvas oC = NewRandLines();
      canvasBackL.Children.Add(oC);            

      Matrix mMatrix = new Matrix();
      
      // 2a - Rotates at top-left point (0,0).
      // mMatrix.Rotate(45.0);      
      
      // 2b - Rotates at top-left point (0,0).
      // mMatrix.RotateAt(45.0, 0.0, 0.0);
      
      // 2c - Rotates at center.
      mMatrix.RotateAt(45.0, SIZE_OBJ / 2, SIZE_OBJ / 2);
      
      // Transformed.
      Canvas oC2 = (Canvas)CloneObject(oC);   
      // Move to center of parent.
      double center = (SIZE_PAN / 2) - (SIZE_OBJ / 2);
      Canvas.SetLeft(oC2, center);
      Canvas.SetTop(oC2, center);
      MatrixTransform mT = new MatrixTransform(mMatrix);
      oC2.RenderTransform = mT;
      // oC2.LayoutTransform = mT;

      canvasBackR.Children.Add(oC2);
  }





Fig. 5.2 - Rotation.
Test_02L.xaml, Test_02R.xaml.


5.3 - Scale

Scaling with the Matrix Scale method resizes the object, in x- and/or y-direction.
Use this to make thumbnails or to stretch the object. Contrary to bitmaps, the resized image still has the same quality as the original image because it is vector graphics in XAML. After scaling you can save the image as high resolution bitmap. See the FileSavePNG function in the download.


  C# - Scale.

  // Test 03 - Scale.
  // Scale a Canvas with MatrixTransform (x and y).
  private void Test_03()
  {
      sFileNameL = "Test_03L";
      sFileNameR = "Test_03R";
      ClearCanvasBack();

      // Original.
      Canvas oC = NewRandLines();
      canvasBackL.Children.Add(oC);     

      // Transformed.
      Matrix mMatrix = new Matrix(); 
      mMatrix.Scale(0.5, 0.5);
      MatrixTransform mT = new MatrixTransform(mMatrix);
      Canvas oC2 = (Canvas)CloneObject(oC);
      oC2.RenderTransform = mT;
      // oC2.LayoutTransform = mT;

      canvasBackR.Children.Add(oC2);
  }





Fig. 5.3 - Scaling.
Test_03L.xaml, Test_03R.xaml.


5.4 - Skew

Skewing with the Matrix Skew method pulls one side of the object to the left or to the right and/or to top or bottom. This gives a simple 3D effect to the image. Notice, however, that converging lines are not possible. The transformed object has always a parallelogram as boundary.


  C# - Skew.

  // Test 04 - Skew.
  // Skew in x- and y-direction.
  private void Test_04()
  {
      sFileNameL = "Test_04L";
      sFileNameR = "Test_04R";
      ClearCanvasBack();
      
      // Original.
      Canvas oC = NewRandLines();
      canvasBackL.Children.Add(oC); 

      // Transformed.
      Matrix mMatrix = new Matrix();
      mMatrix.Skew(15.0, 5.0);                            // Skew x,y.
      MatrixTransform mT = new MatrixTransform(mMatrix);
      Canvas oC2 = (Canvas)CloneObject(oC);
      oC2.RenderTransform = mT;
      // oC2.LayoutTransform = mT;                        // No translation

      canvasBackR.Children.Add(oC2);
  }





Fig. 5.4 - Skewing in x- and y-direction.
Test_04L.xaml, Test_04R.xaml.


5.5 - Flip

Flipping will mirror the object in x- and/or y-direction. Use the Scale method with negative parameters to achieve this.
A parameter value of -1 will flip the image as it is. With larger or smaller negative values the object is also scaled. So, flipping and scaling is easily combined.


  C# - Flip horizontally.

  // Test 05 - Flip horizontally.
  private void Test_05()
  {
      sFileNameL = "Test_05L";
      sFileNameR = "Test_05R";
      ClearCanvasBack();

      // Original.
      Canvas oC = NewRandLines();
      canvasBackL.Children.Add(oC);

      // Transformed.
      Matrix mMatrix = new Matrix();
      mMatrix.Scale(-1.0, 1.0);                           // Flip x.
      mMatrix.Translate(SIZE_OBJ, 0.0);                   // Translate.
      MatrixTransform mT = new MatrixTransform(mMatrix);
      Canvas oC2 = (Canvas)CloneObject(oC);
      oC2.RenderTransform = mT;
      // oC2.LayoutTransform = mT;                        // No translation

      canvasBackR.Children.Add(oC2);
  }





Fig. 5.5 - Flip in x-direction.
Test_05L.xaml, Test_05R.xaml.

  C# - Flip vertically.

  // Test 06 - Flip vertically.
  private void Test_06()
  {
      sFileNameL = "Test_06L";
      sFileNameR = "Test_06R";
      ClearCanvasBack();

      // Original.
      Canvas oC = NewRandLines();
      canvasBackL.Children.Add(oC);

      // Transformed.
      Matrix mMatrix = new Matrix();
      mMatrix.Scale(1.0, -1.0);                           // Flip y.
      mMatrix.Translate(0.0, SIZE_OBJ);                   // Translate.
      MatrixTransform mT = new MatrixTransform(mMatrix);
      Canvas oC2 = (Canvas)CloneObject(oC);
      oC2.RenderTransform = mT;
      // oC2.LayoutTransform = mT;

      canvasBackR.Children.Add(oC2);
  }





Fig. 5.6 - Flip in y-direction.
Test_06L.xaml, Test_06R.xaml.


6.0 - Combining transformations

There are two ways to combine transformations. The TransformGroup class is used to combine one or more of the RotateTransform, ScaleTransform, SkewTransform and TranslateTransform transformations. When working with Matrix simply execute two or more of it's Rotate, Scale, Skew or Translate methods in a sequence.


6.1 - TransformGroup

The TransformGroup is a wrapper to combine, RotateTransform, ScaleTransform, SkewTransform and/or TranslateTransform transformations.


  C# - Combining ScaleTransform and RotateTransform.

  double dScale = 8.0;

  // The Polygon to scale and rotate.
  Polygon oP = new Polygon();
  oP.StrokeThickness = 1.0;
  oP.Stroke = Brushes.Tomato;
  oP.Fill = Brushes.Lavender;
  [...]

  TransformGroup tg = new TransformGroup();

  // Scale up.
  ScaleTransform st = new ScaleTransform(dScale, dScale, 0.0, 0.0);
  tg.Children.Add(st);

  // Rotate.
  RotateTransform rt = new RotateTransform(225.0, 0.0, 0.0);
  tg.Children.Add(rt);

  oP.LayoutTransform = tg;


The structure in XAML:


  XAML - A scaled and rotated Polygon.

 <Polygon 
    Points="0,5 8,5 8,0 16,8 8,16 8,11 0,11" 
    Fill="#FFE6E6FA" 
    Stroke="#FFFF6347" 
    StrokeThickness="0.125" 
    <Polygon.LayoutTransform>
       <TransformGroup>
          <TransformGroup.Children>
             <ScaleTransform ScaleX="8" ScaleY="8" CenterX="0" CenterY="0" />
             <RotateTransform Angle="225" CenterX="0" CenterY="0" />
          </TransformGroup.Children>
       </TransformGroup>
    </Polygon.LayoutTransform>
 </Polygon>


6.2 - Matrix

Matrix transformations are simply combined by executing two or more transformation methods after each other. Any number of transformations and transformation-types can be combined.


  C# - Combining several Matrix transformations.

  private Canvas canvasBack;   // Background for the objects to transform.
  [...]

  // Test 09 - Combined transformations (scale, flip, move).
  private void Test_09()
  {
      sFileNameL = "Test_09L";
      sFileNameR = "Test_09R";
      ClearCanvasBack();

      // Original.
      Canvas oC = NewRandLines();
      canvasBackL.Children.Add(oC);

      // Transformed.
      Matrix mMatrix = new Matrix();
      // 1/2 size + flip x + flip y.
      mMatrix.Scale(-0.7, -0.7);         
      // Move.
      mMatrix.Translate(SIZE_OBJ + 20.0, SIZE_OBJ + 20.0);  

      MatrixTransform mT = new MatrixTransform(mMatrix);
      Canvas oC2 = (Canvas)CloneObject(oC);
      oC2.RenderTransform = mT;
      // oC2.LayoutTransform = mT;

      canvasBackR.Children.Add(oC2);
  }


In this example a new Matrix is resized and also flipped in x- and y-direction (by using negative values). After that it is moved to another position with Translate. A copy of the original Canvas (CloneObject) is then transformed with this Matrix.

Notice that the order in which transformations are executed is important because some of them are done with respect to the origin of the coordinate system (rotation, scaling). An object might be positioned in the origin but most often it is on another position which gives a different result.
See also the Matrix Prepend, RotatePrepend, RotateAtPrepend, ScalePrepend, ScaleAtPrepend and SkewPrepend methods to place transformation at the beginning of a queue.


7.0 - Matrix properties M11, M12, M21, M22, offsetX, offsetY.

Matrix has some specialized properties for directly working with it's structure. The matrix has 3 rows and 3 columns and the elements in the first two columns are accessible with the M11, M12, M21, M22, offsetX, and offsetY properties.

A default matrix (identity matrix) has a value of 1 in coefficients [1,1],[2,2],[3,3] and a value of 0 in the rest of the coefficients:
M11=1 and M22=1, M12=0, M21=0, OffsetX=0, and OffsetY=0.
In an affine matrix (for WPF) coefficients [3,1],[3,2],[3,3] are implied to always have the values 0,0,1.


  C# - Identity matrix

 M11      M12      0    |->     1 0 0
 M21      M22      0            0 1 0
 OffsetX  OffsetY  1            0 0 1


A Matrix object saved as XAML with the XamlWriter (System.Windows.Markup):


  XAML - Matrix (Test_08_Matrix.xaml).

 <Matrix xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    1,0,0.4,1,7.5,49.5
 </Matrix>


This matrix has values M11=1 and M22=0, M12=0.4, M21=1, OffsetX=7.5, and OffsetY=49.5.
See also the MatrixValueSerializer Class (System.Windows.Media.Converters).

There are two ways to create a matrix with certain values for M11, M12, M21, M22, offsetX and offsetY: with the parameters in the constructor or directly with the properties after an instance is made.


  C# - Matrix constructor.

  // Test 07 - Matrix constructor.
  private void Test_07()
  {
      sFileNameL = "Test_07L";
      sFileNameR = "Test_07R";
      ClearCanvasBack();

      // Original object.
      Canvas oC = NewRandLines();
      canvasBackL.Children.Add(oC);                                    

      // Params: M11, M12, M21, M22, offsetX, offsetY.

      // 7a - Identity matrix. No change.
      // Matrix mMatrix = new Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);   
      
      // 7b - Scale x (Stretch).
      // Matrix mMatrix = new Matrix(1.2, 0.0, 0.0, 1.0, 0.0, 0.0);   

      // 7c - Scale -x (Shrink).
      // Matrix mMatrix = new Matrix(0.5, 0.0, 0.0, 1.0, 0.0, 0.0);   
      
      // 7d - Skew y.
      // Matrix mMatrix = new Matrix(1.0, 0.4, 0.0, 1.0, 0.0, -20.0);   
      
      // 7e - Skew x,y.
      // Matrix mMatrix = new Matrix(1.0, 0.3, 0.3, 1.0, -10.0, -10.0);   

      // 7f - Skew x, translate. 
      Matrix mMatrix = new Matrix(1.0, 0.0, 0.4, 1.0, -20.0, 0.0);

      // 7g - Skew y, scale x (stretch).
      // Matrix mMatrix = new Matrix(1.2, 0.4, 0.0, 1.0, -10.0, -17.0);     
      
      // 7h - Translate x,y. No effect with LayoutTransform.
      // Matrix mMatrix = new Matrix(1.0, 0.0, 0.0, 1.0, 20.0, 20.0); 

      // Transformed object.
      MatrixTransform mT = new MatrixTransform(mMatrix);
      Canvas oC2 = (Canvas)CloneObject(oC);
      // oC2.LayoutTransform = mT;
      oC2.RenderTransform = mT;

      canvasBackR.Children.Add(oC2);
  }


  C# - Matrix properties M11 ... offsetY.

  // Test 08 - Matrix properties M.
  // Matrix properties M11, M12, M21, M22, offsetX, offsetY.
  private void Test_08()
  {
      sFileNameL = "Test_08L";
      sFileNameR = "Test_08R";
      ClearCanvasBack();
      
      // Original object.
      Canvas oC = NewRandLines();
      canvasBackL.Children.Add(oC);

      // Skew x, translate.
      // Same as Test 7f.
      Matrix mMatrix = new Matrix();
      mMatrix.M11 = 1.0;
      mMatrix.M12 = 0.0;
      mMatrix.M21 = 0.4;
      mMatrix.M22 = 1.0;
      mMatrix.OffsetX = -20.0;
      mMatrix.OffsetY = 0.0;

      // Transformed object.
      MatrixTransform mT = new MatrixTransform(mMatrix);
      Canvas oC2 = (Canvas)CloneObject(oC);
      // oC2.LayoutTransform = mT;
      oC2.RenderTransform = mT;

      canvasBackR.Children.Add(oC2);
  }





Fig. 7.1 - Skew x and translation.
Test_08L.xaml, Test_08R.xaml.


Saving the image as XAML reveals more about it's structure:


  XAML - A Canvas transformed with a Matrix (Test_08R.xaml).

 <Canvas 
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   Background="#FFFFFFFF" 
   Width="160" Height="160" 
   HorizontalAlignment="Left" VerticalAlignment="Top" 
   SnapsToDevicePixels="True" 
   Canvas.Left="168" Canvas.Top="40">
   <!-- Border -->
   <Rectangle 
      Fill="#00FFFFFF" Stroke="#FFA9A9A9" 
      Width="160" Height="160" 
      Canvas.Left="0" Canvas.Top="0" />
   <!-- Transformed Canvas -->
   <Canvas 
      Background="#FFFFFAF0" 
      Width="100" Height="100" 
      RenderTransform="1,0,0.4,1,-20,0"
      SnapsToDevicePixels="True" 
      Canvas.Left="30" Canvas.Top="30">
      <!-- Border -->
      <Rectangle 
         RadiusX="0" RadiusY="0" 
         Fill="#00FFFFFF" Stroke="#FFA9A9A9" 
         Width="100" Height="100" 
         Canvas.Left="0" Canvas.Top="0" />
      <!-- Random lines -->
      <Line X1="21.09375" Y1="0" X2="61.328125" Y2="100" 
            Stroke="#FFFF6347" StrokeThickness="1" />
      <Line X1="100" Y1="39.84375" X2="0" Y2="60.15625" 
            Stroke="#FFFF6347" StrokeThickness="1" />
      <Line X1="60.9375" Y1="0" X2="14.453125" Y2="100" 
            Stroke="#FFFF6347" StrokeThickness="1" />
      <Line X1="100" Y1="4.6875" X2="0" Y2="71.484375" 
            Stroke="#FFFF6347" StrokeThickness="1" />
      <Line X1="4.6875" Y1="0" X2="93.75" Y2="100" 
            Stroke="#FFFF6347" StrokeThickness="1" />
      <Line X1="100" Y1="31.640625" X2="0" Y2="58.984375" 
            Stroke="#FFFF6347" StrokeThickness="1" />
      <Line X1="37.890625" Y1="0" X2="31.25" Y2="100" 
            Stroke="#FFFF6347" StrokeThickness="1" />
      <Line X1="100" Y1="55.859375" X2="0" Y2="43.359375" 
            Stroke="#FFFF6347" StrokeThickness="1" />
   </Canvas>
 </Canvas>



The transformed Canvas with the random pattern (oC2, skewing and translation) has a surprisingly simple representation in XAML as RenderTransform property (or LayoutTransform property) with the value of the Matrix (M11, M12, M21, M22, offsetX, offsetY).
In contrast, RotateTransform, ScaleTransform, SkewTransform and TranslateTransform need an extensive structure as TransformGroup. The XAML demonstrates how objects can be transformed in a straightforward way with a minimum of code if the designer is used.


8.0 - Matrix from LayoutTransform or RenderTransform

It is possible to get the Matrix from an object (UIElement, FrameworkElement) as a definition how this object is (was) transformed, consistent with the structure we have seen in the XAML above.

An object is transformed with it's LayoutTransform or RenderTransform property. The RenderTransform property gets or sets transform information that affects the rendering position of an UIElement (System.Windows) and is intended for animating or applying a temporary effect to an element. The LayoutTransform property gets or sets a graphics transformation that should apply to a FrameworkElement (System.Windows) when layout is performed. In contrast to RenderTransform, LayoutTransform will affect results of layout, but ignores TranslateTransform operations and Matrix Transform.

Either way, the transformation matrix is available from the Transform Value property:


  C# - Get the Matrix from an object.

  // A transformed Polygon.
  Polygon oP = new Polygon();
  [...]

  // Get Matrix.
  Matrix m = (Matrix)oP.LayoutTransform.Value;
  // Matrix m = (Matrix)oP.RenderTransform.Value; 
  double m11 = m.M11;
  double m12 = m.M12;
  double m21 = m.M21;
  double m22 = m.M22;
  double mox = m.OffsetX;
  double moy = m.OffsetY; 


If the object is not transformed you will get the identity matrix, which can be tested with the Matrix IsIdentity property.

The Matrix structure has several methods to manipulate matrices directly:


  Table 5 - Matrix - Working with matrices.
Matrix Method Description
Append Appends the specified Matrix structure to this Matrix structure
Equals Determines whether the two specified Matrix structures have the same values
Invert Inverts this Matrix structure
Multiply Multiplies a Matrix structure by another Matrix structure
Prepend Prepends the specified Matrix structure onto this Matrix structure
SetIdentity Changes this Matrix structure into an identity matrix


With these properties and methods the transformation of an object can be modified via it's Matrix.


  C# - Modify the Matrix of an object.

  // The object to transform.
  Polygon oP = new Polygon();
  [...]

  // Get Matrix.
  Matrix m = (Matrix)oP.RenderTransform.Value;

  // Modify matrix.
  // ...

  // Set Matrix.
  MatrixTransform mT = new MatrixTransform(mMatrix);
  oP.RenderTransform = mT;


In the following example the matrix from a skewed object (a Canvas with random circles) is inverted with the Invert method (10a) and also changed into an identity matrix (10b) with the SetIdentity method, which will un-skew it.


  C# - Methods Invert and Identity.

  // Test_10 - Matrix Invert, Identity.
  private void Test_10()
  {
      sFileNameL = "Test_10L";
      sFileNameR = "Test_10R";
      int iN = 25;
      double Diameter = 10.0;

      // The canvas to skew.
      Canvas oC = new Canvas();
      oC.Width = SIZE_OBJ;
      oC.Height = SIZE_OBJ;
      oC.Background = Brushes.Cornsilk;
      oC.SnapsToDevicePixels = true;
      Canvas.SetLeft(oC, (SIZE_PAN / 2) - (SIZE_OBJ / 2));
      Canvas.SetTop(oC, (SIZE_PAN / 2) - (SIZE_OBJ / 2));

      // Border for canvas.
      Rectangle oR = new Rectangle();
      oR.Width = SIZE_OBJ;
      oR.Height = SIZE_OBJ;
      oR.Stroke = Brushes.DarkGray;
      oR.Fill = Brushes.Transparent;
      oR.RadiusX = 0.0;
      oR.RadiusY = 0.0;
      Canvas.SetLeft(oR, 0);
      Canvas.SetTop(oR, 0);

      // Random x-y coordinates for ellipses on canvas (0..255).
      RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider();
      byte[] byteX = new byte[iN];
      csp.GetBytes(byteX);
      byte[] byteY = new byte[iN];
      csp.GetBytes(byteY);

      // Add border to canvas.
      oC.Children.Add(oR);
      // Add ellipses to canvas.
      for (int i = 0; i < iN; i++)
      {
          Ellipse oE = new Ellipse();
          oE.Stroke = Brushes.DarkGray;
          oE.Fill = Brushes.Lavender;
          oE.Width = Diameter;
          oE.Height = Diameter;
          Canvas.SetLeft(oE, 
              ((double)byteX[i] / 256.0) * SIZE_OBJ - (Diameter / 2));
          Canvas.SetTop(oE, 
              ((double)byteY[i] / 256.0) * SIZE_OBJ - (Diameter / 2));
          oC.Children.Add(oE);
      }

      // Skew canvas x-y.
      SkewTransform st = 
          new SkewTransform(15.0, 15.0, (SIZE_OBJ / 2), (SIZE_OBJ / 2));
      oC.RenderTransform = st;

      // Show skewed canvas on left panel.
      canvasBackL.Children.Clear();
      canvasBackL.Children.Add(oBorderL);
      canvasBackL.Children.Add(oC);

      // ------------------------------

      // Clone canvas and modify it's matrix.
      Canvas oC2 = (Canvas)CloneObject(oC);
      Matrix m = (Matrix)oC.RenderTransform.Value;
      m.Invert();          // 10a) Invert.
      // m.SetIdentity();     // 10b) un-skew.

      // Transform with the new matrix.
      MatrixTransform mT = new MatrixTransform(m);
      oC2.RenderTransform = mT;

      // Show on right panel.
      canvasBackR.Children.Clear();
      canvasBackR.Children.Add(oBorderR);
      canvasBackR.Children.Add(oC2);
  }






Fig. 8.1 - Invert.
The matrix from the left image is inverted.
Test_10La.xaml, Test_10Ra.xaml.





Fig. 8.2 - Identity.
The matrix from the left image is changed into an identity matrix.
Test_10Lb.xaml, Test_10Rb.xaml.


The SetIdentity method undoes all transformations previously carried out on the object. This can be used to 'reset' the object before applying a new transformation.
This example also shows that transformations with RotateTransform, ScaleTransform, SkewTransform and TranslateTransform are interchangeable with MatrixTransform, via Matrix.


9.0 - Summary

A universal class for transformations on UIElement or FrameworkElement is the MatrixTransform class (System.Windows.Media). It has all the functions from the RotateTransform, ScaleTransform, SkewTransform and TranslateTransform classes but adds extra control via the Matrix structure.


10.0 - Notes/Links


[1] MSDN - MatrixTransform Class.
Creates an arbitrary affine matrix transformation that is used to manipulate objects or coordinate systems in a 2-D plane.
http://msdn.microsoft.com/en-us/library/system.windows.media.matrixtransform.aspx

[2] MSDN - Matrix Structure.
Represents a 3x3 affine transformation matrix used for transformations in 2-D space.
http://msdn.microsoft.com/en-us/library/system.windows.media.matrix.aspx