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

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

矩阵操作

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的输出。

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

图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).。事实上:

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

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

接下来,我们考虑矩阵相乘。在代码里,你创建了两个矩阵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)。事实上:

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

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

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

这和图中显示的(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的输出:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

对于Prepend旋转,我们得到

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

这个结果和图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)旋转应该表示成下面的形式:

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

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

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

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

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

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

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

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

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

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

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

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

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

对于Prepend Skew变换,我们得到

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

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

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