Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF
Article

XAML polygon transformation tool

Rate me:
Please Sign up or sign in to vote.
4.77/5 (12 votes)
26 Aug 2008CPOL3 min read 34.1K   594   18   2
A tool to automate flipping, shifting, rotating, and scaling polygons and polylines.

Image 1

Introduction

Recently, while working on an application, I found myself needing a small polygon for use in a toolbar button. I considered using a bitmap effect to get the desired appearance, but was unsatisfied. If it was going to be necessary to put the necessary geometry into the XAML anyway, why shouldn't one put the exact XAML that was needed? Arithmetic is the problem. One of the major reasons computers are programmed is to avoid calculating numbers on spreadsheets or with a calculator.

Using the code

Hopefully, this code will be useful as is, even if one does not take the time to examine the source. Paste your polygon into the left hand side bottom panel. If there are no XAML parsing problems, a preview will appear above it. Select some options, and press the Transform button. The transformed XAML will appear on the right, with a preview.

How it works

The code performs the following steps:

  • Extract the string containing the points, from the original XAML
  • Convert the points to a matrix
  • Perform the chosen calculations on the matrix
  • Preview the result

Extract the string containing the points, from the original XAML

Two Regular Expressions are used to extract the points in the polygon/line.

C#
string  rString = @"(?x:
                    (?<PreNums><(?<Type>Polygon|Polyline).*Points\s*=\s*'\s*) 
                    (?<Numbers>.*?\s*)
                    (?<PostNums>'.*)
                    )".Replace("'", "\""); 
//PreNums  contains the strign before the points 
//Points   contains the point locations
//PostNums contains the string after the points

A second Regular Expression is used to pull out the list of coordinates.

C#
string rMatrixString = @"(?x:
                         (? <x>\d+)
                          \s*,\s*
                         (? <y>\d+))";
//x contains the x coordinate
//y contains the y coordinate

It is worth noting that VS2008 is very loose in handling whitespace within the Points value. All the following works without problem:

  • Points = "1,2 3,4"
  • Points = "1, 2 3 ,4"
  • Points = "001,2 3, 0004"

This requires extra care in testing. The class MatrixConverter has a method FormatString that reduces the Points string to a canonical format.

Convert the points to a matrix

The string representing the points is converted into a matrix by way of the MatrixConverter.

C#
public static Matrix Convert(string value)
{
    value = FormatString(value);
    string[] lines = value.Split(new string[] { " " }, StringSplitOptions.None);
    Matrix m = new Matrix(lines[0].Split(new string[] { "," }, 
                          StringSplitOptions.None).Length);
    try
    {
        foreach (string str in lines)
        {
            string[] line = str.Split(new string[] { "," }, 
                            StringSplitOptions.None);
            Double[] newRow = new Double[m.Width];
            for (int iCol = 0; iCol < m.Width; iCol++)
            {
                newRow[iCol] = System.Convert.ToDouble(line[iCol]);
            }
            m.AddRow(newRow);
        }
    }
    catch (Exception ex)
    {
        throw new Exception("NonNumeric Input");
    }
    return m;
}

Perform the chosen calculations on the matrix

The transformations are performed in the following order: flip, rotate, scale. Also, all of the transformations implement the ITransformation interface which provides a Transform() method.

Flip transformation

The Transform method for FlipTransformation is as follows:

C#
public void Transform()
{
    double axis = 0;
    if (mFlipX)
    {
        if (mIsXAxisCentered)// Is the x Axis centered
        {
            double min = mMatrix.Min(1);
            double max = mMatrix.Max(1);
            axis = (min + max) / 2;
        }
        else
        {
            axis = mXAxis;
        }
        FlipColumn(1, axis);

    }
    if (mFlipY)
    {
        if (mIsYAxisCentered) // Is the Y axis centered
        {
            double min = mMatrix.Min(0);
            double max = mMatrix.Max(0);
            axis = (min + max) / 2;
        }
        else
        {
            axis = mYAxis;
        }
        FlipColumn(0, axis);
    }
}

public void FlipColumn(int col, double axis)
{
    mMatrix.Subtract(axis, col);
    mMatrix.Multiply(-1, col);
    mMatrix.Add(axis, col);

}

One moves the points so that the axis one is flipping across is 0, multiplies the points by -1, and then moves them back.

Rotational transformation

The Transform method for RotateTransformation is as follows:

C#
public void Transform()
{
    double xMid =0;
    double yMid = 0 ;

    if (mIsRotatedAboutCenter)
    {
        double[] MinArray = mMatrix.MinArray();
        double[] MaxArray = mMatrix.MaxArray();
        xMid = (MinArray[0] + MaxArray[0]) / 2;
        yMid = (MinArray[1] + MaxArray[1]) / 2;

        mMatrix.Subtract(xMid,0);
        mMatrix.Subtract(yMid,1);

    }
    switch (mRotationalUnit)
    {
        case ERotationalUnit.Degrees:
            RotateDegreesAroundOrigin(mAngle);
            break;
        case ERotationalUnit.PiRadians:
            RotatePiRadiansAroundOrigin(mAngle);
            break;
        case ERotationalUnit.Radians:
            RotateRadiansAroundOrigin(mAngle);
            break;
    }

    if (mIsRotatedAboutCenter)
    {
        mMatrix.Add(xMid, 0);
        mMatrix.Add(yMid, 1);
    }

}
        
public void RotateRadiansAroundOrigin(double radians)
{
    Matrix mat = new Matrix(2);

    mat.AddRow(new Double[2] { Math.Cos(radians), Math.Sin(radians) });
    mat.AddRow(new Double[2] { -Math.Sin(radians), Math.Cos(radians) });

    mMatrix.MultiplyRows(mat);

}

First, the Matrix is translated so the the origin is 0 for both the X and Y coordinates. Then, it is multiplied by the standard 2Dd transformation matrix. If you want an explanation, you could look at this.

Linear transformation

The linear transformation is a bit more involved as there are a lot of options. The basic distinction is between confining the transformation to a bounding box, or directly choosing the amount that we want to have things shifted and scaled. In that case, we can decide if we want the scaling to only affect the dimensions, but not the top and the left values. The Transform method follows. Please look at the source code for the helper methods.

C#
public void Transform()
{
    if (mBoundingBox)
    {
        Double currentWidth = mMatrix.Max(0) - mMatrix.Min(0);
        Double currentHeight = mMatrix.Max(1) - mMatrix.Min(1);
        Double ScaleX = (Right - Left) / currentWidth;
        Double ScaleY = (Bottom - Top) / currentHeight;

        if (mUniformStretch)
        {
            mMatrix.Multiply(Math.Min(ScaleX, ScaleY));
        }
        else
        {
            mMatrix.Multiply(ScaleX, 0);
            mMatrix.Multiply(ScaleY, 1);
        }

        if (mTopLeftOrigin)
        {
            ShiftMinTo();
        }
        else
        {
            ShiftMinTo(Left, 0);
            ShiftMinTo(Top, 1);
        }
    }
    else
    {
        if (mTopLeftOrigin)
        {
            ShiftMinTo();
        }
        else
        {
            ShiftPlus(mShiftX, 0);
            ShiftPlus(mShiftY, 1);
        }

        if (mUniformStretch)
        {
            ScaleUniform(StretchX, mFixTopLeft);
        }
        else
        {
            mMatrix.Multiply(StretchX, 0);
            mMatrix.Multiply(StretchY, 1);
        }
    }

}

Preview the result

After the transformation is completed, the XAML is deserialized back into an object, and that object is set to the content of the previewTransformed. As this is only dealing with two classes, I opted for a simple preview.

C#
private static void PreviewXaml(ContentControl location, string xamlString )
{
    if (xamlString.IndexOf("winfx/2006/xaml/presentation", 0, 
                           StringComparison.CurrentCulture) == -1)
    {
        xamlString = xamlString.Replace("/>", 
          " xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" />");
    }
    if (xamlString.Contains("Polygon"))
    {
        Polygon poly = null;
        try
        {
            poly = CreatePoly<polygon />(xamlString);
        }
        catch (XamlParseException ex)
        {
            xamlString = Regex.Replace(xamlString, "Fill=\".*?\"", "Fill=\"White\"");
            xamlString = Regex.Replace(xamlString, "Stroke=\".*?\"", "Fill=\"Black\"");
            poly = CreatePoly<polygon />(xamlString);
        }
        location.Content = poly;
    }
    else
    {
        Polyline poly = null;
        try
        {
            poly = CreatePoly<polyline />(xamlString);
        }
        catch (XamlParseException ex)
        {
            xamlString = Regex.Replace(xamlString, "Fill=\".*?\"", "Fill=\"White\"");
            xamlString = Regex.Replace(xamlString, "Stroke=\".*?\"", "Fill=\"Black\"");
            poly = CreatePoly<polyline />(xamlString);
        }
        location.Content = poly;
    }
}

Points of interest

Note that you may have trouble getting your copy of VS2008 to catch XamlParseExceptions. It appears that it may refuse even when set to do so.

I hope this is useful and saves some time from the calculator.

License

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


Written By
Software Developer (Senior)
United States United States
Written software for what seems like forever. I'm currenly infatuated with WPF. Hopefully my affections are returned.

Comments and Discussions

 
QuestionWell done, but is this good for XAML? Pin
Drew Stainton26-Aug-08 17:47
Drew Stainton26-Aug-08 17:47 
The article and implementation look good but I can't help but wonder if this doesn't defeat the purpose of having a verbose system like XAML.

In this case, wouldn't it be more XAML'ish if your application added markup rather than actually computing new points? It would leave the markup readable and allows for easy changes later, which IMO are two of the benefits of XAML.

For example, to rotate your original polygon:
<polygon points="10,20 30,20 30,10 50,30 30,50 30,40 10,40" fill="White" stroke="Black" />

You application could add markup to do the rotation, leaving the points alone:
<polygon
Points="10,20 30,20 30,10 50,30 30,50 30,40 10,40"
Fill="White"
Stroke="Black"
<polygon.rendertransform>
<rotatetransform angle="45" />
</polygon.rendertransform>
/>

Obviously setting a centre point etc. is also required, which your app. could add as well.

Is there a reason to actually transform the points and replace them in the markup? (Speed, obviously is one, but if speed were a big concern I don't think XAML would be a good candidate in the first place).

Cheers,
Drew.
AnswerRe: Well done, but is this good for XAML? Pin
Leslie Godwin26-Aug-08 19:18
Leslie Godwin26-Aug-08 19:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.