1. 程式人生 > >Android Matrix原始碼詳解

Android Matrix原始碼詳解

尊重原創,轉載請標明出處   http://blog.csdn.net/abcdef314159

Matrix是一個3*3的矩陣,通過矩陣執行對影象的平移,旋轉,縮放,斜切等操作。先看一段程式碼

    public static final int MSCALE_X = 0;   //!< use with getValues/setValues
    public static final int MSKEW_X  = 1;   //!< use with getValues/setValues
    public static final int MTRANS_X = 2;   //!< use with getValues/setValues
    public static final int MSKEW_Y  = 3;   //!< use with getValues/setValues
    public static final int MSCALE_Y = 4;   //!< use with getValues/setValues
    public static final int MTRANS_Y = 5;   //!< use with getValues/setValues
    public static final int MPERSP_0 = 6;   //!< use with getValues/setValues
    public static final int MPERSP_1 = 7;   //!< use with getValues/setValues
    public static final int MPERSP_2 = 8;   //!< use with getValues/setValues

在Matrix中表示為

在研究矩陣之前先來了解一下矩陣的原理,在學過線性代數的都知道,矩陣不具有乘法的交換律。一般情況下影象的點由螢幕上的座標所確定,螢幕上的座標一般是由(x,y)所確定,因為Matrix是一個3*3矩陣,所以這裡又添加了一個座標z,在立體空間一個點是由(x,y,z)三個座標來確定的。如果z越大螢幕就會拉遠,圖片就會越小。


AB相乘結果為


下面就來看一看Matrix的一些主要的方法,

    /**
     * Returns true if the matrix is identity.
     * This maybe faster than testing if (getType() == 0)
     */
    public boolean isIdentity() {
        return native_isIdentity(native_instance);
    }
判斷矩陣是否是單位矩陣,是就返回true,否則就返回false。所謂單位矩陣就是從左上角到右下角的對角線上的元素均為1。除此以外全都為0的矩陣,也就是下面這種


看一下程式碼

		mMatrix = new Matrix();
		mMatrix.setValues(new float[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 });
		Log.d("wld_________", mMatrix.isIdentity() + "");
在看一下log


說明上面矩陣不是單位矩陣,再來修改一下程式碼

		mMatrix.setValues(new float[] { 1, 0, 0, 0, 1, 0, 0, 0, 1 });
再看一下列印log


列印true,說明上面的矩陣是單位矩陣。

isAffine()表示這個矩陣是否是仿射的,仿射變換是從二維座標到二維座標之間的線性變換,且保持二維圖形的“平直性”和“平行性”,即直線還是直線,曲線還是曲線。仿射變換可以通過一系列的原子變換的複合來實現,包括平移,縮放,旋轉,斜切。這類變換可以用一個3*3的矩陣M來表示,其最後一行為(0,0,1),代入上面A*B的公式可以發現原來x,y的座標並不會對z產生影響,且z方向上的值只會保留不變。該變換矩陣可能會改變線的長度和夾角,但不會改變線段上點的順序,以及線段之間的比例,比如兩個三角形通過仿射變換之後他們的面積比還是不變的,但面積可能會變,更通俗一點講就是有一束平行的光線通過一個平面把平面上的影象投射到另一個平面上,平面可以是任意角度,但不能與光速平行,這樣投影的結果無論是拉伸,平移,旋轉還是斜切,都能保證他的整體比例不變,且具有以下性質

1,使共線點變為共線點的雙射, 且對應點連線相互平行。
2,平行直線變為平行直線;
3, 保持共線三點的簡單比, 從而保持兩平行線段的比值不變.

其中最主要一點就是他的平行關係還繼續保持,如果平行關係不保持就變成了投影變換。先來看一段程式碼

		mMatrix = new Matrix();
		mMatrix.setValues(new float[] { 1, 1, 2, 5, 1, 0, 1, 0, 1 });
		Log.d("wld_________", mMatrix.isAffine() + "");
由於最後一行不是0,0,1,所以不是仿射變換矩陣,我們看一下列印log

再來修改一下,讓最後一行為0,0,1

		mMatrix = new Matrix();
		mMatrix.setValues(new float[] { 1, 1, 2, 5, 1, 0, 0, 0, 1 });
		Log.d("wld_________", mMatrix.isAffine() + "");
看一下結果,返回true,說明是仿射變換。


具體可以參考:http://www.cnblogs.com/ghj1976/p/5199086.html,裡面介紹的很詳細,這裡就不在介紹。

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

set(Matrix src):把src矩陣複製到這個矩陣中,如果src為null,則重置當前矩陣為單位矩陣。

reset():重置當前矩陣為單位矩陣,我們來看一下

		mMatrix = new Matrix();
		mMatrix.setValues(new float[] { 1, 1, 2, 5, 1, 0, 0, 0, 1 });
		Log.d("wld_________", mMatrix.isIdentity() + "");
		mMatrix.reset();
		Log.d("wld_________", mMatrix.isIdentity() + "");
		float values []=new float[9];
		mMatrix.getValues(values);
		Log.d("wld_________", Arrays.toString(values));

再看一下列印log


我們看到剛開始的時候設定的不是單位矩陣,呼叫reset()方法之後,變成了單位矩陣。

setTranslate(float dx, float dy),平移操作,因為平移是相對於原來的位置,所以只需要兩個引數就夠了,我們看一下上面的矩陣A,MTRANS_X和MTRANS_Y是分別表示x方向和y方向的平移量,因為Matrix初始化時候的矩陣是單位矩陣[1,0,0,0,1,0,0,0,1],假如setTranslate(20, 30)則矩陣變為[1,0,20,0,1,30,0,0,1];假如原來的位置為(x,y,1),代入上面的A*B公式,結果為(x+20,y+30,1),如果用矩陣表示為


其中x0,y0分別為x軸方向和y軸方向的偏移量。我們看一下程式碼

		mMatrix = new Matrix();
		float values[] = new float[9];
		mMatrix.getValues(values);
		Log.d("wld_________", Arrays.toString(values));
		mMatrix.setTranslate(20, 30);
		float values1[] = new float[9];
		mMatrix.getValues(values1);
		Log.d("wld_________", Arrays.toString(values1));
看一下列印結果


我們看到,初始化的時候是單位矩陣,通過平移,矩陣MTRANS_X和MTRANS_Y分別變成了20,和30。通過具體數字可能不是很明白,下面通過一張圖來演示一下,先看一下程式碼

public class MatrixView extends View {

	private Matrix mMatrix;
	private Bitmap mBitmap;
	private Paint mPaint;

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

	private void init() {
		mMatrix = new Matrix();
		mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
		mMatrix.setTranslate(100, 200);
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
	}
}
看一下執行結果


我們看到圖片x軸方向移動100,豎直方向移動200.

setScale(float sx, float sy, float px, float py)表示圖片的縮放,其中sx,sy是縮放的比例,(px,py)是縮放的軸點,關於矩陣的縮放有兩個引數MSCALE_X,MSCALE_Y,這個可以自己看一下,就不在介紹,我們看一下程式碼

		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
		mMatrix.setScale(1.5f, 1.5f, mBitmap.getWidth(), mBitmap.getHeight());
		canvas.translate(0, 2*mBitmap.getHeight());
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);

上面是原始的圖片,然後通過矩陣在x和y方向都放大1.5倍,放大的參照點在圖片的右下角位置,為了防止兩張圖片重疊,我把放大的圖片往下移動了一點距離。這個有一點要注意,移動為什麼不用之前的setTranslate方法,因為在Matrix中以set開頭的方法都會把之前的set清除,這個我們待會再演示,先看一下上面程式碼的效果圖。


我們看到下面的圖是比上面的圖放到1.5倍,並且以右下角為參照點的。同理,當sx,sy小於1的時候圖片會縮小,這個就不在演示,如果為負的會怎麼樣,我們修改程式碼看一下

		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
		mMatrix.setScale(-.5f, -.5f, mBitmap.getWidth(), mBitmap.getHeight());
//		canvas.translate(0, 2*mBitmap.getHeight());
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
看一下執行結果


再來修改一下程式碼

		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
		mMatrix.setScale(1, -.5f, mBitmap.getWidth(), mBitmap.getHeight());
//		canvas.translate(0, 2*mBitmap.getHeight());
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
看一下結果


x軸方向不變,y軸為負且縮放一半。如果大家看過我之前的Android Paint之ColorFilter詳解,是不是可以參照做一個倒影的效果,這個就不在演示。上面說到在matrix的set方法會把之前的矩陣清除,也就是先變為單位矩陣在set,我們結合程式碼來看一下

		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
		float values1[] = new float[9];
		mMatrix.getValues(values1);
		Log.d("wld_________", Arrays.toString(values1));
		mMatrix.setScale(1, -.5f, mBitmap.getWidth(), mBitmap.getHeight());
		float values2[] = new float[9];
		mMatrix.getValues(values2);
		Log.d("wld_________", Arrays.toString(values2));
		mMatrix.setTranslate(100, 100);
		float values3[] = new float[9];
		mMatrix.getValues(values3);
		Log.d("wld_________", Arrays.toString(values3));
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
我們看一下執行結果


我們看到縮放的方法沒有執行,只執行了下面的平移方法,因為前面的縮放方法平移的時候重置了,我們看一下列印的log


首先Matrix初始化的時候是單位矩陣,縮放的的時候我們看第二個log,A中的MSCALE_X,MSCALE_Y,MTRANS_Y都變了,其中前兩個是縮放的比例,因為縮放的y方向是負的,所以縮放之後的影象往下平移的,所以我們看到MTRANS_Y是有值的,但這個值很奇葩,他是原來圖片最上端減去縮放之後圖片上端的距離,但縮放之後圖片的上端倒過來了,所以他移動的距離相當於圖片的高度加上縮放之後圖片的高,相當於移動了圖片高度的1.5倍,如果我們改一下程式碼

mMatrix.setScale(1,2f, mBitmap.getWidth(), mBitmap.getHeight());

那麼會得到MTRANS_Y的值為-462,正好相當於圖片的高度,因為圖片放大了2倍且沒有倒過來,所以相減為負。這裡log就不在貼出,我們再看上面的列印的第三個log,把第二個log列印的值清除了,又重新設了值,因為剛才說過Matrix的set方法會清除之前設定的值。如果想要平移,縮放等所有操作都一起設定該怎麼辦呢,那麼就要用到待會下面要講的pre和post開頭的方法了。

setScale(float sx, float sy)和上面的方法類似,表示縮放,不過他的縮放點是在左上角,看一下程式碼

		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
		mMatrix.setScale(.3f, .3f);
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
執行結果如下,我們看到參照點是左上角。


為了具體瞭解圖形的縮放,下面來畫個圖加深一下印象


如果用矩陣表示,如下


我們來驗證一下,看程式碼

		canvas.drawBitmap(mBitmap, 0, 0, mPaint);
		mMatrix.setScale(0.4f, .3f, 60, 90);
		float values[] = new float[9];
		mMatrix.getValues(values);
		Log.d("wld_________", Arrays.toString(values));
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
先猜想一下列印的log,其中(60,90)是偏移量,代入上面矩陣得到[0.4,0,36,0,0.3,63,0,0,1],我們再來看一下列印log


結果完全一樣。

setRotate(float degrees, float px, float py)和setRotate(float degrees)表示旋轉,(px,py)表示旋轉的參照點,第二個方法的參照點為(0,0),degrees是旋轉的角度,當大於360度的時候,會對360求餘,看一下程式碼

		// 原始圖
		canvas.drawBitmap(mBitmap, 0, 0, mPaint);
		// 以(0,0)為參照點旋轉60度
		mMatrix.setRotate(60);
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
		// 以圖片右下角為參照點旋轉30度
		mMatrix.setRotate(30, mBitmap.getWidth(), mBitmap.getHeight());
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);

		// 畫布往下平移
		canvas.save();
		canvas.translate(0, 2 * mBitmap.getHeight());
		// 旋轉角度大於360
		mMatrix.setRotate(390, mBitmap.getWidth(), mBitmap.getHeight());
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
		canvas.restore();

		// 畫布往下平移
		canvas.save();
		canvas.translate(0, 2 * mBitmap.getHeight());
		// 旋轉角度為負
		mMatrix.setRotate(-30, mBitmap.getWidth(), mBitmap.getHeight());
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
		canvas.restore();
看一下執行結果


我們看到,所有的角度為正的時候全都是按順時針方向,如果為負的時候是按照逆時針方向旋轉的,注意旋轉的參考點。我們看到矩陣的陣列中有MSCALE(縮放),MSKEW(錯切),MTRANS(平移),但我們好像沒有看到有旋轉的,我們修改一下程式碼,然後列印一下log

		// 原始圖
		canvas.drawBitmap(mBitmap, 0, 0, mPaint);
		// 以(0,0)為參照點旋轉60度
		mMatrix.setRotate(60);
		float values[] = new float[9];
		mMatrix.getValues(values);
		Log.d("wld_________", Arrays.toString(values));
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
這個就是上面順時針旋轉60度的影象,就不在貼出,下面我們看一下列印的log


這個大家可能有點迷茫,看不太懂,那麼我們就分析一下


假如有一點為P1,座標為(x1,y1),繞座標系中的另一個點p0(x0,y0)旋轉,假如p1與水平方向的夾角為α,繞過β角度之後的點為p,座標為(x,y),則可以通過上面的公式計算出p點的座標,如果轉化為我們這篇所講的矩陣表示為


通過上面矩陣我們看到,圖片的旋轉只和旋轉的角度β與旋轉的參考點(x0,y0)有關,和其他所有的引數一律無關,也難怪setRotate方法中只需要傳入旋轉的角度和旋轉的參考點就可以旋轉。上面的矩陣是仿射矩陣,我們還可以把x0,y0提取出來,繼續轉換


我們看到中間的那個矩陣還可以轉化,最終可轉化為


OK,分析到這大家應該很明白了吧,先看下面這個矩陣


由上面的平移我們知道,當x0,y0為正的時候是往右下角平移的,所以他相當於把p1點往左上角平移了,我們可以這樣理解,p1所在的座標系往右上角平移了(x0,y0),正好p0點和原座標的原點重合了,然後下面矩陣


相當於繞原點旋轉了β角度,然後看下面的矩陣


相當於又回到了原來的地方,所以這下應該很明白了吧,接著我們來看一下上面列印的log

[0.49999997, -0.86602545, 0.0, 0.86602545, 0.49999997, 0.0, 0.0, 0.0, 1.0]
因為上面的影象是圍繞原點旋轉的,所以x0,y0等於0,β為60度,代入矩陣


所以最終結果為[cos60,-sin60,0,sin60,cos60,0,0,0,1],正好和上面列印log相差無幾,為啥說相差無幾,因為cos60是等於0.5,但上面列印的可能由於精度問題導致不等於0.5,不過這並不影響。為了進一步驗證,我們在打一下

		// 原始圖
		canvas.drawBitmap(mBitmap, 0, 0, mPaint);
		// 以(0,0)為參照點旋轉60度
		mMatrix.setRotate(60, 20, 30);
		float values[] = new float[9];
		mMatrix.getValues(values);
		Log.d("wld_________", Arrays.toString(values));
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
這個不是繞原點旋轉,我們先不看log,先來分析一下,代入上面矩陣公式,結果應該為[cos60,-sin60,20(1-cos60)+30sin60,sin60,cos60,30(1-cos60)-20sin60,0,0,1],進一步換算[0.5,-sin60,35.98,sin60,cos60,-2.32,0,0,1],2sin60取的是1.732,我們再來看一下列印的log


結果和我們計算的基本一致。

setSkew(float kx, float ky, float px, float py)和setSkew(float kx, float ky)表示錯切,很明顯後面的方法是相對於原點的操作,我們先來了解一下什麼叫做錯切,在百度百科上有這樣一句話:錯切是在某方向上,按照一定的比例對圖形的每個點到某條平行於該方向的直線的有向距離做放縮得到的平面圖形。在維基百科上是這樣描述的


上面所說的錯切有兩種,一種是水平錯切,一種是垂直錯切,用我的理解來說是一個矩形拉著他的兩個對角,把他變成平行四邊形。先畫個圖來了解一下


實際上錯切應該還有一種就是水平錯切和垂直錯切結合的,用矩陣表示為


公式推算畢竟太過枯燥,我們還是來看一下例項演示,看程式碼

		canvas.drawBitmap(mBitmap, 0, 0, mPaint);
		mMatrix.setSkew(1.5f,1.2f);
		float values[] = new float[9];
		mMatrix.getValues(values);
		Log.d("wld_________", Arrays.toString(values));
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
先不看列印的log,猜想一下log列印的結果,由於相對於原點錯切,由上面的矩陣x0,y0都為0,所以最終結果為[1,1.5,0,1.2,1,0,0,0,1];下面來看一下列印的log,驗證一下是否正確


我們看到,列印結果和我們猜想的一模一樣,再來看一下效果圖


他相對於原點錯切,也就是相對於左上角,並且x方向和y方向同時錯切,在修改一下程式碼

		mMatrix.setSkew(0f,.8f);
執行結果


如果修改不同的值,我們會看到意想不到的效果,這裡就不在演示。我們再來看一下不是相對原點的錯切,看程式碼

		canvas.drawBitmap(mBitmap, 0, 0, mPaint);
		mMatrix.setSkew(1.5f, 1.3f, 200, 300);
		float values[] = new float[9];
		mMatrix.getValues(values);
		Log.d("wld_________", Arrays.toString(values));
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
先來猜想一下列印的log,由上面推倒的公式可知結果為[1,1.5,-450,1.3,1,-260,0,0,1],然後看一下列印的log


結果完全一致。順便再看一下圖形


OK,到目前為止,圖形的平移,縮放,旋轉,錯切都已經分析完畢,除了平移以外,其他的都有參考點,當x0,y0都為0的時候是相對於原點的操作,這個是最簡單的,可以代入上面的矩陣看一下,這裡就不在一一繪出。接著繼續來看Matrix的其他方法

setSinCos(float sinValue, float cosValue, float px, float py),setSinCos(float sinValue, float cosValue)通過指定的sin和cos來設定旋轉,我們看到上面旋轉的矩陣中包含sin,con以及旋轉的參考點,我們一般認為,sin及cos都是小於等於1並且大於等於-1的,但這個方法中我們能不能把sinValue及cosValue的值設定為大於1或小於-1呢,實際上是可以的,這樣圖形就會放大或縮放或反方向形變,還有一個問題,我們把旋轉的矩陣和上面的矩陣A對比一下就會發現,旋轉的時候MSCALE_X和MSKEW也跟著變了,也就是說旋轉會導致圖形縮放和錯切,是不是這樣的呢,我們先來看一段程式碼

		Log.d("wld________", "getWidth=" + mBitmap.getWidth() + "&&getHeight=" + mBitmap.getHeight());
		canvas.drawBitmap(mBitmap, 0, 0, mPaint);
		mMatrix.setSinCos(.866f, .5f,100,100);
		Log.d("wld________", "getWidth=" + mBitmap.getWidth() + "&&getHeight=" + mBitmap.getHeight());
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
程式碼是讓圖形旋轉60度,sin60約等於0.866,cos60為0.5,再來看一下列印的log


在縮放和錯切的共同作用下,導致圖片的大小並沒有改變。

setConcat(Matrix a, Matrix b)將矩陣a和b進行合併,我們看一下他的註釋

     * Set the matrix to the concatenation of the two specified matrices and
     * return true.
     *
     * <p>Either of the two matrices may also be the target matrix, that is
     * <code>matrixA.setConcat(matrixA, matrixB);</code> is valid.</p>
     *
我們可以這樣呼叫matrixA.setConcat(matrixA, matrixB),合併的結果是矩陣a與b的乘積除以2,怎麼說呢,看一下程式碼
		Matrix mMatrix1=new Matrix();
		mMatrix1.setValues(new float[]{1,2,3,4,5,6,7,8,9});
		Matrix mMatrix2=new Matrix();
		mMatrix2.setValues(new float[]{5,1,3,2,2,3,4,5,2});
		mMatrix1.setConcat(mMatrix1, mMatrix2);
		Log.d("wld_________", mMatrix1.toShortString());
		Log.d("wld_________", mMatrix2.toShortString());

很顯然,最終的結果會放到mMatrix1中,我們猜一下結果,矩陣mMatrix1與mMatrix2的乘積為[21,20,15,54,44,39,87,68,63],除以2結果為[10.5,10,7.5,27,22,19.5,43.5,34,31.5],我們再來看一下列印的log


和計算的結果一模一樣。當然如果改為

mMatrix2.setConcat(mMatrix1, mMatrix2);
那麼計算的結果就會儲存到mMatrix2中,如果我們在new一個新的mMatrix3,呼叫mMatrix3.setConcat(mMatrix1, mMatrix2);,結果就會儲存到mMatrix3中。

然後接著就是以pre和post開頭的一些方法,pre開頭的表示前乘,就是矩陣在前面,比如M' = M * T(dx, dy),post開頭的表示後乘,就是矩陣在後面,比如M' = T(dx, dy) * M。因為上面說過,以set開頭的會把之前的矩陣置為單位矩陣,然後在set,如果想要上面的幾種方式一起操作,則需要使用pre或post開頭的方法,看程式碼

		mMatrix.setScale(0.4f, .3f);
		mMatrix.postTranslate(600, 600);
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
我們暫且可以這樣理解,圖形先縮放然後在平移,究竟是先縮放還是先平移,這個都不重要,因為他是矩陣相乘的最終結果作用在圖形上的,看一下圖形所在的位置


下面在修改一下程式碼

		mMatrix.setScale(0.4f, .3f);
		mMatrix.preTranslate(600, 600);
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
再看一下圖形所在的位置


差別竟那麼大,為什麼會這樣,因為在開頭的時候說過矩陣不具有乘法的交換律,矩陣的前乘和後乘可能會出現不同的結果。我們可以想象一下,上面一個圖形是先縮放然後在平移(我們暫且這樣認為,不管他對不對,因為這樣有助於我們理解),那麼他的左上角點的座標肯定為(600,600)。下面一個圖形相當於先平移然後在縮放,如果按照我們正常人的思維,我們肯定會認為無論是先平移還是先縮放,最終的位置肯定是一樣的,沒錯,確實只這樣。但這裡我們忽略了一個問題,就是上面講的縮放的參考點,上面一個圖形的縮放點在圖形的左上角,但下面那個圖形由於平移,導致他的縮放點不在圖形的左上角。如果我們想要和上面一個圖形的位置一樣,改怎麼辦呢,我們還可以這樣寫,看程式碼

		mMatrix.setScale(0.4f, .3f, 600, 600);
		mMatrix.preTranslate(600, 600);
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
讓他縮放的參考點還是在圖形的左上角上就行了,如果想和下面那個圖形的位置一樣,還可以這樣寫
		mMatrix.setScale(0.4f, .3f,-600,-600);
		mMatrix.postTranslate(600, 600);
		canvas.drawBitmap(mBitmap, mMatrix, mPaint);
其實這只是一個演示,告訴我們可以這樣實現,但在工作中沒必要搞這麼複雜,知道就行。下面就通過矩陣來了解一下前乘和後乘的區別,先看setScale(0.4f, .3f),會把單位矩陣置為


然後呼叫postTranslate(600, 600),我們知道post是後乘,即M' = T(dx, dy) * M,之前矩陣在後面,我們看一下相乘的結果


我們再來看一下setScale(0.4f, .3f)和preTranslate(600, 600)的結果,pre開頭的是前乘,M' = M * T(dx, dy) ,即之前矩陣在前,我們看一下


所以兩個矩陣交換位置相乘的結果完全不同,上面一個矩陣相當於移動了(600,600),下面一個矩陣相當於移動了(240,180),所以我們看到上面圖形中兩個位置是不一樣的。還有,如果呼叫了set方法,那麼之前的所有set,post和pre開頭的方法都將作廢,即相當於重置。由於Matrix中平移,縮放,旋轉,錯切都具有set,pre和post開頭的方法,想怎麼使用,在工作中可以根據需要來進行自由組合,這裡就不在一一演示。

setRectToRect(RectF src, RectF dst, ScaleToFit stf),將src矩形的內容填充到dst矩形中,其中ScaleToFit是列舉類,有四種類型,我們看一下注釋

    /** Controlls how the src rect should align into the dst rect for
        setRectToRect().
    */
    public enum ScaleToFit {
        /**
         * Scale in X and Y independently, so that src matches dst exactly.
         * This may change the aspect ratio of the src.
         */
        FILL    (0),
        /**
         * Compute a scale that will maintain the original src aspect ratio,
         * but will also ensure that src fits entirely inside dst. At least one
         * axis (X or Y) will fit exactly. START aligns the result to the
         * left and top edges of dst.
         */
        START   (1),
        /**
         * Compute a scale that will maintain the original src aspect ratio,
         * but will also ensure that src fits entirely inside dst. At least one
         * axis (X or Y) will fit exactly. The result is centered inside dst.
         */
        CENTER  (2),
        /**
         * Compute a scale that will maintain the original src aspect ratio,
         * but will also ensure that src fits entirely inside dst. At least one
         * axis (X or Y) will fit exactly. END aligns the result to the
         * right and bottom edges of dst.
         */
        END     (3);

        // the native values must match those in SkMatrix.h
        ScaleToFit(int nativeInt) {
            this.nativeInt = nativeInt;
        }
        final int nativeInt;
    }
首先我們看一下上面的註釋,

FILL:表示分別縮放x,y,讓src更準確的匹配dst,但這可能會改變src的長寬比。就是不成比例的縮放,完全填充dst

START:表示計算一個合適的值,保留src的長寬比,但要確保src完全在dst中,x,y方向上至少有一個要完全填充dst,要沿著dst的左端和頂端。

CENTER:和上面類似,也是保證縮放的長寬比,也是完全在dst中,也是至少有一個邊要完全填充dst,但不同的是他會在dst的中間。

END:和START類似,不過他是沿著dst的右端和底端。

下面老規矩,看程式碼

public class MatrixView extends View {

	private Bitmap mBitmapV;
	private Bitmap mBitmapH;
	private Paint mPaint;
	private RectF hSrcRectF;
	private RectF hDstRectF;
	private RectF vSrcRectF;
	private RectF vDstRectF;
	private ScaleToFit mScaleToFit[] = { ScaleToFit.CENTER, ScaleToFit.END, ScaleToFit.FILL, ScaleToFit.START };
	private Matrix mMatrix;
	private TextPaint mTextPaint;

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

	private void init() {
		mBitmapV = BitmapFactory.decodeResource(getResources(), R.drawable.v);
		mBitmapH = BitmapFactory.decodeResource(getResources(), R.drawable.h);
		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mPaint.setStyle(Style.FILL_AND_STROKE);
		mPaint.setColor(Color.BLUE);
		hSrcRectF = new RectF(0, 0, mBitmapH.getWidth(), mBitmapH.getHeight());
		hDstRectF = new RectF(0, 0, mBitmapH.getWidth() * 1.5f, mBitmapV.getHeight() * 1.5f);

		vSrcRectF = new RectF(0, 0, mBitmapV.getWidth(), mBitmapV.getHeight());
		vDstRectF = new RectF(0, 0, mBitmapH.getWidth() * 1.5f, mBitmapV.getHeight() * 1.5f);
		mMatrix = new Matrix();
		mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
		mTextPaint.setTextSize(56);
		mTextPaint.setColor(Color.RED);

	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawBitmap(mBitmapH, 260, 100, null);
		canvas.drawBitmap(mBitmapV, 700, 100, null);
		canvas.drawText("原圖", 40, 200, mTextPaint);
		for (int i = 0; i < mScaleToFit.length; i++) {
			canvas.drawText(mScaleToFit[i].toString(), 40,
					100 + (i + 1) * (hDstRectF.height() + 60) + hDstRectF.height() / 2, mTextPaint);
			canvas.save();
			canvas.translate(300, 100 + (i + 1) * (hDstRectF.height() + 60));
			canvas.drawRect(hDstRectF, mPaint);
			mMatrix.reset();
			mMatrix.setRectToRect(hSrcRectF, hDstRectF, mScaleToFit[i]);
			canvas.drawBitmap(mBitmapH, mMatrix, null);
			mMatrix.reset();
			canvas.translate(400, 0);
			canvas.drawRect(vDstRectF, mPaint);
			mMatrix.setRectToRect(vSrcRectF, vDstRectF, mScaleToFit[i]);
			canvas.drawBitmap(mBitmapV, mMatrix, null);
			canvas.restore();
		}
	}
}
在看一下執行結果


最上面兩個是原圖,下面的8個圖中,藍色部分是dst,紅色是原圖進行縮放後的圖,比較簡單,和上面的描述一模一樣,這裡就不在分析。這裡的dst都是大於src的,如果小於src會怎麼樣呢,其實他會縮放的,我們修改程式碼看一下

		hSrcRectF = new RectF(0, 0, mBitmapH.getWidth(), mBitmapH.getHeight());
		hDstRectF = new RectF(0, 0, mBitmapH.getWidth() * .5f, mBitmapV.getHeight() * .5f);

		vSrcRectF = new RectF(0, 0, mBitmapV.getWidth(), mBitmapV.getHeight());
		vDstRectF = new RectF(0, 0, mBitmapH.getWidth() * .5f, mBitmapV.getHeight() * .5f);
看一下執行結果


setPolyToPoly(float[] src, int srcIndex,float[] dst, int dstIndex,int pointCount)表示把圖形的四個點對映到另外一個圖形中,src是原圖的需要對映的座標,以(x,y)成對出現的,srcIndex指原圖對映的起始點,dst指目標圖,dstIndex指目標圖的起始點,pointCount指對映的點數,最多是4個,左上,右上,左下,右下分別為1,2,3,4四個點,還是先看一段程式碼

public class MatrixView extends View {

	private Matrix mMatrix;
	private Path mPath;
	private Bitmap mBitmap;
	private Paint mPaint;
	private Paint mSrcPaint;
	private Paint mDstPaint;

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

	private void init() {
		mMatrix = new Matrix();
		mPath = new Path();
		mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);

		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mPaint.setStyle(Style.STROKE);
		mPaint.setStrokeWidth(12);// 防止被下面的線覆蓋,畫寬一點。
		mPaint.setColor(Color.RED);

		mSrcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mSrcPaint.setStyle(Style.STROKE);
		mSrcPaint.setStrokeWidth(8);
		mSrcPaint.setColor(Color.BLUE);

		mDstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mDstPaint.setStyle(Style.STROKE);
		mDstPaint.setStrokeWidth(8);
		mDstPaint.setColor(Color.YELLOW);

	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.drawBitmap(mBitmap, 0, 0, null);
		float src[] = { 0, 0, mBitmap.getWidth(), 0, 0, mBitmap.getHeight(),
				mBitmap.getWidth(), mBitmap.getHeight() };
		float dst[] = { 300, 300, mBitmap.getWidth() + 100, 30, 100,
				mBitmap.getHeight() + 200, mBitmap.getWidth() + 300,
				mBitmap.getHeight() + 300 };
		float array[] = { 0, 0, mBitmap.getWidth(), 0, 0, mBitmap.getHeight(),
				mBitmap.getWidth(), mBitmap.getHeight() };

		drawPath(canvas, array, mPaint);
		drawPath(canvas, src, mSrcPaint);
		drawPath(canvas, dst, mDstPaint);

		canvas.translate(0, 800);
		mMatrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1);
		canvas.drawBitmap(mBitmap, mMatrix, null);

		drawPath(canvas, array, mPaint);
		drawPath(canvas, src, mSrcPaint);
		drawPath(canvas, dst, mDstPaint);

	}

	private void drawPath(Canvas canvas, float array[], Paint mPaint) {
		mPath.reset();
		mPath.moveTo(array[0], array[1]);
		mPath.lineTo(array[2], array[3]);
		mPath.lineTo(array[6], array[7]);
		mPath.lineTo(array[4], array[5]);
		mPath.close();
		canvas.drawPath(mPath, mPaint);
	}
}

看一下效果圖



他是用原圖四個角的座標對映到另一個四邊形中,把藍色部分對映到黃色部分,為了便於觀察,上面一個圖形是原始圖,下面一個圖是對映之後的,當然,我們還可以對映3個點,或2個點,看一下,下面從左往右分別是3個點,2個點,1個點和0個點的對映

3個點和2個點

                                               

1個點和0個點

                  

當然,如果只是想對映原圖中的一部分,還可以這樣改,
		float src[] = { 300, 100, mBitmap.getWidth(), 0, 0, mBitmap.getHeight(), mBitmap.getWidth(),
				mBitmap.getHeight() };

看一下執行結果



在修改程式碼看一下

		float src[] = { 300, 100, mBitmap.getWidth(), 0, 0, mBitmap.getHeight()-100, mBitmap.getWidth()-100,
				mBitmap.getHeight() };
看截圖



我們可以很明顯看到,上面圖中把原圖擷取藍色部分,然後拉伸放到黃色部分中,下面一個圖由於螢幕不夠所以左右兩邊沒有完全顯示,至於擷取之外的部分是怎麼排放的,這個就真的不知道了。不過這種方法用的不是很多,一般都是對映原圖的四個點,或者對映原圖的一部分到一個矩形中,比如網上常見的圖片摺疊。

public boolean invert(Matrix inverse)如果矩陣可逆,求當前矩陣的逆矩陣並且存入inverse中,如果當前矩陣不可逆,忽略 inverse,返回false, 當前矩陣*inverse(逆矩陣)=單位矩陣,我們看一下程式碼

		Matrix mMatrix1 = new Matrix();
		Matrix mMatrix2 = new Matrix();
		Matrix mMatrix3 = new Matrix();

		mMatrix1.setValues(new float[] { 1, 2, 2, 3, 3, 5, 2, 3, 5 });
		Log.d("wld________", mMatrix1.invert(mMatrix2) + "");
		mMatrix3.setConcat(mMatrix1, mMatrix2);
		Log.d("wld________mMatrix1=", mMatrix1.toShortString());
		Log.d("wld________mMatrix2=", mMatrix2.toShortString());
		Log.d("wld________mMatrix3=", mMatrix3.toShortString());

先不看列印log,分析一下mMatrix1的逆矩陣mMatrix2的值,學過線性代數的都知道逆矩陣的演算法,下面來推導一下,可能不是最好的演算法,但這都不重要,我們要的只是結果,畢竟過去那麼多年,也忘的差不多了,


所以我們看到,最終得到的逆矩陣就是上面的最後3排,也就是【0,1,-1,5/4,-1/4,-1/4,-3/4,-1/4,3/4】,我們再來看一下列印的log


我們看到返回為true,說明上面的矩陣是可逆的,同時還看到列印的mMatrix2正好和我們推導的一樣,他就是mMatrix1的逆矩陣,同時還注意到mMatrix3,是一個單位矩陣,他是當前矩陣mMatrix1和他的逆矩陣mMatrix2相乘的結果。但並非所有的矩陣都有逆矩陣,比如下面一個矩陣

		mMatrix1.setValues(new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
		Log.d("wld________", mMatrix1.invert(mMatrix2) + "");
		mMatrix3.setConcat(mMatrix1, mMatrix2);
		Log.d("wld________mMatrix1=", mMatrix1.toShortString());
		Log.d("wld________mMatrix2=", mMatrix2.toShortString());
		Log.d("wld________mMatrix3=", mMatrix3.toShortString());
看一下列印log


我們看到,返回為false,說明這個矩陣是不可逆的,同時還看到mMatrix2是一個單位矩陣,並不是mMatrix1的逆矩陣。在改一下程式碼看一下

		mMatrix1.setValues(new float[] { 1, 2, 2, 3, 3, 5, 2, 3, 5 });
//		mMatrix1.setValues(new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
		Log.d("wld________", mMatrix1.invert(mMatrix2) + "");
//		mMatrix3.setConcat(mMatrix1, mMatrix2);
		mMatrix2.invert(mMatrix3);
		Log.d("wld________mMatrix1=", mMatrix1.toShortString());
		Log.d("wld________mMatrix2=", mMatrix2.toShortString());
		Log.d("wld________mMatrix3=", mMatrix3.toShortString());

看一下列印log


我們看到mMatrix1和mMatrix3是一樣的,這說明逆矩陣的逆矩陣還是原矩陣。那麼逆矩陣有什麼作用呢,其實就相當於整數的倒數一樣,逆矩陣和原矩陣相乘等於單位矩陣,矩陣中沒有直接相除的概念,那麼逆矩陣就可以實現矩陣相除。我們看一下程式碼

public class MatrixView extends View {

	private Matrix mMatrix1;
	private Matrix mMatrix2;
	private Bitmap mBitmap;
	private Paint mPaint;

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

	private void init() {
		mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		mPaint.setColor(Color.RED);
		mPaint.setStyle(Style.FILL_AND_STROKE);
		mPaint.setStrokeWidth(8);

		mMatrix1 = new Matrix();
		mMatrix2 = new Matrix();
		mMatrix1.setValues(new float[] { 1, 2, 2, 3, 3, 5, 2, 3, 5 });
		mMatrix1.setTranslate(100, 200);
		Log.d("wld________", mMatrix1.invert(mMatrix2) + "");
		Log.d("wld________mMatrix1=", mMatrix1.toShortString());
		Log.d("wld________mMatrix2=", mMatrix2.toShortString());
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		canvas.translate(300, 300);
		canvas.drawBitmap(mBitmap, mMatrix1, null);
		canvas.drawBitmap(mBitmap, mMatrix2, null);
		canvas.drawLine(0, 0, getMeasuredWidth(), 0, mPaint);
	}
}
看一下執行結果


為了防止變換之後部分看不到,我把canvas往下移動了紅線的距離,我們看到最下面的圖是mMatrix1變換得到的,在前面說過矩陣的set方法會先把當前的矩陣置為單位矩陣,所以他只是往下平移了200,往右平移了100,在看上面那張圖是由mMatrix2變換得到的,好像和下面那張圖正好相反,應該是平移了(-100,-200),我們看一下列印log


看的沒,正好和我們猜的一樣,這回逆矩陣的一些性質我們應該很清楚了吧,下面在把上面程式碼修改一下

mMatrix1.setRotate(60);
直接上圖


列印log


在修改

mMatrix1.setScale(.5f, .8f);
圖形一個會變大一個會變小,這個會重疊,需要移動一點距離才能看的更加明白,就不上傳了,看一下列印log


繼續修改

mMatrix1.setSkew(.3f, .5f);
看一下執行結果


再看一下log


這回我們知道逆矩陣的原理了吧,和原矩陣是對著幹的,至於他是怎麼得到的,可以按照上面逆矩陣的推算來得到,這裡就不可能一一來推算了。

mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,int pointCount),mapPoints有幾個過載的,這裡就拿這個來分析,他指的是對映點的值到指定的陣列中,他可以在矩陣變換以後,給出指定點的值,我們找個簡單的看一下

		mMatrix1 = new Matrix();
		mMatrix1.setScale(.3f, .5f);
		float src[] = { 20, 20, 100, 100 };
		float[] dst = new float[4];
		mMatrix1.mapPoints(dst, 0, src, 0, dst.length >> 1);
		Log.d("wld________mMatrix1=", mMatrix1.toShortString());
		Log.d("dst=", Arrays.toString(dst));
(20,20)和(100,100)是原圖形上的點,通過矩陣縮放後,應該為(6,10)(30,50),看一下列印log


結果和預想一樣,不過那個30.000002可能是由於精度問題有一點點偏差,不過這並不影響。

mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex,int vectorCount) 他也有幾個重構的方法,這裡也只分析這一個。他是改變向量然後儲存在dst中,我們知道向量是可以移動的,所以他對平移式不起作用的,來看一下他的註釋就明白了

    /**
    * Apply this matrix to the array of 2D vectors specified by src, and write
     * the transformed vectors into the array of vectors specified by dst. The
     * two arrays represent their "vectors" as pairs of floats [x, y].
     *
     * Note: this method does not apply the translation associated with the matrix. Use
     * {@link Matrix#mapPoints(float[], int, float[], int, int)} if you want the translation
     * to be applied.
可以把上面程式碼改一下
mMatrix1.mapVectors(dst, 0, src, 0, dst.length >> 1);
列印log還是和之前的一樣,基本沒有變化


mapRect(RectF dst, RectF src)測量矩形變換後的位置,如果返回任為矩形返回true,否則返回false,他只會記錄變換之後圖形的左上角及右下角的座標,看一下程式碼

		mMatrix1 = new Matrix();
		mMatrix1.setScale(.3f, .5f);
		RectF src = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
		RectF dst = new RectF();
		Log.d("mapRect=", mMatrix1.mapRect(dst, src) + "");
		Log.d("wld________mMatrix1=", mMatrix1.toShortString());
		Log.d("src=", src.toShortString());
		Log.d("dst=", dst.toShortString());
看一下列印log


src經過縮放之後等於dst,且返回true,說明縮放之後還是矩形,其實不列印也能猜的出來,矩形的縮放,平移之後還是矩形,在來修改一下,這回不讓他縮放了,讓他旋轉

	mMatrix1.setRotate(60);

我們看到返回false,說明旋轉就不在是矩形了,這個可能不太好理解,我們看到旋轉之後之前的兩個點的位置變化了,感覺圖形是變大了,旋轉之後記錄的是圖形所在外矩形框的左上角和右下角座標,旋轉之後圖形大小實際上沒變,因為我們前面分析過在旋轉的時候矩陣的錯切引數也會跟著變化(或者我們看上面的旋轉公式也可以看得出來),所以旋轉之後外框是變大了,但是在旋轉和錯切的共同作用下導致影象大小並不會改變,再改一下
mMatrix1.setRotate(90);
看一下log


我們看到是返回了true,說明矩形旋轉90度之後還是矩形,我們還可以參考最上面講的rectStaysRect()方法。當圖形執行錯切的時候會返回false,改一下來看看

mMatrix1.setSkew(.3f, .7f);

因為錯切之後矩形就不再是矩形了,所以返回false。

mapRadius(float radius)一個圓經過矩陣變換之後的半徑,是個平均值,因為有可能是橢圓,看一下程式碼

		mMatrix = new Matrix();
		mMatrix.setTranslate(120, 160);
		Log.d("dst=", mMatrix.mapRadius(100) + "");
看一下log


我們看到平移的時候半徑是不會變的,在修改一下

mMatrix.setScale(.5f, .5f);
看一下列印log


其實這個我們猜也能猜得到,因為都縮放一半,所以半徑自然會縮放一半,下面在改一下
mMatrix.setScale(.5f, 1.5f);

這回變成了長為300,寬為100的橢圓了,那麼橢圓的公式變成了x^2/150^2+y^2/50^2=1(^2表示平方的意思),他的半徑不好求,那麼我們求他的面積然後按照圓的面積計算公式計算出半徑,我們知道橢圓的面積為S=π(圓周率)×a×b(其中a,b分別是橢圓的半長軸,半短軸的長),那麼他的面積為π*150*50=750π,由圓的面積S=π*r^2,所以計算出半徑約為86.602540378,我們看一下列印log


和我們計算基本一致。

OK,到目前為止,Matrix基本已經分析完畢。