Click here to Skip to main content
Click here to Skip to main content

Flattening a TransformGroup

By , 22 Oct 2012
Rate this:
Please Sign up or sign in to vote.

Sometimes it would be nice to flatten (merge) multiple transforms into a single transform.  Therefore, I thought it would be fun to figure out how to do this and share it with others.

Posts in this series:

A transform consists of the following operations, performed in the following order:

  1. Translate
  2. Rotate about a center point
  3. Scale

Note: This task is difficult enough when flattening transforms with center points of 0,0.  Having to account for transformations with center points not at the origin is just too much.  Therefore, it is assumed that all transformations have their center points zeroed out as described in Zeroing the Center of a CompositeTransform.

Cp

In the above render, the red rectangle is transformed Tx=100, Ty=30, and R=45. Remember that all center points are 0,0.  The green rectangle is positioned using a group of two transforms, Tx=100, Ty=30, R=45 and then Tx=120, Ty=-80, and R=-60.  The goal of this post is to come up with an equivalent single transformation that results in the same location as the green rectangle.  The answer is the transformation Tx=241, Ty=58 and R=-15, and rendered as the blue border.

FlattenExample.xaml:
<Page
    x:Class="PanViewDemoApp.FlattenExample"
    IsTabStop="false"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PanViewDemoApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontSize="20"
    Foreground="Black">
    <Canvas>
        <Border
            Height="120"
            Width="200"
            Background="#40FF0000">
            <Border.RenderTransform>
                <CompositeTransform
                    x:Name="J"
                    TranslateX="100"
                    TranslateY="30"
                    Rotation="45" />
            </Border.RenderTransform>
            <TextBlock
                Foreground="Red"
                Margin="5">Tx=100 Ty=30 R=45</TextBlock>
        </Border>
        <Border
            Height="120"
            Width="200"
            Background="#4000FF00">
            <Border.RenderTransform>
                <TransformGroup>
                    <CompositeTransform
                        x:Name="K"
                        TranslateX="120"
                        TranslateY="-80"
                        Rotation="-60" />
                    <CompositeTransform
                        TranslateX="100"
                        TranslateY="30"
                        Rotation="45" />
                </TransformGroup>
            </Border.RenderTransform>
            <TextBlock
                Foreground="Green"
                Margin="3">Tx=100 Ty=30 R=45<LineBreak />Tx=120 Ty=-80 R=-60</TextBlock>
        </Border>
        <Border
            Height="120"
            Width="200"
            BorderBrush="Blue"
            BorderThickness="3">
            <Border.RenderTransform>
                <CompositeTransform
                    x:Name="L"
                    TranslateX="241"
                    TranslateY="58"
                    Rotation="-15" />
            </Border.RenderTransform>
            <TextBlock
                Foreground="Blue"
                Margin="3"
                VerticalAlignment="Bottom">Tx=241 Ty=58 R=-15</TextBlock>
        </Border>
    </Canvas>
</Page>

Above is the XAML that created the rendering.

From my perspective, the order of transforms specified in the TransformGroup are applied in reverse order. I have no idea whether this is a bug or by design.  In any case, we have to work with what we have.

Now that we named the transformations, let me restate the goal of this post in different terms: Our goal is to modify the transform J so that it is equivalent to the group of the transformations J and K.

<CompositeTransform
   x:Name="J"
   TranslateX="100"
   TranslateY="30"
   Rotation="45" />
      
<CompositeTransform
   x:Name="K"
   TranslateX="120"
   TranslateY="-80"
   Rotation="-60" />

The easy part is the rotation.  Rotations are simply additive.  Therefore:

  • j.Rotation += k.Rotation;

The harder part is applying the translation, since translations in the x and y direction of transform K are now rotated 45° (because transform J has already been applied).  The easiest way to apply the translation is to first convert the translations of K from rectangular (x,y) to polar form.  In this example, moving 120x,-80y is the same as moving 144.2 pixels in a direction of -33°.  Therefore, we need to translate 144.2 pixels in a direction of (45-33)° or 12°.

By the way, it is important to note that a move in the positive y direction is upwards in trigonometry and downward in Windows.  A rotation in a positive direction is counter-clockwise in trigonometry and clockwise in Windows.  Therefore it is easy to make direction mistakes when using trigonometry functions in Windows.  Yet, these two inconsistencies can actually cancel each other out very nicely.  Just keep it in mind when reading the code below.  Oh, and one more thing: rotations in Windows are specified in degrees and angles used in trigonometric functions are all specified in radians.

The first number, 144.2, is computed as:

  • var d = Math.Sqrt(k.TranslateX * k.TranslateX + k.TranslateY * k.TranslateY);

The second number, -33°, is computed as:

  • var a = Math.Atan2(k.TranslateY, k.TranslateX);

We now need to add 45° to the angle, making sure to convert 45° to radians first.

  • a += j.Rotation *
  • Math.PI / 180;

Now that we have the final angle for K, we need to break it back down into screen x and y distances for K:

  • var x = Math.Cos(a) * d;
  • var y = Math.Sin(a) * d;

Finally, we can add those translations back into J:

  • j.TranslateX += x;
  • j.TranslateY += y;

Here is the C# code that performs the flattening of two transformations:

TransformExtensions.cs
static void InternalAppend(ICompositeTransform j, ICompositeTransform k)
{
    InternalZeroCenterPoint(j);
    InternalZeroCenterPoint(k);
    var d = Math.Sqrt(k.TranslateX * k.TranslateX + k.TranslateY * k.TranslateY);
    var a = Math.Atan2(k.TranslateY, k.TranslateX);
    a += j.Rotation * Math.PI / 180;
    var x = Math.Cos(a) * d;
    var y = Math.Sin(a) * d;
    j.Rotation += k.Rotation;
    j.TranslateX += x;
    j.TranslateY += y;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

John Michael Hauck
Software Developer (Senior) LECO Corporation
United States United States
John Hauck has been developing software professionally since 1981, and focused on Windows-based development since 1988. For the past 17 years John has been working at LECO, a scientific laboratory instrument company, where he manages software development. John also served as the manager of software development at Zenith Data Systems, as the Vice President of software development at TechSmith, as the lead medical records developer at Instrument Makar, as the MSU student who developed the time and attendance system for Dart container, and as the high school kid who wrote the manufacturing control system at Wohlert. John loves the Lord, his wife, their three kids, and sailing on Lake Michigan.
Follow on   Twitter

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web04 | 2.8.140415.2 | Last Updated 22 Oct 2012
Article Copyright 2012 by John Michael Hauck
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid