To handle 2D transformations, WPF has high-level classes (TranslateTransform, ScaleTransform, RotateTransform and SkewTransform). But for various reasons, it may be interesting to use the lower-level technique of matrices. For educational purposes first. But also to keep reflexes acquired with frameworks such as Direct3D or OpenGL.

A matrix is an array of values that defines a transformation of coordinates.

The Matrix structure represents a matrix with two columns and three rows :

M11

M12

M21

M22

OffsetX (M31)

OffsetY (M32)

M11, M12, M21, M22, OffsetX and Offset are properties of the Matrix structure (M31 and M32 don't exist as properties).

A transformation can be applied to a point by multiplying it by a matrix (it is also possible to use the Transform methods of the Matrix and MatrixTransform classes). The resulting point has the following coordinates :

x’ = M11x + M21y + OffsetX y’ = M12x + M22y + OffsetY

A transformation can be applied to a vector according to the same principle.

The identity matrix has a value 1 for M11 and M22, and 0 for the other cells. The constructor of the Matrix structure creates by default an identity matrix.

The M11 and M22 properties determine the scale factor, respectively on the X axis and on the Y axis, as shown by the equation :

x’ = M11x y’ = M22y

The GeometryHelper Class of Perspective library provides the following static method which returns a scaling matrix :

public static Matrix GetScaleMatrix(double scaleX, double scaleY) { return new Matrix { M11 = scaleX, M22 = scaleY }; }

A translation is defined by properties OffsetX and OffsetY :

public static Matrix GetTranslationMatrix(double offsetX, double offsetY) { return new Matrix { OffsetX = offsetX, OffsetY = offsetY }; }

This matrix corresponds to the following equation :

x’ = x + OffsetX y’ = y + OffsetY

For rotation, things get a little complicated, and involve trigonometry.

Consider a point of coordinates x and y. Compared to the origin point, it represents an initial rotation with an angle alpha. If r is the distance between the origin and the point (ie the radius of an imaginary circle), we can write:

x = r * cos(alpha) y = r * sin(alpha)

The rotation of the point on the same circle at an angle beta results in a point of coordinates :

x' = r * cos(alpha + beta) y' = r * sin(alpha + beta)

Ie :

x' = r * cos(alpha) * cos(beta) - r * sin(alpha) * sin(beta) y' = r * sin(alpha) * cos(beta) + r * cos(alpha) * sin(beta)

Either simply :

x' = x * cos(beta) - y * sin(beta) y' = y * cos(beta) + x * sin(beta)

So, the matrix corresponding to this equation is :

M11 = cos(beta)

M21 = -sin(beta)

M12 = sin(beta)

M22 = cos(beta)

The following method returns an object defining a rotation matrix at an angle expressed in degrees :

public static Matrix GetRotationMatrix(double angle) { var radianAngle = GeometryHelper.DegreeToRadian(angle); return new Matrix { M11 = Math.Cos(radianAngle), M21 = -Math.Sin(radianAngle), M12 = Math.Sin(radianAngle), M22 = Math.Cos(radianAngle) }; }

This method is used as follows :

private double _angle = 15.0; private Point _currentPoint; ... _currentPoint *= GeometryHelper.GetRotationMatrix(_angle);

The Y axis is downward, so the rotation takes place in the opposite direction of the conventional trigonometric direction.

The code of this example is available in the MatrixDemo.xaml page of the Perspective library example application.

Several matrices (each representing a transformation) may be combined by multiplying them together. Matrix multiplication is not commutative (m1 * m2 does not give the same result as m1 * m2).

Matrix m1 = GeometryHelper.GetRotationMatrix(45.0); Matrix m2 = GeometryHelper.GetTranslationMatrix(50.0, 0.0); _currentPoint *= m1 * m2;

An alternative is to use the MultiplyMatrices method of the GeometryHelper class from the Perspective library :

Matrix m1 = GeometryHelper.GetRotationMatrix(45.0); Matrix m2 = GeometryHelper.GetTranslationMatrix(50.0, 0.0); Matrix m3 = GeometryHelper.MultiplyMatrices(m1, m2); _currentPoint = m3.Transform(_currentPoint);

Powered by the Perspective WebApp - © Olivier Dewit - Lyon, Paris, France - Legal informations