在前文中已经介绍过二维图像矩阵变换的一些基础知识,本文中主要介绍一下如何在WPF中进行矩阵变换。
Matrix结构
在WPF中,用Matrix结构(struct类型)表示二维变换矩阵,它是一个3*3的数组,结构如下,
由于第三列是常量0,0,1,因此并不作为公开属性,可见的只有剩余六个属性。
构造变换
虽然Matrix类公开了这六个属性让我们设置,但是靠直接设置这六个属性来实现平移、旋转等变换对于我们来说实在太困难了,因此又增加了如下许多函数来帮助我们实现这一过程,常见了有:
- Rotate
- RotateAt
- Scale
- ScaleAt
- Skew
- Translate
这些函数的效果是叠加的,例如,我们要先平移(10,20),然后绕原点旋转30度,方式如下:
Matrix matrix = Matrix.Identity; matrix.Translate(10, 20); matrix.Rotate(30);
其中Matrix.Identity是矩阵的默认值,它是一个恒等矩阵(不进行任何变换,可以用于重置)。
反转矩阵
关于反转矩阵,Matrix类中提供了一个属性和函数:
- HasInverse 属性 用于检查该矩阵是否可以反转。
- Invert() 用于获取反转矩阵
反转矩阵可以非常方便我们进行矩阵的逆运算,十分有用。
应用变换
在WPF中可以接受矩阵运算的基础元素有Point和Vector,可以通过Transform函数进行矩阵变换:
var transForm = Matrix.Identity; transForm.Scale(2, 3); var point = new Point(1, 1); var newPoint = transForm.Transform(point); Console.WriteLine(newPoint); //输出(2,3)
在C#中还重载了"*"运算符,这样更加直观了:
var newPoint = point * transForm;
另外,Transform函数还有一个可以接收数组的的版本,这个版本中并不生成新的对象,因此具有更高的效率。
复合变换
前文已经介绍过,矩阵是可以通过乘运算实现变换的叠加的,Matrix类中提供了Multiply函数进行两个矩阵相乘,在C#中也可以使用"*"运算符来实现这一过程。
Matrix scale = Matrix.Identity; scale.Scale(2, 2); Matrix transLate = Matrix.Identity; transLate.Translate(10, 20); var transForm = scale * transLate; Matrix transForm2 = Matrix.Identity; transForm2.Scale(2, 2); transForm2.Translate(10, 20); Contract.Assert(transForm == transForm2);
需要注意的是,矩阵并不满足交换律,如:
Contract.Assert((transLate * scale) != (scale * transLate));
扩展函数
在日常的使用过程中,我们的变换矩阵往往是通过一系列操作叠加起来的。可能是为了效率,WPF的变换函数返回值都是Void,叠加起来并不方便。这里我写了几个扩展函数简化这一过程:
public class GeometryTransForm { Matrix _matrix; public Matrix Matrix { get { return _matrix; } private set { _matrix = value; } } ////// 获取一个恒等变换 /// public static GeometryTransForm Identity { get { return new GeometryTransForm(); } } ////// 以指定点为中心旋转指定的角度。 /// /// 要旋转的角度(单位为度)。 /// 要围绕其旋转的点的 x 坐标。 /// 要围绕其旋转的点的 y 坐标。 public GeometryTransForm Rotate(double angle, double centerX = 0, double centerY = 0) { _matrix.RotateAt(angle, centerX, centerY); return this; } ////// 围绕指定的点按指定的量缩放 /// /// 沿 x 轴的缩放量 /// 沿 y 轴的缩放量 /// 缩放操作中心点的 x 坐标 /// 缩放操作中心点的 y 坐标 public GeometryTransForm Scale(double scaleX, double scaleY, double centerX = 0, double centerY = 0) { _matrix.ScaleAt(scaleX, scaleY, centerX, centerY); return this; } ////// 在 x 和 y 维中指定角度的扭曲。 /// /// 用于扭曲此的 x 维角度 /// 用于扭曲此的 y 维角度 public GeometryTransForm Skew(double skewX, double skewY) { _matrix.Skew(skewX, skewY); return this; } ////// 按指定偏移量的平移 /// /// 沿 x 轴的偏移量 /// 沿 y 轴的偏移量 public GeometryTransForm Translate(double offsetX, double offsetY) { _matrix.Translate(offsetX, offsetY); return this; } public GeometryTransForm Transfrom(GeometryTransForm transform) { return Transfrom(transform.Matrix); } public GeometryTransForm Transfrom(Matrix transform) { _matrix = _matrix * transform; return this; } ////// 反转变换 /// public GeometryTransForm Invert() { _matrix.Invert(); return this; } public static Point operator *(Point point, GeometryTransForm transform) { return point * transform.Matrix; } //如果是struct就用不着这个了,每一次 = 都是Clone public GeometryTransForm Clone() { return new GeometryTransForm() { Matrix = this.Matrix }; } }
通过这个扩展函数,前面的变换可以简化如下:
var transForm = GeometryTransForm.Identity.Scale(2, 2).Translate(10, 20);
另外,这个类也支持直接和Point相乘,用起来还是蛮方便的。
UI的矩阵变换
由于篇幅所限,本文只介绍了WPF矩阵变换的基础操作,下一篇文章中再介绍如何将矩阵变换应用到UI界面上