天天看點

Android Matrix 帶你掌控雷電

Android Matrix 帶你掌控雷電

Matrix 是什麼

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

Matrix.PNG

Matrix 有個方法 isAffine(),判斷矩陣是否為仿射矩陣。那麼是什麼仿射矩陣呢?下面一段話來自百度百科

仿射變換:它是一種二維坐标到二維坐标之間的線性變換,保持二維圖形的“平直性”(變換後,直線還是直線,圓弧還是圓弧)、“平行性”(保持二維圖形間的相對位置不變,平等線還是平等線,但是向量夾角可以發生變化)。仿射變換可以通過一系列的原子變換的複合來實作,包括平移(Translate)、縮放(Scale)、翻轉(Flip),旋轉(Rotation)和錯切(Skew)。此類變換可以用一個 3*3的矩陣來表示,其最後一行為(0,0,1)。

Matrix 操作的仿射變換操作有平移(Translate),縮放(Scale),旋轉(Rotate),錯切(Skew),是以這些操作對應的矩陣最後 一行永遠是(0,0,1)。

當然 Matrix 并不隻是操作仿射變換,它還可以操作透視變換(Perspective),也就是矩陣最後一行操作的就是透視變換 。是以仿射變換其實就是透視變換的一種特殊情況。Matrix.setPolyToPoly() 提供了透視變換的操作,但是畢竟不是專業處理圖像的,是以隻能帶大家入個門吧。

下面先從數學角度推導下 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 處理的圖示

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);
}
           

}

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~~