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

来源:互联网 发布:h5微场景模板源码 编辑:程序博客网 时间:2024/06/03 07:09

矩阵操作

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 WindowTransformation2D项目中,命名为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方向的拉伸系数为1Y方向为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,2Append旋转45度变成

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

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

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

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

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

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

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

对于Prepend Skew变换,我们得到

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

这里,我对WPF中的矩阵变换进行了详细的解释。这些信息对于理解定义和WPF中的内部表示矩阵,和在WPF应用程序中正确使用矩阵很有用。



原创粉丝点击