Matrix 变换原理

转载原文:https://www.jianshu.com/p/5e30db034596

我们在自定义 View 控件时随处可见 Matrix 的身影,主要用于座标转换映射,我们可以通过 Matrix 矩阵来控制视图的变换。

Matrix 本质上是一个如下图所示的矩阵:

在这里插入图片描述

上面每个值都有其对应的操作。
Matrix 提供了如下几个操作:

  • 缩放(Scale)
    对应 MSCALE_X 与 MSCALE_Y

  • 位移(Translate)
    对应 MTRANS_X 与 MTRANS_Y

  • 错切(Skew)
    对应 MSKEW_X 与 MSKEW_Y

  • 旋转(Rotate)
    旋转没有专门的数值来计算,Matrix 会通过计算缩放与错切来处理旋转。

数学原理
我们先来简单的复习一下矩阵乘法规则(A*B 要求A的列数 = B的行数)。
在这里插入图片描述
我们在使用 Matrix 处理视图变换时本质上是通过矩阵映射座标。
所以上述的几个操作都是对矩阵的操作,我们新建一个 Matrix 后其矩阵为默认状态,其值如下:
在这里插入图片描述
可以看到默认状态下的数据都是初始值,即不做任何变换处理,所有座标保持原样(即:缩放默认为1,错切和位移默认为0)。

缩放(Scale)
对於单个座标来说,缩放只要将其座标值值乘以缩放值即可。
假设对某个点宽度缩放 k1 倍,高度缩放 k2 倍,该点座标为 x0、y0,缩放后坐标为 x、y,那么缩放的公式如下:
在这里插入图片描述
我们现在知道了缩放对应矩阵中的两个值的位置以及上面的公式,那现在在用矩阵来描述缩放操作:
在这里插入图片描述
等号左边的矩阵就是计算后的缩放结果。

Matrix 中用于缩放操作的方法有如下两个:

void setScale(float sx, float sy);
void setScale(float sx, float sy, float px, float py);

前面两个参数 sx、sy,分别是宽和高的缩放比例。
第二个重载方法多了两个参数 px、py,这两个参数用来描述缩放的枢轴点,关于枢轴点的含义可以看下注释:

Set the matrix to scale by sx and sy, with a pivot point at (px, py).
The pivot point is the coordinate that should remain unchanged by the
specified transformation.

大概说枢轴点是指定转换应保持不变的座标。
当我们不传这两个参数时,枢轴点默认为左上角的点,缩放都是向下和向右,所以枢轴点可以大概的理解为缩放的锚点,缩放从这个点开始向四周扩散。
我们用矩阵来描述一下就能明白了。
初始化一个矩阵之后调用缩放方法:

Matrix matrix = new Matrix()
matrix.setScale(0.5F, 0.5F, 300F, 300F);

缩放 0.5 倍,枢轴点为 300,调用该方法后矩阵变换为:
在这里插入图片描述
实际上我们设置了枢轴点后 Matrix 会做一次位移操作,平移距离就是 s * p.

位移(Translate)
位移操作是指将座标(x0,y0)平移一定的距离,我们直接将座标加上平移的距离即可得到平移后的座标:
在这里插入图片描述
用矩阵表示:
在这里插入图片描述
用于设置位移操作的只有一个方法:

void setTranslate(float dx, float dy);

错切(Skew)

在这里插入图片描述
上图是通过下面的代码进行错切的前后对比图。

matrix.setSkew(0.3F, 0.3F);

分别设置了水平错切垂直错切的值为 0.3,效果就是上面的样子。
错切公式如下:
在这里插入图片描述
矩阵描述
在这里插入图片描述
错切操作的方法:

void setSkew(float kx, float ky);
void setSkew(float kx, float ky, float px, float py);

这里的两个方法类似于缩放操作的两个 setScale 方法,包括最后两个参数也具有相同的意义,这里就不多做介绍了。

旋转(Rotate)
旋转的公式比较复杂,证明也比较麻烦,这里不做详细解释了,因为我也没看懂,有兴趣的同学可以自己去国外某知名视频网站找到详细的推导证明视频,这里放一张刚刚看了但是没看懂的截图:
在这里插入图片描述
注意一下,Android 中的座标系与上图的不同,所以结果有些区别,那么旋转的公式如下:
在这里插入图片描述
矩阵描述:

在这里插入图片描述
用于控制旋转的方法:

void setRotate(float degrees);
void setRotate(float degrees, float px, float py);

上面讲了 Matrix 变换的数学原理,以及其中提供的几个方法,不仅如此,Matrix 还提供复合变换的功能,下面来说一下。

Matrix 复合变换
复合变换是指矩阵同时实现两种或以上变换,例如在平移的同时改变其大小。

Matrix 的复合变换实际上就是矩阵相乘,原理很简单,但是因为矩阵相乘不符合交换律、且执行顺序对结果会有影响,所以想准确的使用好符合变换需要了解其原理。

上面我们在介绍这几种变换的同时也说了他们对应的方法,可以看到他们都是 set 方法,但 Matrix 中实际上提供了三种操作,分别是:设置(set)、前乘(pre)以及后乘(post)。

所以上述介绍的几个 set 方法都有与之对应的 pre 及 post 方法,方法列表如下:

//scale
boolean preScale(float sx, float sy);
boolean preScale(float sx, float sy, float px, float py);
boolean postScale(float sx, float sy);
boolean postScale(float sx, float sy, float px, float py);

//translate
boolean preTranslate(float dx, float dy);
boolean postTranslate(float dx, float dy);

//skew
boolean preSkew(float kx, float ky);
boolean preSkew(float kx, float ky, float px, float py);
boolean postSkew(float kx, float ky);
boolean postSkew(float kx, float ky, float px, float py);

//rotate
boolean preRotate(float degrees);
boolean preRotate(float degrees, float px, float py);
boolean postRotate(float degrees);
boolean postRotate(float degrees, float px, float py);

那么这三种方法(set、pre、post)有什么区别呢?

设置(set)
如果我们不需要考虑复合变换的情况,一般可以直接使用 set 方法,因为 set 方法可能会重置之前的 Matrix 状态,导致之前设置的变换失效。

前乘(pre)
前乘相当于矩阵右乘:

在这里插入图片描述
假设当前矩阵 M 为:

在这里插入图片描述
我们使用 pre 方法做一个平移操作:

matrix.preTranslate(100, 100);

变换过程如下:
在这里插入图片描述

后乘(post)
后乘相当于矩阵左乘:
在这里插入图片描述
我们用上面的矩阵 M 举个例子,同样对其做一个平移操作,但是使用 post 方法:

matrix.postTranslate(100, 100);

变换过程如下:
在这里插入图片描述
这里的前乘后乘的概念主要是由于矩阵不符合乘法交换律引起的,我们使用时一定要注意,除此之外,调用顺序的不同对其结果也有影响,所以我们在使用时需要先确定好矩阵的变换方式,过程之后,再决定如何使用这些方法。

下面给出一个例子。

package chroya.demo.graphics;    
    
import android.content.Context;    
import android.graphics.Bitmap;    
import android.graphics.Canvas;    
import android.graphics.Matrix;    
import android.graphics.Rect;    
import android.graphics.drawable.BitmapDrawable;    
import android.util.DisplayMetrics;    
import android.view.MotionEvent;    
import android.view.View;    
    
public class MyView extends View {    
        
    private Bitmap mBitmap;    
    private Matrix mMatrix = new Matrix();    
        
    public MyView(Context context) {    
        super(context);    
        initialize();    
    }    
    
    private void initialize() {  
        Bitmap bmp = ((BitmapDrawable)getResources().getDrawable(R.drawable.show)).getBitmap();    
        mBitmap = bmp;    
        /*首先,将缩放为100*100。这里scale的参数是比例。有一点要注意,如果直接用100/  
bmp.getWidth()的话,会得到0,因为是整型相除,所以必须其中有一个是float型的,直接用100f就好。*/    
        mMatrix.setScale(100f/bmp.getWidth(), 100f/bmp.getHeight());    
                //平移到(100,100)处    
        mMatrix.postTranslate(100, 100);    
                //倾斜x和y轴,以(100,100)为中心。    
        mMatrix.postSkew(0.2f, 0.2f, 100, 100);    
    }    
        
    @Override protected void onDraw(Canvas canvas) {    
//      super.onDraw(canvas);  //如果界面上还有其他元素需要绘制,只需要将这句话写上就行了。    
        canvas.drawBitmap(mBitmap, mMatrix, null);    
    }    
}

运行效果如下:
在这里插入图片描述
setScale重新设置了矩阵的值,之前的两个变换是无效的了,所以最终的显示效果只有三个变换效果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章