Android Matrix 帶你掌控雷電

Matrix 是什么

Matrix 擁有一個 3 * 3 矩陣,這個矩陣用于坐標變換。這個矩陣定義如下

Matrix.PNG

Matrix 操作圖像的 API 有平移(Translate),縮放(Scale),旋轉(Rotate),錯切(Skew),然而并沒有對透視(Perspctive)的操作,Google 了關于 Matrix 的文章,MPERSP_0, MPERSP_1, MPERSP_2 分別為0,0,1,至于為什么,簡單來看,就是為了用一個通用矩陣表示以上四種變換。

下面先從數學角度推導下四種變換對應的 Matrix 是什么。

平移的矩陣

一個點 P 平移到 P' ,在坐標系表示如下

Translate.PNG

用數學公式表示

Translate_Math.PNG

轉換為坐標系表示

Translate_Matrix1.PNG

中間的3 * 3 矩陣就是平移對應的 Matix , ?x 就是 x 軸的增量,?y 就是 y 軸的增量。

旋轉矩陣

旋轉是有中心點的,如果把 Matrix 應用到圖片,默認旋轉點是圖片的左上角,也就是圖片的坐標原點(0,0)。

繞坐標原點旋轉矩陣

Rotate_XY.PNG

用數學公式表示

Rotate_Math.PNG

用矩陣表示

Rotate_Matrix1.PNG

中間的 3 * 3 矩陣就是繞坐標原點旋轉的 Matrix。

繞非坐標原點旋轉矩陣

如果圖片并非繞它的坐標原點(左上角)旋轉,而是繞任意點旋轉,例如 圖片中心點 (x1, y1),那么這個又如何計算呢?

這里我們先用數學思維思考,我們可以把(x1,y1) 作為坐標原點,那么 P 的表示應該為 (x0-x1, y0-y1),P' 的坐標為 (x-x1, y-y1),那么我們再應用旋轉點是坐標原點的矩陣公式,就應該這樣寫

Rotate_Matrix1.PNG

再簡化下

Rotate_Matrix2.PNG

是不是現在感覺比較明朗,等式右邊第一個和第三個矩陣就是平移矩陣,我們在后面會看到如何生成這三個矩陣的結果。

我們再來理解下這個矩陣,最后兩個矩陣相乘代表坐標原點移動到(x1,y1),最后三個矩陣代表以(x1, y1)為原點旋轉,最后四個矩陣代表坐標原點從(x1, y1)移動到(0,0),這樣是不是好記一些。

縮放矩陣

圖片都是由像素點構成的,如果一個圖片放大 k 倍,可以看作每個點的 x 和 y 坐標值放大 k 倍。當然縮放也有中心點的。圖片默認的縮放中心為圖片的左上角,也就是圖片的原點(0,0)。

原點為中心的縮放

用數學表示如下

Scale_Math.PNG

用矩陣表示如下

Scale_Matrix.PNG

中間的矩陣就是繞原點綻放的 Matrix

非原點的縮放

這與非原點的旋轉的數學思維是一樣的,這里直接給出矩陣

Scale_Matrix1.PNG

中間的三個矩陣相乘就是非原點縮放 Matrix

錯切矩陣

錯切分為 x 軸和 y 軸的錯切。

x 軸錯切矩陣

Skew_X.PNG

x 軸錯切,是保持坐標的 y 軸值不變,x 軸值的做線性變換 ,表示如下

Skew_Math.PNG

斜率為 1/k

矩陣表示如下

Skew_MatrixX.PNG

矩陣表示用到的是 k,而不是斜率 1 / k,因此 k 越大,圖形錯切的越大。

y 軸錯切矩陣

Skew_Y.PNG

y軸錯切,就是 x 軸的值不變,y 軸的值做線性變換,矩陣就不用我再推理吧,表示如下

Skew_MatrixY.PNG

x 軸 y 軸的錯切矩陣

綜合 x 軸 和 y 軸錯切,統一表示如下

Skew_MatrixXY.PNG

kx 表示 x 軸的錯切值,ky 表示 y 軸的錯切值。kx,ky 越大,圖形錯切的越大。

看完了可惡的數學公式,我們就懂得了原理 ,現在用 Matrix 的 API 來測試測試吧。

每種變換都有 setXx() ,postXx(),preXx() 方法來設置相應的變換 。如 Translate,有 Matrix.setTranslate(),Matirx.postTranslate(),Matrix.preTranslate()。我將會在代碼中解釋這些到底怎么用,請大家多注意,因為大部分人會用錯。

默認顯示一個 launcher 圖標

我們先顯示一個不做 Matrix 處理的圖標

/**

  • Created by David Chow on 2016/12/6. */

public class MatrixView extends View {

private Matrix mMatrix;
private Bitmap mBitmap;

public MatrixView(Context context) {
    this(context, null);
}

public MatrixView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

private void init() {
    mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    mMatrix = new Matrix();
    Log.d("david", mMatrix.toString());
}


@Override
protected void onDraw(Canvas canvas) {
    canvas.drawBitmap(mBitmap, mMatrix, null);
}

}</code></pre>

Default.PNG

我們看看這個初始的 Matrix 矩陣是什么

Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}

這就是一個 3 * 3 單位矩陣 ,因此它不論前乘還是后乘都無所謂。

平移

@Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200); // 單位 pixel
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", "translate matrix : " + mMatrix.toString());
    }

Translate.PNG

我們看到了圖像平移了,這個時候,我們打印 Log,可以看到矩陣的值為

translate matrix : Matrix{[1.0, 0.0, 200.0][0.0, 1.0, 200.0][0.0, 0.0, 1.0]}

我們可以看到 Matrix.setTranslate() 把單位矩陣重置為 平移的矩陣(上面數學分析得出的平移矩陣)

那么用理論的的矩陣這樣表示

Translate.PNG

這樣與我們上面理論是不是就一致了,這個時候我們心情是不是稍微好點了,因為我們理論終于在實際中得到驗證了。后面我們將不再去這個用矩陣來驗證理論,我們只打印相應的 Log 即可。

縮放

@Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200); // 單位 pixel
        mMatrix.setScale(3, 3);
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }

Log打印如下

Matrix{[3.0, 0.0, 0.0][0.0, 3.0, 0.0][0.0, 0.0, 1.0]}

就算我們先用了一個無關的 Matrix.setTranslate() , 后面的 Matrix.setScale() 還是會重置矩陣為縮放的矩陣。這點大家要記住,免得以后沒有達到效果,卻不知道問題在哪里了。

Scale.PNG

默認的縮放中心是圖片的左上角,也就是(0,0),當然我們也可以調整縮放中心位置

@Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setScale(3, 3,mBitmap.getWidth()/2,mBitmap.getHeight()/2);
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }

現在縮放中心的位置為 Bitmap 的中心位置,現在看下效果

Scale1.PNG

從效果看,確實是根據中心點縮放,以致圖片的左上角超出了屏幕顯示。

我們打印Log看下

Matrix{[3.0, 0.0, -126.0][0.0, 3.0, -126.0][0.0, 0.0, 1.0]}

那么與我們上面理論分析是不是相符,就留給大家去驗證了。

Matrix.setRotate()旋轉

旋轉也是有中心點的,先看看個繞圖片中心旋轉的情況

@Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setRotate(45, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
        canvas.drawBitmap(mBitmap, mMatrix, null);
    }

Log打印如下

Matrix{[0.70710677, -0.70710677, 63.0][0.70710677, 0.70710677, -26.095451][0.0, 0.0, 1.0]}

驗證理論還是交給大家了~~

Rotate_Default.png

當然默認的旋轉中心也是圖片的左上角

@Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200); // 單位 pixel
        mMatrix.setScale(3, 3);
        mMatrix.setRotate(30);
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }

同樣,setRotate() 覆蓋了前面的 setScale(), setTranslate()

打印下矩陣信息

Matrix{[0.8660254, -0.5, 0.0][0.5, 0.8660254, 0.0][0.0, 0.0, 1.0]}

根據我們上面講的,這個里面的小數值 ,對應角度為 30 的 sin 或者 cos 值,大家可以自己用計算器算算~~

現在看下效果圖

Rotate.PNG

我們發現這個移出了屏幕了,因此現在我們來平移下

@Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200); // 單位 pixel
        mMatrix.setScale(3, 3);
        mMatrix.setRotate(30);
        mMatrix.preTranslate(200, 200);
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }

Rotate_Translate_Error.PNG

一個奇怪的現象就產生了,X 方向的平移呢?其實這是因為矩陣乘法不滿足交換律的原因。 mMatrix.preTranslate() 意思是用 mMatrix 矩陣前乘 translate 矩陣,數據表示如下

Rotate_Translate.png

這個結果就不用我算了吧,很顯然實際偏移的并不是200,200。那么我們如何讓它既旋轉30°,又正常偏移200,200呢。 我們可以讓 mMatrix 后乘 translate

@Override
    protected void onDraw(Canvas canvas) {
        canvas.drawLine(0, 0, 200, 200, mPaint);
        mMatrix.setRotate(30);
        mMatrix.postTranslate(200, 200);
        canvas.drawBitmap(mBitmap, mMatrix, null);
    }

為了看到我們確實是偏移了200,200,我畫了一條紅色的線

Rotate_PostTranslate.png

那么我們用數學來表達下

Post_Translate.png

大家計算下,是不是既旋轉了又平移了200,200?

我用這個例子是為了讓大家理解 preXx() 和 postXx() 的區別,希望大家自己動手試試加深理解。

再給大家一個 Tip,如果想要平移達到效果,最后調用 postTranslate()。

Matrix.setSkew() 錯切

先用默認中心點,即圖片的左上角進行錯切。為了看到效果,我們先平移200,200,再進行錯切變換

mMatrix.setTranslate(200, 200);
mMatrix.preSkew(1, 0);
canvas.drawBitmap(mBitmap, mMatrix, null);

Skew.png

這里我并沒有遵循上面的例子最后用 postTranslate(),這是因為根據矩陣的特性,這樣剛好不影響平移,我們可以打印Log看看矩陣

Matrix{[1.0, 1.0, 200.0][0.0, 1.0, 200.0][0.0, 0.0, 1.0]}

我沒說錯吧,但是如果你再操作下,就有問題了,例如例如 postScale(3,3),就會平移 600,600了。當然如果你最后還是調用 postTranslate(200,200) ,會正常平移400,400。

大家別看我說的很簡單,自動寫的時候就會遇到各種問題,因此大家在看的時候還是多動手。

再看看中心點不是原點的錯切,例如用圖片的左下角來錯切。

@Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200);
        mMatrix.preSkew(1, 0, 0, mBitmap.getHeight());
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }

打印Log

Matrix{[1.0, 1.0, 74.0][0.0, 1.0, 200.0][0.0, 0.0, 1.0]}

理論驗證繼續留給大家了~~

思考

我們使用 Matrix API 的時候,我們只是一名 Developer,但是我們要把自己當做一個 Designer,我們思考下,其實我們可以利用矩陣做很多想要的效果,因為我們可以用 Matrix.setValues() 來設置自己想要的矩陣 ,因此我們可以設計對稱效果,倒影效果等等,這就要大家去發掘啦~~

結束

這篇文章大家入門 Matrix,當然這是為了我后面文章打基礎的,還是那句話,多動手,如果遇到問題解決不了,歡迎大家留言討論。 如果大家覺得還不錯,可以點個贊,甚至來一波關注不惜留戀_, what's a nice day~~

 

來自:http://www.jianshu.com/p/b8cc4ac9780b

 

 本文由用戶 chinajodte 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!