矩阵操作
WPF里的Matrix结构提供了进行旋转,拉伸和平移的方法。它也实现了一些进行矩阵操作的方法。例如,你也可以使用Invert方法来得到一个可逆矩阵的逆。这个方法没有参数。Multiply方法将两个矩阵相乘并返回一个新矩阵作为结果。下面是一些矩阵操作常用的方法:
另外还有和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>
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.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>
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; } } }
原始的矩阵,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旋转必须根据下面的步骤进行操作:
考虑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应用程序中正确使用矩阵很有用。