您的位置:首页 > 其它

《Practical WPF Charts and Graphics 》翻译——之十一

2012-09-05 17:34 363 查看
矩阵操作

WPF里的Matrix结构提供了进行旋转,拉伸和平移的方法。它也实现了一些进行矩阵操作的方法。例如,你也可以使用Invert方法来得到一个可逆矩阵的逆。这个方法没有参数。Multiply方法将两个矩阵相乘并返回一个新矩阵作为结果。下面是一些矩阵操作常用的方法:

 Scale—添加一个指定的拉伸向量到Matrix结构
 ScaleAt—将矩阵关于指定点拉伸到指定大小
 Translate—添加一个指定偏移量的平移到到Matrix结构
 Rotate—应用一个关于原点的指定角度的旋转到Matrix结构
 RotateAt—关于指定的点旋转矩阵
 Skew—在X和Y方向增加一个制定的倾斜角度给Matrix结构
 Invert—求Matrix结构的逆矩阵
 Multiply—用另一个Matrix结构乘以一个Matrix结构
 Transform—通过矩阵变换特定的点,点数组,向量,或者向量数组

另外还有和Scale,Translation,Rotation和Skew相关的Prepend方法。缺省的方法是Append。Append和Prepend都决定了矩阵顺序。Append指定新的操作在前一个操作的后面应用;Prepend指定新操作在前一个操作的前面。

让我们考虑一个展示WPF里矩阵操作的例子。创建一个新的WPF windows应用程序项目,命名为Transformation2D。添加一个叫MatrixOperations的WPF window到项目中。这里是例子的XAML文件:

<Window x:Class="Transformation2D.MatrixOperations"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Matrix Operations" Height="250" Width="250">
<Grid>
<StackPanel>
<TextBlock Margin="10,10,5,5" Text="Original Matrix:"/>
<TextBlock x:Name="tbOriginal" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="Inverted Matrix:"/>
<TextBlock x:Name="tbInvert" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="Original Matrices:"/>
<TextBlock x:Name="tbM1M2" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="M1 x M2:"/>
<TextBlock x:Name="tbM12" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="M2 x M1:"/>
<TextBlock x:Name="tbM21" Margin="20,0,5,5"/>
</StackPanel>
</Grid>
</Window>


这个标记文件创建了一个使用TextBlocks显示结果的布局。矩阵操作的结果展示在相关的后台文件中,如下:

using System;
using System.Windows;
using System.Windows.Media;

namespace Transformation2D
{
public partial class MatrixOperations : Window
{
public MatrixOperations()
{
InitializeComponent();

// Invert matrix:
Matrix m = new Matrix(1, 2, 3, 4, 0, 0);
tbOriginal.Text = "(" +  m.ToString() +")";
m.Invert();
tbInvert.Text = "(" + m.ToString() + ")";

// Matrix multiplication:
Matrix m1 = new Matrix(1, 2, 3, 4, 0, 1);
Matrix m2 = new Matrix(0, 1, 2, 1, 0, 1);
Matrix m12 = Matrix.Multiply(m1, m2);
Matrix m21 = Matrix.Multiply(m2, m1);

tbM1M2.Text = "M1 = (" + m1.ToString() + "), " +
" M2 = (" + m2.ToString() + ")";
tbM12.Text = "(" + m12.ToString() + ")";
tbM21.Text = "(" + m21.ToString() + ")";
}
}
}


这个后台文件展示了矩阵求逆和相乘。特别地,他显示了矩阵相乘的结果依赖于矩阵操作的顺序。执行这个例子会得到如图2-5的输出。



图2-5.WPF矩阵操作的结果

首先,我们检查一下矩阵求逆方法,它求的是矩阵(1, 2, 3, 4, 0, 0)的逆。Matrix.Invert方法得到结果(-2, 1, 1.5, -0.5, 0, 0).这个可以轻易通过矩阵(1, 2, 3, 4, 0, 0) 乘以(-2, 1, 1.5, -0.5, 0, 0)来证明,结果应该等于单位矩阵(1, 0, 0, 1, 0, 0).。事实上:



这确实是单位矩阵,和期望的一样.。

接下来,我们考虑矩阵相乘。在代码里,你创建了两个矩阵m1 = (1, 2, 3, 4, 0, 1)和m2 = (0, 1, 2, 1, 0, 1)。首先用m1乘以m2返回结果到m12;然后用m2乘以m1将结果存入m21。注意如果用矩阵M1乘以m2,结果是存在m12(原文为m1,是不是错了)里。你可以从图2-5看出M12=(4, 3, 8, 7, 2, 2)。事实上:



对于M21=m2×m1,你将期望得到下面的结果:



这和图中显示的(3, 4, 5, 8, 3, 5) 是一致的。

矩阵变换

在前面部分提到,WPF里的矩阵结构也提供了选择,拉伸,和平移矩阵的方法。

你可以使用Rotate和RotateAt方法去选择矩阵。Rotate方法以一个指定的角度选择矩阵。这个方法只有一个参数:一个双精度值用来指定角度。RotateAt方法在你需要改变旋转中心的时候会有用处。它的第一个参数是角度,第二个和第三个参数(都是双精度类型)指定旋转中心。

我们用一个例子来说明WPF里的基本矩阵变换(平移,拉伸,旋转和倾斜)。添加一个新的WPF Window到Transformation2D项目中,命名为MatrixTransforms.下面是例子的XAML文件:

<Window x:Class="Transformation2D.MatrixTransforms"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Matrix Transforms" Height="450" Width="270">
<StackPanel>
<TextBlock Margin="10,10,5,5" Text="Original Matrix:"/>
<TextBlock Name="tbOriginal" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="Scale:"/>
<TextBlock Name="tbScale" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="Scale - Prepend:"/>
<TextBlock Name="tbScalePrepend" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="Translation:"/>
<TextBlock Name="tbTranslate" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="Translation – Prepend:"/>
<TextBlock Name="tbTranslatePrepend" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="Rotation:"/>
<TextBlock Name="tbRotate" Margin="20,0,5,5" TextWrapping="Wrap"/>
<TextBlock Margin="10,0,5,5" Text="Rotation – Prepend:"/>
<TextBlock Name="tbRotatePrepend" Margin="20,0,5,5" TextWrapping="Wrap"/>
<TextBlock Margin="10,0,5,5" Text="RotationAt:"/>
<TextBlock x:Name="tbRotateAt" Margin="20,0,5,5" TextWrapping="Wrap"/>
<TextBlock Margin="10,0,5,5" Text="RotationAt – Prepend:"/>
<TextBlock x:Name="tbRotateAtPrepend" Margin="20,0,5,5"
TextWrapping="Wrap"/>
<TextBlock Margin="10,0,5,5" Text="Skew:"/>
<TextBlock Name="tbSkew" Margin="20,0,5,5"/>
<TextBlock Margin="10,0,5,5" Text="Skew - Prepend:"/>
<TextBlock Name="tbSkewPrepend" Margin="20,0,5,5"/>
</StackPanel>
</Window>


这个标记文件使用TextBlocks创建了一个显示结果的布局,他们被嵌入到StackPanel里。相关的后台代码如下:

namespace Transformation2D
{
public partial class MatrixTransforms : Window
{
public MatrixTransform()
{
InitializeComponent();

// Original matrix:
Matrix m = new Matrix(1, 2, 3, 4, 0, 1);
tbOriginal.Text = "(" + m.ToString() + ")";

//Scale:
m.Scale(1, 0.5);
tbScale.Text = "(" + m.ToString() + ")";

// Scale - Prepend:
m = new Matrix(1, 2, 3, 4, 0, 1);
m.ScalePrepend(1, 0.5);
tbScalePrepend.Text = "(" + m.ToString() + ")";

//Translation:
m = new Matrix(1, 2, 3, 4, 0, 1);
m.Translate(1, 0.5);
tbTranslate.Text = "(" + m.ToString() + ")";

// Translation - Prepend:
m = new Matrix(1, 2, 3, 4, 0, 1);
m.TranslatePrepend(1, 0.5);
tbTranslatePrepend.Text =
"(" + m.ToString() + ")";
//Rotation:
m = new Matrix(1, 2, 3, 4, 0, 1);
m.Rotate(45);
tbRotate.Text = "(" + MatrixRound(m).ToString()
+ ")";

// Rotation - Prepend:
m = new Matrix(1, 2, 3, 4, 0, 1);
m.RotatePrepend(45);
tbRotatePrepend.Text = "(" + MatrixRound(m).ToString() + ")";

//Rotation at (x = 1, y = 2):
m = new Matrix(1, 2, 3, 4, 0, 1);
m.RotateAt(45, 1, 2);
tbRotateAt.Text = "(" + MatrixRound(m).ToString() + ")";
// Rotation at (x = 1, y = 2) - Prepend:
m = new Matrix(1, 2, 3, 4, 0, 1);
m.RotateAtPrepend(45, 1, 2);
tbRotateAtPrepend.Text = "(" + MatrixRound(m).ToString() + ")";

// Skew:
m = new Matrix(1, 2, 3, 4, 0, 1);
m.Skew(45, 30);
tbSkew.Text = "(" + MatrixRound(m).ToString() + ")";

// Skew - Prepend:
m = new Matrix(1, 2, 3, 4, 0, 1);
m.SkewPrepend(45, 30);
tbSkewPrepend.Text = "(" + MatrixRound(m).ToString() + ")";
}

private Matrix MatrixRound(Matrix m)
{
m.M11 = Math.Round(m.M11, 3);
m.M12 = Math.Round(m.M12, 3);
m.M21 = Math.Round(m.M21, 3);
m.M22 = Math.Round(m.M22, 3);
m.OffsetX = Math.Round(m.OffsetX, 3);
m.OffsetY = Math.Round(m.OffsetY, 3);
return m;
}
}
}


生成并运行这个应用程序得到如图2-6的输出:



原始的矩阵,m= (1, 2, 3, 4, 0, 1),被进行了各种变换。首先,我们检查拉伸变换,它设定X方向的拉伸系数为1,Y方向为0.5。对于Append拉伸(默认设置),我们得到



这和图2-6显示的结果(1, 1, 3, 2, 0, 0.5)是一样的。另一方面,对于Prepend拉伸,我们得到



这证实了图2-6显示的结果(1, 1, 3, 2, 0, 0.5)。

我们接下来在X方向平移矩阵m一个单位,在Y方向平移一半的单位。对于Append(默认设置)平移,我们得到:



这和图2-6显示的结果(1, 2, 3, 4, 1, 1.5) 一致。

对于Prepend平移,我们进行下面的变换:



它证实了图2-6显示的结果(1, 2, 3, 4, 2.5, 5)。

对于旋转变换,原始矩阵被m旋转45度。在Append选择的情况下,我们得到



注意在前面的计算中,我们使用了 cos(π/4) = sin(π/4) = 0.707。这和图2-6给出的结果一样( -0.707, 2.121,  -0.707, 4.95, -0.707, 0.707) 。

对于Prepend旋转,我们得到



这个结果和图2-6中显示的(2.828, 4.243, 1.414, 1.414, 0, 1)一致。

RotateAT方法被设计用在你需要改变旋转中心的情况。事实上,Rotate方法是RotateAt方法的一个特例,它的选转中心是(0,0)。在这个例子里,矩阵m响度点(1,2)旋转了45度。正如本章前面讨论到的,一个对象关于一个任意点P1旋转必须根据下面的步骤进行操作:

 平移P1到原点
 将它旋转一个想要的角度
 将P1平移回原来的点

考虑WPF里的矩阵旋转,关于点(1,2)旋转应该表示成下面的形式:



因此,矩阵m的关于点(1,2)Append旋转45度变成



这给出了和图2-6一样的结果(-0.707, 2.121,  -0.707, 4.949, 1, 0.586)。存在微小的差异是因为小数舍入。

相似地,关于点(1,2)Prepend旋转45度应该是



同样,结果和图2-6显示的一样。

最后,我们检查Skew方法,它创建了一个剪切变换。这个方法有两个参数,AngleX和AngleY,分别表示,水平和垂直倾斜因子。倾斜变换在齐次坐标系里可以表示成下面的形式

这里,tan(AngleX)和tan(AngleY)分别是X和Y方向的倾斜变换因子。看一下这个例子中的Skew变换。倾斜角度是AngleX=45度,AngleY=30度。这种情况下,Skew矩阵是



因此,对于Append变换,我们得到



这证实了图2-6中显示的结果。

对于Prepend Skew变换,我们得到



结果和图2-6给出的一样。

这里,我对WPF中的矩阵变换进行了详细的解释。这些信息对于理解定义和WPF中的内部表示矩阵,和在WPF应用程序中正确使用矩阵很有用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息