Dealing with transformations in WPF: PartII-Custom and advanced 2D transformations


In a previous article we've discovered the different transformation modes like the RenderTransform, the LayoutTransform,the RelativeTransform and the Transform, we also discovered the  standard 2D transformation types like the translation, the rotation, the scale and the skew. In this article, we continue discovering the transformations. We will expose the customized ones including the MatrixTransform and the transformation combinations. 

I The MatrixTransform:

i.1 The Matrix

Before proceeding in the MatrixTransform, we need to know how to use the Matrix structure before, because all transformations are using this object. Of Corse, it will be more convenient to have a mathematical background about the matrix in order to be comfortable with the notion and then it will be more simple and straightforward to use this tool in order to build more complex transformations. Here is good reference to get fresh your memory about Matrix


Some rapid notes concerning Matrixes are exposed as follow, the multiplication should follow some rules.

1. The column number of the first one must be the same used for the row number of the second one.
2. The multiplication is not commutative. I can show this through this example. 

2x3 = 3x2 = 6 this rule is true for each multiplication IRxIR

Now, try to do the same multiplication operation for the matrix case:

1     2          2    4        14   18
             x              = 
3     4          6    8        30   40

Now switching the matrixes positions, one can note what will happen

2     4          1    2        14   20
            x               =
6     8          3    4        27   40

So this last operation will lead to a different result.

3. Matrix is not always invertible like real numbers

1/5 = 0.25 as a consequence, IR/IR is possible except for the case IR/0 

but 

6     3  
6     3 

Has not an inverse as its determinant 6x3 - 3x6 = 0 

4. As a consequence of the previous rule, the Matrix divisions are not always guarantied. 

5. Like the multiplication, the division is not commutative; the reason is quite simple, because the division is a particular case of the multiplication. In other words, it is a multiplication of one matrix by the inverse of another matrix A*B-1.

i.2 The 2D Matrix in WPF

The matrix in WPF is a structure that belongs to the namespace System.Windows.Media. The Matrix is of 3x2 dimensions.  It means that it takes 6 elements

Matrix m1 = new Matrix(m1, m2, m3, m4,
                          OffsetX, OffsetY);

The last ones are used to make shift either by X or Y form a transformation point of view.

The matrix presents some useful methods those could be used to make some standard transformations like those are exposed in the first part of the article, I mean the Translate, Rotate, Rotate At, Scale , ScaleAt, Skew, Invert and Multiply. Let's introduce them one by one

Void Translate (double offsetX, double offsetY)

Translate method accepts two arguments whishes are the offsetX and the offsetY and add them to those existing in the matrix

void Rotate(double angle)

Rotate method will accept an argument that represents the rotation angle. 

void RotateAt(double Angle, double CenterX,
                            double CenterY)

RotateAt method needs a rotation center in addition to the angle, the coordinates of the first one are determined using CenterX and CenterY  

void Scale(double ScaleX, double ScaleY)

Scale method accepts two arguments those are used to scale the matrix members. Each element in the first column will be multiplied by ScaleX and each element in the second column will be multiplied by ScaleY 

void ScaleAt(double ScaleX, double ScaleY, double CenterX, double CenterY)

Scale method accepts four arguments those are used to scale the matrix members. Each element in the first column will be multiplied by ScaleX and  each element in the second column will be multiplied by ScaleY, in addition to that the CenterX and CenterY will be used to set the new offsets.  

void Skew(double SkewX, double SkewY)

Skew method accepts two arguments SkewX and SkewY theses are used to skew the matrix. Skewing from the geometrical point of view is to make to line neither in parallel nor in orthogonal position.

void Invert()

Invert method doesn't accept arguments, it simply inverts a given matrix, and if the given matrix is not invertible then it throws an InvalidOperationException.  
And finally the Multiply method is a static one, this method accepts two matrixes as arguments and returns a result as a matrix.

static Matrix Multiply(Matrix m1, Matrix m2)

In addition to this set of methods, there are other set of methods with the same name but those have Prepend suffix. Remember that arithmetic operations are not commutative in case of matrixes AxB <>BxA or AxB-1 <> BxA-1 for example.   

Prepend means, if you are making an operation on matrixes we suppose that there are two operands and one operator like X or ./. then A prepends B in a multiplication operation this exactly means AxB, in other words, A comes as a first operand before B, in the opposite case B prepends A in a multiplication operation  means BxA and the multiplication result will not be the same in both cases for the major situations as the commutative aspect is not guaranteed in the matrixes operations.

In the other hand, if we were in a situation where there are successive operations, then prepend means that the new or the next operation will be executed before the actual one.

Recall that even transformations composition, let's say, is not commutative. Imagine a combination of a rotation R and a translation T then the result of (R o T) is completely different when comparing with (T o R) in major cases.     

Finally, the last method is the Transform one. This method enables to apply a custom transformation. It has 4 overloads. It transforms a point, a point array, a vector or a vector array. 

The mechanism is simple and is the same for the four methods, Say that you have a point that you want to transform. A point is converted to a Matrix then it is multiplied by the given transformer Matrix and the result will be a matrix that represents the new transformed point. This example will make things clear.

Given this point A (3, 6) and this transformer matrix (5,2,1,1,0,0)

The Point will be presented as follow A  (3,6,0,0,0,0) and then the result will be Point A matrix presentation x transformer matrix = (3,6,0,0,0,0) x (5,2,1,1,0,0) = (21,12,0,0,0,0)

The same is done for the rest of the overloads, I mean if we have an array of points a vector or/and array of vectors then all members will be multiplied by the matrix one by one and the result will be returned with the same manner and the same type of the arguments. In other word, if you use the overload that takes an array of points, then the result will be a transformed array of points. 

In the same context, there is a very useful related class to the matrix structure. It is the matrix converter class that helps to convert other types into the matrix format.

Matrix m2 = (Matrix)converter.ConvertFromString("5,2,1,1,0,0")

This will convert this piece of string to a matrix object. 

i.3 Transformation examples using matrix

Some special cases of linear transformations of two-dimensional space R2 are exposed as follow:

Rotation by 90 degrees counterclockwise:

0   -1
1    0

Rotation by θ degrees counterclockwise:

cosθ  -sinθ
sinθ   cosθ

Reflection against the x axis:

1    0
0   -1

Reflection against the y axis:

-1    0
 0    1

Scaling by 2 in all directions:

2    0
0    2

Horizontal shear mapping:

1    m
0    1

Squeezing horizontaly:

k    0
0    1/k

Squeezing vertically:

1/k  0
0     k

Projection onto the y axis:

0    0
0    1 

So try to play around with those transformations using this piece of XAML:

<Canvas>
   <Rectangle Canvas.Left="132" Canvas.Top="129"
            Fill="Red"
            Height="135" Name="rectangle1" Stroke="Black" Width="287">
            <Rectangle.BitmapEffect>
                <DropShadowBitmapEffect />
            </Rectangle.BitmapEffect>
            <Rectangle.RenderTransform>
                <MatrixTransform Matrix="m1,m2,m3,m4,OffsetX,OffsetY"/>
            </Rectangle.RenderTransform>
        </Rectangle>
</Canvas>

Or try to do this programmatically by using this line of code:

rectangle1.RenderTransform = new MatrixTransform(m1, m2, m3, m4, OffsetX, OffsetY)

II. The Transform combination:

The standard transformations combination is also possible and it results more complex and advanced transformations, by the way you can try this piece of XAML that combines a rotation and a scale transformation.

<Canvas>
        <Rectangle  Canvas.Left="100" Canvas.Top="22"
                   Fill="Red"
                   Height="135" Name="rectangle1" Stroke="Black"
                    Width="287">
            <Rectangle.BitmapEffect>
                <DropShadowBitmapEffect />
            </Rectangle.BitmapEffect>
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <RotateTransform x:Name="rotation" Angle="45" 
                                     CenterX="0" CenterY="0"/>
                    <ScaleTransform x:Name="scale" ScaleX="0.5"
                            ScaleY="0.5" CenterX="0" CenterY="0"/>
                </TransformGroup>
            </Rectangle.RenderTransform>
            <Rectangle.Triggers>
                <EventTrigger RoutedEvent="Window.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
              <DoubleAnimation From="0" To="45"
                             Storyboard.TargetName="rotation"
                             Storyboard.TargetProperty="Angle"/>
                             <DoubleAnimation From="0.5" To="1" 
                                      Storyboard.TargetName="scale"
                                  Storyboard.TargetProperty="ScaleX"/>
                            <DoubleAnimation From="0.5" To="1"  
                           Storyboard.TargetName="scale"
                          Storyboard.TargetProperty="ScaleY"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Rectangle.Triggers>
        </Rectangle>
</Canvas>

III. The RenderTransformOrigin story:

This is another tool used to render the transformation more granular. It helps to set the transformation origin not relative to the absolute positions of the scene but to the shape boundaries. It accepts two parameters those could be set from -1 to 1. Things will be clearer through those cases.

Case X = 0, Y= 0

<Rectangle Fill="Red" Margin="50"
                   Stroke="Black" Height="125"
                   Width="310" Canvas.Left="55"
                   Canvas.Top="80"
                   RenderTransformOrigin="0,0">
            <Rectangle.RenderTransform>
                <RotateTransform Angle="45"/>
            </Rectangle.RenderTransform>
 </Rectangle> 

1.gif

Case X = 1, Y = 1

<Rectangle Fill="Red" Margin="50"
                   Stroke="Black" Height="125"
                   Width="310" Canvas.Left="55"
                   Canvas.Top="80"
                   RenderTransformOrigin="1,1">
            <Rectangle.RenderTransform>
                <RotateTransform Angle="45"/>
            </Rectangle.RenderTransform>
 </Rectangle> 

2.gif

Case X = 1, Y= 0

<Rectangle Fill="Red" Margin="50"
                   Stroke="Black" Height="125"
                   Width="310" Canvas.Left="55"
                   Canvas.Top="80"
                   RenderTransformOrigin="1,0">
            <Rectangle.RenderTransform>
                <RotateTransform Angle="45"/>
            </Rectangle.RenderTransform>
</Rectangle>

3.gif

Case X = 0, Y= 1

<Rectangle Fill="Red" Margin="50"
                   Stroke="Black" Height="125"
                   Width="310" Canvas.Left="55"
                   Canvas.Top="80"
                   RenderTransformOrigin="0,1">
            <Rectangle.RenderTransform>
                <RotateTransform Angle="45"/>
            </Rectangle.RenderTransform>
</Rectangle>

4.gif

Case X = 0.5, Y= 0.5

<Rectangle Fill="Red" Margin="50"
                   Stroke="Black" Height="125"
                   Width="310" Canvas.Left="55"
                   Canvas.Top="80"
                   RenderTransformOrigin="0.5,0.5">
            <Rectangle.RenderTransform>
                <RotateTransform Angle="45"/>
            </Rectangle.RenderTransform>
</Rectangle>

5.gif

That's it. 

Good Dotneting !!!

Up Next
    Ebook Download
    View all
    Learn
    View all