In most – if not all – graphical systems, it is possible to apply some transformation on graphical elements in order to render them in a certain way. These transformations can often be combined in a particular order to yield a new transformation.

Graphical transformations

In most – if not all – graphical systems, it is possible to apply some transformation on graphical elements in order to render them in a certain way. These transformations can often be combined in a particular order to yield a new transformation.

We often notice however, that programmers perceive it as hard to correctly combine these transformations. Graphical transformations are not commutative, so the order of transformations matters. In addition, the actual correct order depends on the way that a graphical systems deals with them. One cannot just always interchange a particular order between various graphical systems, and this is not always explained very well. This typically leads to a lot of trial-and-error before the correct combination of transformations is found.

Below, we will have a look at the graphical transformation systems in PDF, WPF and PDFKit.NET.

Graphical Transformations in PDF

In PDF, as well as in Postscript, is is possible to transform graphical objects via matrix transformations. Here however one does not really transform the objects, but the coordinate system in which they get drawn.

This may seem weird at first sight. Why transform the coordinate system, and not the objects themselves? The main reason for this is that this allows one to draw a group of objects using the same transformation, without having to specify the transformation for each and every object. Within the group, all objects can be drawn relatively to each other as if they are drawn in an untransformed world. And by applying a coordinate system transformation upfront, e.g. a scaling transformation, the entire scene gets scaled.

The order of the transformations is important. The PDF reference manual shows the difference as follows:

In general, transformations in PDF should be done in the following order:

1. Translate

2. Rotate

3. Scale or Skew

Transformation order in WPF

WPF also has transformations. MSDN however is not very clear about combined transformations. It suggests that WPF also transforms the coordinate system, and it contains some samples, but there is no discussion what happens exactly. See for example:https://msdn.microsoft.com/en-us/library/system.windows.media.translatetransform(v=vs.110).aspx

What we see however, is that XAML does not transform the coordinate system. Take for example the following XAML code.

<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Canvas Width="200" Height="200"> <Rectangle Canvas.Left="0" Canvas.Top="0" Width="200" Height="200" Stroke="Black" Opacity="1.0"/> <Rectangle Canvas.Left="0" Canvas.Top="0" Width="50" Height="50" Stroke="RoyalBlue" Opacity="1.0"> <Rectangle.RenderTransform> <TransformGroup> <TranslateTransform X="50" Y="75"/> <ScaleTransform ScaleX="3"/> </TransformGroup> </Rectangle.RenderTransform> </Rectangle> </Canvas> </Window>

This has the following effect. Note that the black rectangle has a size of 200 x 200 and that the blue rectangle has been translated much further to the right than 50 units. It has been translated by 150 units to be exact.

If however, we reverse the transformations, we get what we expect:

<TransformGroup> <ScaleTransform ScaleX="3"/> <TranslateTransform X="50" Y="75"/> </TransformGroup>

What we see here is that WPF applies both transformations in sequence on the object, without changing the coordinate system in which the object resides. In fact, if we want to rotate the object in its current location and without distorting, we will need to do this after scaling an before translating, which is exactly the opposite of the correct order in PDF.

<TransformGroup> <ScaleTransform ScaleX="3"/> <RotateTransform Angle="-30"/> <TranslateTransform X="50" Y="75"/> </TransformGroup>

If we rotate before scaling, the object gets distorted (skewed) because scaling then takes places on the rotated object along the untransformed, horizontal x-axis:

Whereas, if we rotate at the end, after translating, the object gets placed at a different location because the center of the rotation is still at the original origin of the coordinate system the top left of the black rectangle.

This means that the information in MSDN about these transformations is actually incorrect.There is however a reason to do things differently in WPF.

In PDF we do not really have graphical objects with Transform properties. We only have graphical operations that operate in a certain environment, of which the transformed coordinate system is just one of the aspects. The main reason to do this is compactness. By having an environment with all sorts of graphical context, the operations themselves can be kept simple and short.

In WPF however, we have an object-oriented system where we want all objects to be as independent as possible, and where – consequently – each object has its own transformation, next to properties like color and transparency, instead of these being part of some environment. And in that case it makes sense to not have transformations change the coordinate system but just the object itself.

This also has the advantage that transformations can be combined in a more natural order, and programmers can reason within a fixed coordinate system.

Shape Transformations in PDFKit.NET

In PDFKit.NET one can draw with shapes, and just as in WPF, each shape is a complete graphical element that has its own transformation property. This means that the correct order of doing transformations in PDFKit.NET is similar to WPF:

1. Scale or Skew

2. Rotate

3. Translate

Below we have shown the effect of applying these transformations in that particular order:

Pen pen = new Pen(RgbColor.Black, 1); FreeHandShape charAsShape = new FreeHandShape { Pen = pen, Brush = null }; charAsShape.Paths.AddRange(Font.TimesRoman.CreatePaths('n', 100)); charAsShape.Transform = new TransformCollection { new ScaleTransform(3,1), new RotateTransform(-30), new TranslateTransform(50, 75) };

Original:

Step 1: scaling by a factor 3 in the x direction:

Step 2: Rotation by 30 degrees counterclockwise:

Step 3: Translation by 50 units in the x direction and 75 units in the y direction: