2D平面中关于矩阵(Matrix)跟图形变换的讲解


在二维平面上,常用的有以下三种基本的图形变化:

1)Translation

2)Scale

3)Rotation

在canvas的开发中,我们也经常会用到这样的一些图形变换,尤其是我们在写自定义View时,更是会经常利用到Matrix来实现一些效果,比如平移,旋转,缩放及切变等,相信很多朋友应该很想知道,矩阵实现这种变换的原理是什么,什么是矩阵的左乘右乘,它们在实现效果上有什么差别吗?今天就让我们一起来看一下吧。

都是由点组成的

平面上的元素,就是点,线,面,而线就是由一个个点组成的,而是由一条条线组成的,所以归根结底,平面上所有的图形都是由点组成的。而在我们坐标系中,一个点就是由一对x,y值组成的,p = {x, y}。而在平面上,过两点间的,我们可以画一条直线,所以我们一般通过 p1, p2可定义一条直线,e = {p1, p2},而图形呢,则是由众多的点和点之间的的线段组成的。所以,其实平面上的图形变换,就是点坐标位置的变换。 在平面上,一个点,可以通过一个向量或者矩阵来表示: 而下面这条红色的直线则是由一组组的点组成的,起始点是(120,0),终点是(240,120)。

Translation(平移)

如果我们现在要平移这条直线,向右120(tx),向下120(ty),那么新的点会是怎么样呢?很显然,起始点就会是(240,120),而终点就会是(360,240),效果如下: 绿色的线就是平移后的线了,可以看出每一个新的点的值是 这样的一个变换translation也可以用一对值来表示,t = {tx, ty},其中tx是在x坐标上的偏移量,而ty是在y坐标上的偏移量。移动点 p 到 p',我们只要加上这个偏移就行,如果用矩阵或者向量来表示就是: (可能会有朋友觉得奇怪,怎么是加呢,canvas里面矩阵不都是乘吗?这是什么原因呢?)

Scale(缩放)

那如果我们对这条直线进行放大呢,比如放大2倍呢,一般来讲,我们缩放,是指所有维度的缩放,当然在这里就只有x坐标跟y坐标,当然也可以只针对一个维度,但是就会变形了哦。我们先看一下放大到2倍的效果和只放大列的效果吧。            很显然,两边都放大的话,起始点由(120,0)变成(240,0),终点由(240,120)变成(480,240)。而如果只放大列的话,起始点的坐标是不变的。而且我们可以看到,放大的时候,线也跟着向右平移了一个单位,为什么会这样呢?这是因为缩放是基于原点(0,0)的,在canvas中,也就是屏幕的左上角,但缩放的位数大于1的时候,就会远离原点,而相反,当缩放的位数小于1时,则会趋近原点。 缩放的变换是由下面的矩阵来表示的: 那么缩放后的直线的点就是: 各位朋友,可以想一下,这样直接Scale的话,这个图形可是会平移的哦,如果不想要平移,应该怎么办?

Rotation(旋转)

我们再看一下下图,这条直线顺时针旋转了45度,也就是往逆时针方向旋转了 - 45 度 那么新的点是怎么算出来的呢? 逆时针(注意这里)旋转的矩阵表示是:(至于为什么是这个矩阵,请查看文章结尾处附图) 同样的,旋转后的点就是根据下面的矩阵相乘而得出来的结果: 我们可以将我们的点代进去求解   可得新的起始点P0'为(84.85,84.85),而新的结束点为:(84.85,254.56) 以结束点为例,将-45度带入公式: x=240*Math.cos(-45/180*Math.PI)+120*Math.sin(-45/180*Math.PI)   计算结果84.85 y=-240*Math.sin(-45/180*Math.PI)+120*Math.cos(-45/180*Math.PI)   计算结果254.56 可看出,刚好是上面绿色线所在的地方。  

Combine Transformation (组合变换)

对于Scale 和 Rotation 来说,它们都是基于原点(0,0)的变换,那如果我们要让它基于某个点缩放或者旋转,就比如绕着起始点转呢,而这也经常是我们想要的一种效果。这个点就是所谓的旋转,而解决办法其实就是将图形先平移到原点,再进行缩放或者旋转的变化,然后再移回来,就可以了。 假设这三种变换的矩阵表示如下: 那么它应该实施的变换就如下:先平移 T 到原点,再基于原点进行缩放(或者旋转),然后再平移回去, 其实这一步,我们可以在Canvas的代码中看到的,如下:
 1     /**
 2      * Preconcat the current matrix with the specified scale.
 3      *
 4      * @param sx The amount to scale in X
 5      * @param sy The amount to scale in Y
 6      * @param px The x-coord for the pivot point (unchanged by the scale)
 7      * @param py The y-coord for the pivot point (unchanged by the scale)
 8      */
 9     public final void scale(float sx, float sy, float px, float py) {
10         translate(px, py);
11         scale(sx, sy);
12         translate(-px, -py);
13     }
上面代码中的轴点的实现,其实就是对于平移的来回操作,至于为什么是translate(px,py)在前,而translate(-px,py)在后呢,这涉及到矩阵左乘和右乘的计算,后面我们会谈到的。  

Homogeneous Coordinates(齐次坐标)

在上面的矩阵中,我们可以看到平移的矩阵是相加的,而旋转跟缩放的矩阵都是相乘的,这样计算起来多麻烦呀!于是为了方便计算,大家都统一用一种方式来进行计算,聪明的计算机图形科学家,它们就设计出这样一种坐标系,叫homogeneous coordinates,而它的目的只是为了更加方便地去用矩阵来计算图形的变换,没有其他。 那什么是齐次坐标呢? 其实就是在原来2D的维度,再加上一个新的维度,多出来的维度的值永远是1,比如点的矩阵就变成: 而Translation(平移)的矩阵表示就变成: 这样,平移变换的加法就可以变成乘法: 而Scale(缩放)跟Rotation(旋转)相对应的矩阵也就变成: 看到这几个,大家发现了没有?没错,这几个就是我们canvas中用到的矩阵了。   当我们在Canvas上用Scale的时候,其实就是乘上S矩阵,当我们用Rotate的时候,其实就是乘上R矩阵,大家明白没有?   附图,解释旋转矩阵的由来:  

在二维平面上,常用的有以下三种基本的图形变化:

1)Translation

2)Scale

3)Rotation

在canvas的开发中,我们也经常会用到这样的一些图形变换,尤其是我们在写自定义View时,更是会经常利用到Matrix来实现一些效果,比如平移,旋转,缩放及切变等,相信很多朋友应该很想知道,矩阵实现这种变换的原理是什么,什么是矩阵的左乘右乘,它们在实现效果上有什么差别吗?今天就让我们一起来看一下吧。

都是由点组成的

平面上的元素,就是点,线,面,而线就是由一个个点组成的,而是由一条条线组成的,所以归根结底,平面上所有的图形都是由点组成的。而在我们坐标系中,一个点就是由一对x,y值组成的,p = {x, y}。而在平面上,过两点间的,我们可以画一条直线,所以我们一般通过 p1, p2可定义一条直线,e = {p1, p2},而图形呢,则是由众多的点和点之间的的线段组成的。所以,其实平面上的图形变换,就是点坐标位置的变换。 在平面上,一个点,可以通过一个向量或者矩阵来表示: 而下面这条红色的直线则是由一组组的点组成的,起始点是(120,0),终点是(240,120)。

Translation(平移)

如果我们现在要平移这条直线,向右120(tx),向下120(ty),那么新的点会是怎么样呢?很显然,起始点就会是(240,120),而终点就会是(360,240),效果如下: 绿色的线就是平移后的线了,可以看出每一个新的点的值是 这样的一个变换translation也可以用一对值来表示,t = {tx, ty},其中tx是在x坐标上的偏移量,而ty是在y坐标上的偏移量。移动点 p 到 p',我们只要加上这个偏移就行,如果用矩阵或者向量来表示就是: (可能会有朋友觉得奇怪,怎么是加呢,canvas里面矩阵不都是乘吗?这是什么原因呢?)

Scale(缩放)

那如果我们对这条直线进行放大呢,比如放大2倍呢,一般来讲,我们缩放,是指所有维度的缩放,当然在这里就只有x坐标跟y坐标,当然也可以只针对一个维度,但是就会变形了哦。我们先看一下放大到2倍的效果和只放大列的效果吧。            很显然,两边都放大的话,起始点由(120,0)变成(240,0),终点由(240,120)变成(480,240)。而如果只放大列的话,起始点的坐标是不变的。而且我们可以看到,放大的时候,线也跟着向右平移了一个单位,为什么会这样呢?这是因为缩放是基于原点(0,0)的,在canvas中,也就是屏幕的左上角,但缩放的位数大于1的时候,就会远离原点,而相反,当缩放的位数小于1时,则会趋近原点。 缩放的变换是由下面的矩阵来表示的: 那么缩放后的直线的点就是: 各位朋友,可以想一下,这样直接Scale的话,这个图形可是会平移的哦,如果不想要平移,应该怎么办?

Rotation(旋转)

我们再看一下下图,这条直线顺时针旋转了45度,也就是往逆时针方向旋转了 - 45 度 那么新的点是怎么算出来的呢? 逆时针(注意这里)旋转的矩阵表示是:(至于为什么是这个矩阵,请查看文章结尾处附图) 同样的,旋转后的点就是根据下面的矩阵相乘而得出来的结果: 我们可以将我们的点代进去求解   可得新的起始点P0'为(84.85,84.85),而新的结束点为:(84.85,254.56) 以结束点为例,将-45度带入公式: x=240*Math.cos(-45/180*Math.PI)+120*Math.sin(-45/180*Math.PI)   计算结果84.85 y=-240*Math.sin(-45/180*Math.PI)+120*Math.cos(-45/180*Math.PI)   计算结果254.56 可看出,刚好是上面绿色线所在的地方。  

Combine Transformation (组合变换)

对于Scale 和 Rotation 来说,它们都是基于原点(0,0)的变换,那如果我们要让它基于某个点缩放或者旋转,就比如绕着起始点转呢,而这也经常是我们想要的一种效果。这个点就是所谓的旋转,而解决办法其实就是将图形先平移到原点,再进行缩放或者旋转的变化,然后再移回来,就可以了。 假设这三种变换的矩阵表示如下: 那么它应该实施的变换就如下:先平移 T 到原点,再基于原点进行缩放(或者旋转),然后再平移回去, 其实这一步,我们可以在Canvas的代码中看到的,如下:
 1     /**
 2      * Preconcat the current matrix with the specified scale.
 3      *
 4      * @param sx The amount to scale in X
 5      * @param sy The amount to scale in Y
 6      * @param px The x-coord for the pivot point (unchanged by the scale)
 7      * @param py The y-coord for the pivot point (unchanged by the scale)
 8      */
 9     public final void scale(float sx, float sy, float px, float py) {
10         translate(px, py);
11         scale(sx, sy);
12         translate(-px, -py);
13     }
上面代码中的轴点的实现,其实就是对于平移的来回操作,至于为什么是translate(px,py)在前,而translate(-px,py)在后呢,这涉及到矩阵左乘和右乘的计算,后面我们会谈到的。  

Homogeneous Coordinates(齐次坐标)

在上面的矩阵中,我们可以看到平移的矩阵是相加的,而旋转跟缩放的矩阵都是相乘的,这样计算起来多麻烦呀!于是为了方便计算,大家都统一用一种方式来进行计算,聪明的计算机图形科学家,它们就设计出这样一种坐标系,叫homogeneous coordinates,而它的目的只是为了更加方便地去用矩阵来计算图形的变换,没有其他。 那什么是齐次坐标呢? 其实就是在原来2D的维度,再加上一个新的维度,多出来的维度的值永远是1,比如点的矩阵就变成: 而Translation(平移)的矩阵表示就变成: 这样,平移变换的加法就可以变成乘法: 而Scale(缩放)跟Rotation(旋转)相对应的矩阵也就变成: 看到这几个,大家发现了没有?没错,这几个就是我们canvas中用到的矩阵了。   当我们在Canvas上用Scale的时候,其实就是乘上S矩阵,当我们用Rotate的时候,其实就是乘上R矩阵,大家明白没有?   附图,解释旋转矩阵的由来,及转换:      主要参照:https://blog.csdn.net/linmiansheng/article/details/18801947 关于矩阵左乘右乘,前乘后乘的关系,如果有困惑的朋友,可以看一下下面这篇文章: https://blog.csdn.net/linmiansheng/article/details/18820599  另外矩阵相关文章: https://www.cnblogs.com/fangsmile/p/5651429.html