android matrix 最全方法詳解與進階(完整篇)

1 概述

這裏我們會詳細講解matrix的各個方法,以及它的用法。matrix叫做矩陣,在前面講解ColorFilter 的文章中,我們講解了ColorMatrix,他是一個4*5的矩陣。而這裏,我們講解的Matrix不是用於處理顏色的,而是處理圖形的。他是一個3*3的矩陣。

2 原理

先看看matrix的矩陣是什麼樣子的:

這裏寫圖片描述

這裏可以查看Matrix的代碼得到。那麼這個矩陣分別代表了什麼呢,這裏通過他們的名字可以看出,scale是縮放,skew是錯切(canvas變換中有講過),trans是平移,persp代表透視(官方文檔中,也沒有詳細講解,透視在這裏只做簡單介紹)。這裏需要把矩陣根據他們的作用劃分爲4塊:

這裏寫圖片描述

如上圖所示,這四塊區域各有作用。後面會詳細講解各個作用,先來看看這個矩陣是如何影響圖像的。先看看屏幕的座標系:

這裏寫圖片描述

看上圖,這裏表示了屏幕的座標系,其中的x,y軸是大家所熟知的,但是其實,一個物體他是存在於一個三維空間的,所以必然會有z軸。我們的屏幕,就像是一個窗口,透過它,我們看到了屏幕後面的世界,那裏面有各種物體,我們看到的是映射在x,y平面上的一個投射圖像。屏幕就像是一個鏡頭一樣,將裏面的物體映射到x,y平面上,成爲一個二維的圖像。那麼如果,我們把屏幕這個鏡頭沿着z軸,拉遠或者拉進,那麼圖像會有什麼變化呢,肯定會變小或者變大。就好比坐在飛機上透過窗口看地面的汽車,和在地面上看到的大小是不同的。

結論就是,在屏幕上顯示的像素,不僅僅有x,y座標,其實還有z軸的影響。所以這裏對應的像素描述由一個3行一列的矩陣來表示:

這裏寫圖片描述

x,y分別代表x,y軸上的座標,而1代表屏幕在z軸上的座標爲默認的。如果將1變大,那麼屏幕會拉遠, 圖形會變小。

現在我們來看看matrix怎麼作用於每個像素的值。這裏需要用到矩陣的乘法,首先需要明確的是,矩陣的前乘和後乘是不相同的,也就是說不滿足乘法交換律。

這裏我們通過一個旋轉變換來看看原理,其實一張圖片圍繞一個點旋轉,也就是所有的點都圍繞一個點旋轉,所以只需要關注一個點的情況即可:

假定有一個點 ,相對座標原點順時針旋轉後的情形,同時假定P點離座標原點的距離爲r,如下圖:

這裏寫圖片描述

那麼就有:

這裏寫圖片描述

換做矩陣運算就如下圖:

這裏寫圖片描述

從這裏就可以看出,矩陣中的值,是如何作用於像素點的x,y座標以及z軸遠近。

同時,可以看到,上面的矩陣四塊區域的切分也是因爲矩陣乘法的操作決定的,由於這裏的乘法運算中,左上角的四個值,可以和x,y值做乘法運算,所以可以影響到旋轉等操作,而右上角的模塊,只能做加法,所以只能影響到平移。右下角的模塊主要管z軸,自然就可以進行等比的縮放了,左下角的模塊一般不去動他,否則會把x,y值加入到z軸中來,會不可控。

3 基本方法解析

講解完了matrix作用於像素點的原理之後,我們逐個講解它的方法。

(1) 構造函數

public Matrix()
public Matrix(Matrix src)

構造函數有兩個,第一個是直接創建一個單位矩陣,第二個是根據提供的矩陣創建一個新的矩陣(採用deep copy)

單位矩陣如下:

這裏寫圖片描述

(2) isIdentity與isAffine

public boolean isIdentity()//判斷是否是單位矩陣
public boolean isAffine()//判斷是否是仿射矩陣

是否是單位矩陣很簡單,就不做講解了,這裏是否是仿射矩陣可能大家不好理解。

首先來看看什麼是仿射變換。仿射變換其實就是二維座標到二維座標的線性變換,保持二維圖形的“平直性”(即變換後直線還是直線不會打彎,圓弧還是圓弧)和“平行性”(指保持二維圖形間的相對位置關係不變,平行線還是平行線,而直線上點的位置順序不變),可以通過一系列的原子變換的複合來實現,原子變換就包括:平移、縮放、翻轉、旋轉和錯切。這裏除了透視可以改變z軸以外,其他的變換基本都是上述的原子變換,所以,只要最後一行是0,0,1則是仿射矩陣。

(3) rectStaysRect

public boolean rectStaysRect()

判斷該矩陣是否可以將一個矩形依然變換爲一個矩形。當矩陣是單位矩陣,或者只進行平移,縮放,以及旋轉90度的倍數的時候,返回true。

(4) reset

public void reset()

重置矩陣爲單位矩陣。

(5) setTranslate

public void setTranslate(float dx, float dy)

設置平移效果,參數分別是x,y上的平移量。 
效果圖如下:

這裏寫圖片描述

代碼如下:

Matrix matrix = new Matrix();
canvas.drawBitmap(bitmap, matrix, paint);

matrix.setTranslate(100, 1000);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5

(6) setScale

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

兩個方法都是設置縮放到matrix中,sx,sy代表了縮放的倍數,px,py代表縮放的中心。這裏跟上面比較類似不做講解了。

(7) setRotate

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

和上面類似,不再講解。

(8) setSinCos

public void setSinCos(float sinValue, float cosValue, float px, float py)
public void setSinCos(float sinValue, float cosValue)

這個方法乍一看可能有點蒙,其實在前面的原理中,我們講解了一個旋轉的例子,他最終的矩陣效果是這樣的:

這裏寫圖片描述

其實旋轉,就是使用了這樣的matrix,顯而易見,這裏的參數就清晰了。 
sinValue:對應圖中的sin值 
cosValue:對應cos值 
px:中心的x座標 
py:中心的y座標

看一個示例,我們把圖像旋轉90度,那麼90度對應的sin和cos分別是1和0。

這裏寫圖片描述

看代碼如下:

Matrixmatrix = new Matrix();
matrix.setSinCos(1, 0, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3

(9) setSkew

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

錯切,這裏kx,ky分別代表了x,y上的錯切因子,px,py代表了錯切的中心。不瞭解錯切了在前面canvas變換中去查看,這裏不再講解。

(10) setConcat

public boolean setConcat(Matrix a,Matrix b)

將當前matrix的值變爲a和b的乘積,它的意義在下面的 進階方法中來探討。

4 進階方法解析

上面的基本方法中,有關於變換的set方法都可以帶來不同的效果,但是每個set都會把上個效果清除掉,例如依次調用了setSkew,setTranslate,那麼最終只有setTranslate會起作用,那麼如何才和將兩種效果複合呢。Matrix給我們提供了很多方法。但是主要都是2類:

preXXXX:以pre開頭,例如preTranslate 
postXXXX:以post開頭,例如postScale

他們分別代表了前乘,和後乘。看一段代碼:

Matrix matrix = new Matrix();
matrix.setTranslate(100, 1000);
matrix.preScale(0.5f, 0.5f);
  • 1
  • 2
  • 3

這裏matrix前乘了一個scale矩陣,換算成數學式如下:

這裏寫圖片描述

從上面可以看出,最終得出的matrix既包含了縮放信息也有平移信息。 
後乘自然就是matrix在後面,而縮放矩陣在前面,由於矩陣前後乘並不等價,也就導致了他們的效果不同。我們來看看後乘的結果:

這裏寫圖片描述

可以看到,結果跟上面不同,並且這也不是我們想要的結果,這裏縮放沒有更改,但是平移被減半了,換句話說,平移的距離也被縮放了。所以需要注意前後乘法的關係。

來看看他們對應的效果圖:

前乘:

這裏寫圖片描述

後乘:

這裏寫圖片描述

可以明顯看到,後乘的平移距離受了影響。

瞭解清除了前後乘的意義,在使用的過程中,多個效果的疊加時,一樣要注意,否則效果達不到預期。

5 其他方法解析

matrix除了上面的方法外,還有一些其他的方法,這裏依次解析

(1) setRectToRect

public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf)

將rect變換成rect,上面的rectStaysRect已經說過,要保持rect只能做縮放平移和選擇90度的倍數,那麼這裏其實也是一樣,只是這幾種變化,這裏通過stf參數來控制。

ScaleToFit 有如下四個值:

FILL: 可能會變換矩形的長寬比,保證變換和目標矩陣長寬一致。 
START:保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。左上對齊。 
CENTER: 保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。 
END:保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。右下對齊。

這裏使用谷歌的api demo的圖片作爲例子:

這裏寫圖片描述

(2) setPolyToPoly

public boolean setPolyToPoly(float[] src, int srcIndex,float[] dst, int dstIndex,int pointCount)

通過指定的0-4個點,原始座標以及變化後的座標,來得到一個變換矩陣。如果指定0個點則沒有效果。

下面通過例子分別說明1到4個點的可以達到的效果:

這裏寫代碼片##### 1個點,平移 
只指定一個點,可以達到平移效果:

這裏寫圖片描述

代碼如下:

float[] src = {0, 0};
int DX = 300;
float[] dst = {0 + DX, 0 + DX};
matrix.setPolyToPoly(src, 0, dst, 0, 1);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
2個點,旋轉或者縮放

兩個點,可以達到旋轉效果或者縮放效果,縮放比較簡單,這裏我們來看旋轉效果,一個點指定中心,一點指出旋轉的效果

這裏寫圖片描述

代碼如下

int bw = bitmap.getWidth();
int bh = bitmap.getHeight();
float[] src = {bw / 2, bh / 2, bw, 0};
float[] dst = {bw / 2, bh / 2, bw / 2 + bh / 2, bh / 2 + bw / 2};
matrix.setPolyToPoly(src, 0, dst, 0, 2);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

圖片的中心點作爲旋轉的中心,前後不變,右上角變化到了下方,所以導致圖片旋轉了90度。

3個點,錯切

使用3個點,可以產生錯切效果,指定3個頂點,一個固定,另外兩個移動。

看圖:

這裏寫圖片描述

代碼如下:

Matrix matrix = new Matrix();
int bw = bitmap.getWidth();
int bh = bitmap.getHeight();
float[] src = {0,0, 0, bh,bw,bh};
float[] dst = {0, 0, 200, bh, bw + 200, bh};
matrix.setPolyToPoly(src, 0, dst, 0, 3);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
4個點,透視

透視就是觀察的角度變化了。導致投射到平面上的二維圖像變化了。

我們看下面的例子,更容易理解:

這裏寫圖片描述

圖片看起來好像傾斜了,實現特別簡單:

Matrix matrix = new Matrix();
int bw = bitmap.getWidth();
int bh = bitmap.getHeight();
float[] src = {0, 0, 0, bh, bw, bh, bw, 0};
int DX = 100;
float[] dst = {0 + DX, 0, 0, bh, bw, bh, bw - DX, 0};
matrix.setPolyToPoly(src, 0, dst, 0, 4);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以看到,只是把左右兩個頂點往裏面收攏了,這樣就得出了一個有3d效果的透視圖。

(3) invert

public boolean invert(Matrix inverse)

反轉當前矩陣,如果能反轉就返回true並將反轉後的值寫入inverse,否則返回false。當前矩陣*inverse=單位矩陣。

反轉前後有什麼效果,我們來看看示例:

這裏寫圖片描述

可以看到,反轉之後,其實是對效果的一種反轉。

(4) mapPoints

public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,int pointCount)
public void mapPoints(float[] dst, float[] src)
public void mapPoints(float[] pts)

映射點的值到指定的數組中,這個方法可以在矩陣變換以後,給出指定點的值。 
dst:指定寫入的數組 
dstIndex:寫入的起始索引,x,y兩個座標算作一對,索引的單位是對,也就是經過兩個值才加1 
src:指定要計算的點 
srcIndex:要計算的點的索引 
pointCount:需要計算的點的個數,每個點有兩個值,x和y。

(5) mapVectors

public void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex,int vectorCount)
public void mapVectors(float[] dst, float[] src)
public void mapVectors(float[] vecs)

與上面的mapPoionts基本類似,這裏是將一個矩陣作用於一個向量,由於向量的平移前後是相等的,所以這個方法不會對translate相關的方法產生反應,如果只是調用了translate相關的方法,那麼得到的值和原本的一致。

(6) mapRect

public boolean mapRect(RectF dst, RectF src)
public boolean mapRect(RectF rect)

返回值即是調用的rectStaysRect(),這個方法前面有講過,這裏把src中指定的矩形的左上角和右下角的兩個點的座標,寫入dst中。

(7) mapRadius

public float mapRadius(float radius)

返回一個圓圈半徑的平均值,將matrix作用於一個指定radius半徑的圓,隨後返回的平均半徑。

以上基本解析完畢了所有matrix的方法,以及一些高階用法,本篇文章就到這裏

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