# Matrix Transforms

- PDF for offline use
- Sample Code:
- Related APIs:

Let us know how you feel about this

Translation Quality

0/250

last updated: 2017-04

Dive deeper into SkiaSharp transforms with the versatile transform matrix

All the transforms applied to the `SKCanvas`

object are consolidated in a single instance of the `SKMatrix`

structure. This is a standard 3-by-3 transform matrix similar to those in all modern 2D graphics systems.

As you've seen, you can use transforms in SkiaSharp without knowing about the transform matrix, but the transform matrix is important from a theoretical perspective, and it is crucial when using transforms to modify paths or for handling complex touch input, both of which are demonstrated in this article and the next.

The current transform matrix applied to the `SKCanvas`

is available at any time by accessing the read-only `TotalMatrix`

property. You can set a new transform matrix using the `SetMatrix`

method, and you can restore that transform matrix to default values by calling `ResetMatrix`

.

The only other `SKCanvas`

member that directly works with the canvas's matrix transform is `Concat`

which concatenates two matrices by multiplying them together.

The default transform matrix is the identity matrix and consists of 1's in the diagonal cells and 0's everywhere else:

| 1 0 0 | | 0 1 0 | | 0 0 1 |

You can create an identity matrix using the static `SKMatrix.MakeIdentity`

method:

`SKMatrix matrix = SKMatrix.MakeIdentity();`

The `SKMatrix`

default constructor does *not* return an identity matrix. It returns a matrix with all of the cells set to zero. Do not use the `SKMatrix`

constructor unless you plan to set those cells manually.

When SkiaSharp renders a graphical object, each point (x, y) is effectively converted to a 1-by-3 matrix with a 1 in the third column:

| x y 1 |

This 1-by-3 matrix represents a three-dimensional point with the Z coordinate set to 1. There are mathematical reasons (discussed later) why a two-dimensional matrix transform requires working in three dimensions. You can think of this 1-by-3 matrix as representing a point in a 3D coordinate system, but always on the 2D plane where Z equals 1.

This 1-by-3 matrix is then multiplied by the transform matrix, and the result is the point rendered on the canvas:

| 1 0 0 | | x y 1 | × | 0 1 0 | = | x' y' z' | | 0 0 1 |

Using standard matrix multiplication, the converted points are as follows:

x' = x

y' = y

z' = 1

That's the default transform.

When the `Translate`

method is called on the `SKCanvas`

object, the `tx`

and `ty`

arguments to the `Translate`

method become the first two cells in the third row of the transform matrix:

| 1 0 0 | | 0 1 0 | | tx ty 1 |

The multiplication is now as follows:

| 1 0 0 | | x y 1 | × | 0 1 0 | = | x' y' z' | | tx ty 1 |

Here are the transform formulas:

x' = x + tx

y' = y + ty

Scaling factors have a default value of 1. When you call the `Scale`

method on a new `SKCanvas`

object, the resultant transform matrix contains the `sx`

and `sy`

arguments in the diagonal cells:

| sx 0 0 | | x y 1 | × | 0 sy 0 | = | x' y' z' | | 0 0 1 |

The transform formulas are as follows:

x' = sx · x

y' = sy · y

The transform matrix after calling `Skew`

contains the two arguments in the matrix cells adjacent to the scaling factors:

│ 1 ySkew 0 │ | x y 1 | × │ xSkew 1 0 │ = | x' y' z' | │ 0 0 1 │

The transform formulas are:

x' = x + xSkew · y

y' = ySkew · x + y

For a call to `RotateDegrees`

or `RotateRadians`

for an angle of α, the transform matrix is as follows:

│ cos(α) sin(α) 0 │ | x y 1 | × │ –sin(α) cos(α) 0 │ = | x' y' z' | │ 0 0 1 │

Here are the transform formulas:

x' = cos(α) · x - sin(α) · y

y' = sin(α) · x - cos(α) · y

When α is 0 degrees, it's the identity matrix. When α is 180 degrees, the transform matrix is as follows:

| –1 0 0 | | 0 –1 0 | | 0 0 1 |

A 180 degree rotation is equivalent to flipping an object horizontally and vertically, which is also accomplished by setting scale factors of –1.

All these types of transforms are classified as *affine* transforms. Affine transforms never involve the third column of the matrix, which remains at the default values of 0, 0, and 1. The article Non-Affine Transforms discusses non-affine transforms.

# Matrix Multiplication

One big advantage with using the transform matrix is that composite transforms can be obtained by matrix multiplication, which is often referred to in the SkiaSharp documentation as *concatenation*. Many of the transform-related methods in `SKCanvas`

refer to "pre-concatenation" or "pre-concat." This refers to the order of multiplication, which is important because matrix multiplication is not commutative.

For example, the documentation for the `Translate`

method says that it "Pre-concats the current matrix with the specified translation,"
while the documentation for the `Scale`

method says that it "Pre-concats the current matrix with the specified scale."

This means that the transform specified by the method call is the multiplier (the left-hand operand) and the current transform matrix is the multiplicand (the right-hand operand).

Suppose that `Translate`

is called followed by `Scale`

:

```
canvas.Translate(tx, ty);
canvas.Scale(sx, sy);
```

The `Scale`

transform is multiplied by the `Translate`

transform for the composite transform matrix:

| sx 0 0 | | 1 0 0 | | sx 0 0 | | 0 sy 0 | × | 0 1 0 | = | 0 sy 0 | | 0 0 1 | | tx ty 1 | | tx ty 1 |

`Scale`

could be called before `Translate`

like this:

```
canvas.Scale(sx, sy);
canvas.Translate(tx, ty);
```

In that case, the order of the multiplication is reversed, and the scaling factors are effectively applied to the translation factors:

| 1 0 0 | | sx 0 0 | | sx 0 0 | | 0 1 0 | × | 0 sy 0 | = | 0 sy 0 | | tx ty 1 | | 0 0 1 | | tx·sx ty·sy 1 |

Here is the `Scale`

method with a pivot point:

`canvas.Scale(sx, sy, px, py);`

This is equivalent to the following translate and scale calls:

```
canvas.Translate(px, py);
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);
```

The three transform matrices are multiplied in reverse order from how the methods appear in code:

| 1 0 0 | | sx 0 0 | | 1 0 0 | | sx 0 0 | | 0 1 0 | × | 0 sy 0 | × | 0 1 0 | = | 0 sy 0 | | –px –py 1 | | 0 0 1 | | px py 1 | | px–px·sx py–py·sy 1 |

## The SKMatrix Structure

The `SKMatrix`

structure defines nine read/write properties of type `float`

corresponding to the nine cells of the transform matrix:

│ ScaleX SkewY Persp0 │ │ SkewX ScaleY Persp1 │ │ TransX TransY Persp2 │

`SKMatrix`

also defines a property named `Values`

of type `float[]`

. This property can be used to set or obtain the nine values in one shot in the order `ScaleX`

, `SkewX`

, `TransX`

, `SkewY`

, `ScaleY`

, `TransY`

, `Persp0`

, `Persp1`

, and `Persp2`

.

The `Persp0`

, `Persp1`

, and `Persp2`

cells are discussed in the article, Non-Affine Transforms. If these cells have their default values of 0, 0, and 1, then the transform is multiplied by a coordinate point like this:

│ ScaleX SkewY 0 │ | x y 1 | × │ SkewX ScaleY 0 │ = | x' y' z' | │ TransX TransY 1 │

x' = ScaleX · x + SkewX · y + TransX

y' = SkewX · x + ScaleY · y + TransY

z' = 1

This is the complete two-dimensional affine transform. The affine transform preserves parallel lines, which means that a rectangle is never transformed into anything other than a parallelogram.

The `SKMatrix`

structure defines several static methods to create `SKMatrix`

values. These all return `SKMatrix`

values:

`MakeTranslation`

`MakeScale`

`MakeScale`

with a pivot point`MakeRotation`

for an angle in radians`MakeRotation`

for an angle in radians with a pivot point`MakeRotationDegrees`

`MakeRotationDegrees`

with a pivot point`MakeSkew`

`SKMatrix`

also defines several static methods that concatenate two matrices, which means to multiply them. These methods are named `Concat`

, `PostConcat`

, and `PreConcat`

, and there are two versions of each. These methods have no return values; instead, they reference existing `SKMatrix`

values through `ref`

arguments. In the following example, `A`

, `B`

, and `R`

(for "result") are all `SKMatrix`

values.

The two `Concat`

methods are called like this:

```
SKMatrix.Concat(ref R, A, B);
SKMatrix.Concat(ref R, ref A, ref B);
```

These perform the following multiplication:

R = B × A

The other methods have only two parameters. The first parameter is modified, and on return from the method call, contains the product of the two matrices. The two `PostConcat`

methods are called like this:

```
SKMatrix.PostConcat(ref A, B);
SKMatrix.PostConcat(ref A, ref B);
```

These calls perform the following operation:

A = A × B

The two `PreConcat`

methods are similar:

```
SKMatrix.PreConcat(ref A, B);
SKMatrix.PreConcat(ref A, ref B);
```

These calls perform the following operation:

A = B × A

The versions of these method calls with all `ref`

arguments are slightly more efficient in calling the underlying implementations, but it might be confusing to someone reading your code and assuming that anything with a `ref`

argument is modified by the method. Moreover, it's often convenient to pass an argument that is a result of one of the `Make`

methods, for example:

```
SKMatrix result;
SKMatrix.Concat(result, SKMatrix.MakeTranslation(100, 100),
SKMatrix.MakeScale(3, 3));
```

This creates the following matrix:

│ 3 0 0 │ │ 0 3 0 │ │ 100 100 1 │

This is the scale transform multiplied by the translate transform. In this particular case, the `SKMatrix`

structure provides a shortcut with a method named `SetScaleTranslate`

:

```
SKMatrix R = new SKMatrix();
R.SetScaleTranslate(3, 3, 100, 100);
```

This is one of the few times when it's safe to use the `SKMatrix`

constructor. The `SetScaleTranslate`

method sets all nine cells of the matrix. It is also safe to use the `SKMatrix`

constructor with the static `Rotate`

and `RotateDegrees`

methods:

```
SKMatrix R = new SKMatrix();
SKMatrix.Rotate(ref R, radians);
SKMatrix.Rotate(ref R, radians, px, py);
SKMatrix.RotateDegrees(ref R, degrees);
SKMatrix.RotateDegrees(ref R, degrees, px, py);
```

These methods do *not* concatenate a rotate transform to an existing transform. The methods set all the cells of the matrix. They are functionally identical to the `MakeRotation`

and `MakeRotationDegrees`

methods except that they don't instantiate the `SKMatrix`

value.

Suppose you have an `SKPath`

object that you want to display, but you would prefer that it have a somewhat different orientation, or a different center point. You can modify all the coordinates of that path by calling the `Transform`

method of `SKPath`

with an `SKMatrix`

argument. The **Path Transform** page demonstrates how to do this. The `PathTransform`

class references the `HendecagramPath`

object in a field but uses its constructor to apply a transform to that path:

```
public class PathTransformPage : ContentPage
{
SKPath transformedPath = HendecagramArrayPage.HendecagramPath;
public PathTransformPage()
{
Title = "Path Transform";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
SKMatrix matrix = SKMatrix.MakeScale(3, 3);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(360f / 22));
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeTranslation(300, 300));
transformedPath.Transform(matrix);
}
...
}
```

The `HendecagramPath`

object has a center at (0, 0), and the eleven points of the star extend outward from that center by 100 units in all directions. This means that the path has both positive and negative coordinates. The **Path Transform** page prefers to work with a star three times as large, and with all positive coordinates. Moreover, it doesn't want one point of the star to point straight up. It wants instead for one point of the star to point straight down. (Because the star has eleven points, it can't have both.) This requires rotating the star by 360 degrees divided by 22.

The constructor builds an `SKMatrix`

object from three separate transforms using the `PostConcat`

method with the following pattern, where A, B, and C are instances of `SKMatrix`

:

```
SKMatrix matrix = A;
SKMatrix.PostConcat(ref A, B);
SKMatrix.PostConcat(ref A, C);
```

This is a series of successive multiplications, so the result is as follows:

A × B × C

The consecutive multiplications aid in understanding what each transform does. The scale transform increases the size of the path coordinates by a factor of 3, so the coordinates range from –300 to 300. The rotate transform rotates the star around its origin. The translate transform then shifts it by 300 pixels right and down, so all the coordinates become positive.

There are other sequences that produce the same matrix. Here's another one:

```
SKMatrix matrix = SKMatrix.MakeRotationDegrees(360f / 22);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeTranslation(100, 100));
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeScale(3, 3));
```

This rotates the path around its center first, and then translates it 100 pixels to the right and down so all the coordinates are positive. The star is then increased in size relative to its new upper-left corner, which is the point (0, 0).

The `PaintSurface`

handler can simply render this path:

```
public class PathTransformPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Magenta;
paint.StrokeWidth = 5;
canvas.DrawPath(transformedPath, paint);
}
}
}
```

It appears in the upper-left corner of the canvas:

The constructor of this program applies the matrix to the path with the following call:

`transformedPath.Transform(matrix);`

The path does *not* retain this matrix as a property. Instead, it applies the transform to all of the coordinates of the path. If `Transform`

is called again, the transform is applied again, and the only way you can go back is by applying another matrix that undoes the transform. Fortunately, the `SKMatrix`

structure defines a `TryInverse`

method that obtains the matrix that reverses a given matrix:

```
SKMatrix inverse;
bool success = matrix.TryInverse(out inverse);
```

The method is called `TryInverse`

because not all matrices are invertible, but a non-invertible matrix is not likely to be used for a graphics transform.

You can also apply a matrix transform to an `SKPoint`

value, an array of points, an `SKRect`

, or even just a single number within your program. The `SKMatrix`

structure supports these operations with a collection of methods that begin with the word `Map`

, such as these:

```
SKPoint transformedPoint = matrix.MapPoint(point);
SKPoint transformedPoint = matrix.MapPoint(x, y);
SKPoint[] transformedPoints = matrix.MapPoints(pointArray);
float transformedValue = matrix.MapRadius(floatValue);
SKRect transformedRect = matrix.MapRect(rect);
```

If you use that last method, keep in mind that the `SKRect`

structure is not capable of representing a rotated rectangle. The method only makes sense for an `SKMatrix`

value representing translation and scaling.

## Interactive Experimentation

One way to get a feel for the affine transform is by interactively moving three corners of a bitmap around the screen and seeing what transform results. This is the idea behind the **Show Affine Matrix** page. This page requires two other classes that are also used in other demonstrations:

The `TouchPoint`

class displays a translucent circle that can be dragged around the screen. `TouchPoint`

requires that an `SKCanvasView`

or an element that is a parent of an `SKCanvasView`

have the `TouchEffect`

attached. Set the `Capture`

property to `true`

. In the `TouchAction`

event handler, the program must call the `ProcessTouchEvent`

method in `TouchPoint`

for each `TouchPoint`

instance. The method returns `true`

if the touch event resulted in the touch point moving. Also, the `PaintSurface`

handler must call the `Paint`

method in each `TouchPoint`

instance, passing to it the `SKCanvas`

object.

`TouchPoint`

demonstrates a common way that a SkiaSharp visual can be encapsulated in a separate class. The class can define properties for specifying characteristics of the visual, and a method named `Paint`

with an `SKCanvas`

argument can render it.

The `Center`

property of `TouchPoint`

indicates the location of the object. This property can be set to initialize the location; the property changes when the user drags the circle around the canvas.

The **Show Affine Matrix Page** also requires the `MatrixDisplay`

class. This class displays the cells of an `SKMatrix`

object. It has two public methods: `Measure`

to obtain the dimensions of the rendered matrix, and `Paint`

to display it. The class contains a `MatrixPaint`

property of type `SKPaint`

that can be replaced for a different font size or color.

The **ShowAffineMatrixPage.xaml** file instantiates the `SKCanvasView`

and attaches a `TouchEffect`

. The **ShowAffineMatrixPage.xaml.cs** code-behind file creates three `TouchPoint`

objects and then sets them to positions corresponding to three corners of a bitmap that it loads from an embedded resource:

```
public partial class ShowAffineMatrixPage : ContentPage
{
SKMatrix matrix;
SKBitmap bitmap;
SKSize bitmapSize;
TouchPoint[] touchPoints = new TouchPoint[3];
MatrixDisplay matrixDisplay = new MatrixDisplay();
public ShowAffineMatrixPage()
{
InitializeComponent();
string resourceID = "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
using (SKManagedStream skStream = new SKManagedStream(stream))
{
bitmap = SKBitmap.Decode(skStream);
}
touchPoints[0] = new TouchPoint(100, 100); // upper-left corner
touchPoints[1] = new TouchPoint(bitmap.Width + 100, 100); // upper-right corner
touchPoints[2] = new TouchPoint(100, bitmap.Height + 100); // lower-left corner
bitmapSize = new SKSize(bitmap.Width, bitmap.Height);
matrix = ComputeMatrix(bitmapSize, touchPoints[0].Center,
touchPoints[1].Center,
touchPoints[2].Center);
}
...
}
```

An affine matrix is uniquely defined by three points. The three `TouchPoint`

objects correspond to the upper-left, upper-right, and lower-left corners of the bitmap. Because an affine matrix is only capable of transforming a rectangle into a parallelogram, the fourth point is implied by the other three. The constructor concludes with a call to `ComputeMatrix`

, which calculates the cells of an `SKMatrix`

object from these three points.

The `TouchAction`

handler calls the `ProcessTouchEvent`

method of each `TouchPoint`

. The `scale`

value converts from Xamarin.Forms coordinates to pixels:

```
public partial class ShowAffineMatrixPage : ContentPage
{
...
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
bool touchPointMoved = false;
foreach (TouchPoint touchPoint in touchPoints)
{
float scale = canvasView.CanvasSize.Width / (float)canvasView.Width;
SKPoint point = new SKPoint(scale * (float)args.Location.X,
scale * (float)args.Location.Y);
touchPointMoved |= touchPoint.ProcessTouchEvent(args.Id, args.Type, point);
}
if (touchPointMoved)
{
matrix = ComputeMatrix(bitmapSize, touchPoints[0].Center,
touchPoints[1].Center,
touchPoints[2].Center);
canvasView.InvalidateSurface();
}
}
...
}
```

If any `TouchPoint`

has moved, then the method calls `ComputeMatrix`

again and invalidates the surface.

The `ComputeMatrix`

method determines the matrix implied by those three points. The matrix called `A`

transforms a one-pixel square rectangle into a parallelogram based on the three points, while the scale transform called `S`

scales the bitmap to a one-pixel square rectangle. The composite matrix is `S`

× `A`

:

```
public partial class ShowAffineMatrixPage : ContentPage
{
...
static SKMatrix ComputeMatrix(SKSize size, SKPoint ptUL, SKPoint ptUR, SKPoint ptLL)
{
// Scale transform
SKMatrix S = SKMatrix.MakeScale(1 / size.Width, 1 / size.Height);
// Affine transform
SKMatrix A = new SKMatrix
{
ScaleX = ptUR.X - ptUL.X,
SkewY = ptUR.Y - ptUL.Y,
SkewX = ptLL.X - ptUL.X,
ScaleY = ptLL.Y - ptUL.Y,
TransX = ptUL.X,
TransY = ptUL.Y,
Persp2 = 1
};
SKMatrix result;
SKMatrix.Concat(ref result, A, S);
return result;
}
...
}
```

Finally, the `PaintSurface`

method renders the bitmap based on that matrix, displays the matrix at the bottom of the screen, and renders the touch points at the three corners of the bitmap:

```
public partial class ShowAffineMatrixPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Display the bitmap using the matrix
canvas.Save();
canvas.SetMatrix(matrix);
canvas.DrawBitmap(bitmap, 0, 0);
canvas.Restore();
// Display the matrix in the lower-right corner
SKSize matrixSize = matrixDisplay.Measure(matrix);
matrixDisplay.Paint(canvas, matrix,
new SKPoint(info.Width - matrixSize.Width,
info.Height - matrixSize.Height));
// Display the touchpoints
foreach (TouchPoint touchPoint in touchPoints)
{
touchPoint.Paint(canvas);
}
}
}
```

The iOS screen below shows the bitmap when the page is first loaded, while the two other screens show it after some manipulation:

Although it seems as if the touch points drag the corners of the bitmap, that's only an illusion. The matrix calculated from the touch points transforms the bitmap so that the corners coincide with the touch points.

It is more natural for users to move, resize, and rotate bitmaps not by dragging the corners, but by using one or two fingers directly on the object to drag, pinch, and rotate. This is covered in the next article Touch Manipulation.

## The Reason for the 3-by-3 Matrix

It might be expected that a two-dimensional graphics system would require only a 2-by-2 transform matrix:

│ ScaleX SkewY │ | x y | × │ │ = | x' y' | │ SkewX ScaleY │

This works for scaling, rotation, and even skewing, but it is not capable of the most basic of transforms, which is translation.

The problem is that the 2-by-2 matrix represents a *linear* transform in two dimensions. A linear transform preserves some basic arithmetic operations, but one of the implications is that a linear transform never alters the point (0, 0). A linear transform makes translation impossible.

In three dimensions, a linear transform matrix looks like this:

│ ScaleX SkewYX SkewZX │ | x y z | × │ SkewXY ScaleY SkewZY │ = | x' y' z' | │ SkewXZ SkewYZ ScaleZ │

The cell labeled `SkewXY`

means that the value skews the X coordinate based on values of Y; the cell `SkewXZ`

means that the value skews the X coordinate based on values of Z; and values skew similarly for the other `Skew`

cells.

It's possible to restrict this 3D transform matrix to a two-dimensional plane by setting `SkewZX`

and `SkewZY`

to 0, and `ScaleZ`

to 1:

│ ScaleX SkewYX 0 │ | x y z | × │ SkewXY ScaleY 0 │ = | x' y' z' | │ SkewXZ SkewYZ 1 │

If the two-dimensional graphics are drawn entirely on the plane in 3D space where Z equals 1, the transform multiplication looks like this:

│ ScaleX SkewYX 0 │ | x y 1 | × │ SkewXY ScaleY 0 │ = | x' y' 1 | │ SkewXZ SkewYZ 1 │

Everything stays on the two-dimensional plane where Z equals 1, but the `SkewXZ`

and `SkewYZ`

cells effectively become two-dimensional translation factors.

This is how a three-dimensional linear transform serves as a two-dimensional non-linear transform. (By analogy, transforms in 3D graphics are based on a 4-by-4 matrix.)

The `SKMatrix`

structure in SkiaSharp defines properties for that third row:

│ ScaleX SkewY Persp0 │ | x y 1 | × │ SkewX ScaleY Persp1 │ = | x' y' z` | │ TransX TransY Persp2 │

Non-zero values of `Persp0`

and `Persp1`

result in transforms that move objects off the two-dimensional plane where Z equals 1. What happens when those objects are moved back to that plane is covered in the article on Non-Affine Transforms.

Let us know how you feel about this

Translation Quality

0/250

# Xamarin Workbook

If it's not already installed, install the Xamarin Workbooks app first. The workbook file should download automatically, but if it doesn't, just click to start the workbook download manually.