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重新設置了矩陣的值,之前的兩個變換是無效的了,所以最終的顯示效果只有三個變換效果。

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