Android Matrix源碼詳解
Matrix的數(shù)學(xué)原理
在Android中,如果你用Matrix進行過圖像處理,那么一定知道Matrix這個類。Android中的Matrix是一個3 x 3的矩陣,其內(nèi)容如下:
Matrix的對圖像的處理可分為四類基本變換:
- Translate 平移變換
- Rotate 旋轉(zhuǎn)變換
- Scale 縮放變換
- Skew 錯切變換
從字面上理解,矩陣中的MSCALE用于處理縮放變換,MSKEW用于處理錯切變換,MTRANS用于處理平移變換,MPERSP用于處理透視變換。實際中當然不能完全按照字面上的說法去理解Matrix。同時,在Android的文檔中,未見到用Matrix進行透視變換的相關(guān)說明,所以本文也不討論這方面的問題。
針對每種變換,Android提供了pre、set和post三種操作方式。其中
set用于設(shè)置Matrix中的值。
pre是先乘,因為矩陣的乘法不滿足交換律,因此先乘、后乘必須要嚴格區(qū)分。先乘相當于矩陣運算中的右乘。
post是后乘,因為矩陣的乘法不滿足交換律,因此先乘、后乘必須要嚴格區(qū)分。后乘相當于矩陣運算中的左乘。
除平移變換(Translate)外,旋轉(zhuǎn)變換(Rotate)、縮放變換(Scale)和錯切變換(Skew)都可以圍繞一個中心點來進行,如果不指定,在默認情況下是圍繞(0, 0)來進行相應(yīng)的變換的。
下面我們來看看四種變換的具體情形。由于所有的圖形都是有點組成,因此我們只需要考察一個點相關(guān)變換即可。
一、 平移變換
假定有一個點的坐標是 ,將其移動到
,再假定在x軸和y軸方向移動的大小分別為:
如下圖所示:
不難知道:
如果用矩陣來表示的話,就可以寫成:
二、 旋轉(zhuǎn)變換
2.1 圍繞坐標原點旋轉(zhuǎn):
假定有一個點 ,相對坐標原點順時針旋轉(zhuǎn)
后的情形,同時假定P點離坐標原點的距離為r,如下圖:
那么,
如果用矩陣,就可以表示為:
2.2 圍繞某個點旋轉(zhuǎn)
如果是圍繞某個點順時針旋轉(zhuǎn)
,那么可以用矩陣表示為:
可以化為:
很顯然,
1. 是將坐標原點移動到點
后,
的新坐標。
2. 是將上一步變換后的
,圍繞新的坐標原點順時針旋轉(zhuǎn)
。
3.
經(jīng)過上一步旋轉(zhuǎn)變換后,再將坐標原點移回到原來的坐標原點。
所以,圍繞某一點進行旋轉(zhuǎn)變換,可以分成3個步驟,即首先將坐標原點移至該點,然后圍繞新的坐標原點進行旋轉(zhuǎn)變換,再然后將坐標原點移回到原先的坐標原點。
三、 縮放變換
理論上而言,一個點是不存在什么縮放變換的,但考慮到所有圖像都是由點組成,因此,如果圖像在x軸和y軸方向分別放大k1和k2倍的話,那么圖像中的所有點的x坐標和y坐標均會分別放大k1和k2倍,即
用矩陣表示就是:
縮放變換比較好理解,就不多說了。
四、 錯切變換
錯切變換(skew)在數(shù)學(xué)上又稱為Shear mapping(可譯為“剪切變換”)或者Transvection(縮并),它是一種比較特殊的線性變換。錯切變換的效果就是讓所有點的x坐標(或者y坐標)保持不變,而對應(yīng)的y坐標(或者x坐標)則按比例發(fā)生平移,且平移的大小和該點到x軸(或y軸)的垂直距離成正比。錯切變換,屬于等面積變換,即一個形狀在錯切變換的前后,其面積是相等的。
比如下圖,各點的y坐標保持不變,但其x坐標則按比例發(fā)生了平移。這種情況將水平錯切。
下圖各點的x坐標保持不變,但其y坐標則按比例發(fā)生了平移。這種情況叫垂直錯切。
假定一個點經(jīng)過錯切變換后得到
,對于水平錯切而言,應(yīng)該有如下關(guān)系:
用矩陣表示就是:
擴展到3 x 3的矩陣就是下面這樣的形式:
同理,對于垂直錯切,可以有:
在數(shù)學(xué)上嚴格的錯切變換就是上面這樣的。在Android中除了有上面說到的情況外,還可以同時進行水平、垂直錯切,那么形式上就是:
五、 對稱變換
除了上面講到的4中基本變換外,事實上,我們還可以利用Matrix,進行對稱變換。所謂對稱變換,就是經(jīng)過變化后的圖像和原圖像是關(guān)于某個對稱軸是對稱的。比如,某點 經(jīng)過對稱變換后得到
,
如果對稱軸是x軸,難么,
用矩陣表示就是:
如果對稱軸是y軸,那么,
用矩陣表示就是:
如果對稱軸是y = x,如圖:
那么,
很容易可以解得:
用矩陣表示就是:
同樣的道理,如果對稱軸是y = -x,那么用矩陣表示就是:
特殊地,如果對稱軸是y = kx,如下圖:
那么,
很容易可解得:
用矩陣表示就是:
當k = 0時,即y = 0,也就是對稱軸為x軸的情況;當k趨于無窮大時,即x = 0,也就是對稱軸為y軸的情況;當k =1時,即y = x,也就是對稱軸為y = x的情況;當k = -1時,即y = -x,也就是對稱軸為y = -x的情況。不難驗證,這和我們前面說到的4中具體情況是相吻合的。
如果對稱軸是y = kx + b這樣的情況,只需要在上面的基礎(chǔ)上增加兩次平移變換即可,即先將坐標原點移動到(0, b),然后做上面的關(guān)于y = kx的對稱變換,再然后將坐標原點移回到原來的坐標原點即可。用矩陣表示大致是這樣的:
需要特別注意:在實際編程中,我們知道屏幕的y坐標的正向和數(shù)學(xué)中y坐標的正向剛好是相反的,所以在數(shù)學(xué)上y = x和屏幕上的y = -x才是真正的同一個東西,反之亦然。也就是說,如果要使圖片在屏幕上看起來像按照數(shù)學(xué)意義上y = x對稱,那么需使用這種轉(zhuǎn)換:
要使圖片在屏幕上看起來像按照數(shù)學(xué)意義上y = -x對稱,那么需使用這種轉(zhuǎn)換:
關(guān)于對稱軸為y = kx 或y = kx + b的情況,同樣需要考慮這方面的問題。
第二部分 代碼驗證
在第一部分中講到的各種圖像變換的驗證代碼如下,一共列出了10種情況。如果要驗證其中的某一種情況,只需將相應(yīng)的代碼反注釋即可。試驗中用到的圖片:
其尺寸為162 x 251。
每種變換的結(jié)果,請見代碼之后的說明。
package compattesttransformmatrix; import androidappActivity; import androidcontentContext; import androidgraphicsBitmap; import androidgraphicsBitmapFactory; import androidgraphicsCanvas; import androidgraphicsMatrix; import androidosBundle; import androidutilLog; import androidviewMotionEvent; import androidviewView; import androidviewWindow; import androidviewWindowManager; import androidviewViewOnTouchListener; import androidwidgetImageView; public class TestTransformMatrixActivity extends Activity implements OnTouchListener { private TransformMatrixView view; @Override public void onCreate(Bundle savedInstanceState) { superonCreate(savedInstanceState); requestWindowFeature(WindowFEATURE_NO_TITLE); thisgetWindow()setFlags(WindowManagerLayoutParamsFLAG_FULLSCREEN, WindowManagerLayoutParamsFLAG_FULLSCREEN); view = new TransformMatrixView(this); viewsetScaleType(ImageViewScaleTypeMATRIX); viewsetOnTouchListener(this); setContentView(view); } class TransformMatrixView extends ImageView { private Bitmap bitmap; private Matrix matrix; public TransformMatrixView(Context context) { super(context); bitmap = BitmapFactorydecodeResource(getResources(), Rdrawablesophie); matrix = new Matrix(); } @Override protected void onDraw(Canvas canvas) { // 畫出原圖像 canvasdrawBitmap(bitmap, 0, 0, null); // 畫出變換后的圖像 canvasdrawBitmap(bitmap, matrix, null); superonDraw(canvas); } @Override public void setImageMatrix(Matrix matrix) { thismatrixset(matrix); supersetImageMatrix(matrix); } public Bitmap getImageBitmap() { return bitmap; } } public boolean onTouch(View v, MotionEvent e) { if(egetAction() == MotionEventACTION_UP) { Matrix matrix = new Matrix(); // 輸出圖像的寬度和高度(162 x 251) Loge("TestTransformMatrixActivity", "image size: width x height = " + viewgetImageBitmap()getWidth() + " x " + viewgetImageBitmap()getHeight()); // 平移 matrixpostTranslate(viewgetImageBitmap()getWidth(), viewgetImageBitmap()getHeight()); // 在x方向平移viewgetImageBitmap()getWidth(),在y軸方向viewgetImageBitmap()getHeight() viewsetImageMatrix(matrix); // 下面的代碼是為了查看matrix中的元素 float[] matrixValues = new float[9]; matrixgetValues(matrixValues); for(int i = 0; i < 3; ++i) { String temp = new String(); for(int j = 0; j < 3; ++j) { temp += matrixValues[3 * i + j ] + "\t"; } Loge("TestTransformMatrixActivity", temp); } // // 旋轉(zhuǎn)(圍繞圖像的中心點) // matrixsetRotate(45f, viewgetImageBitmap()getWidth() / 2f, viewgetImageBitmap()getHeight() / 2f); // // // 做下面的平移變換,純粹是為了讓變換后的圖像和原圖像不重疊 // matrixpostTranslate(viewgetImageBitmap()getWidth() * 5f, 0f); // viewsetImageMatrix(matrix); // // // 下面的代碼是為了查看matrix中的元素 // float[] matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // 旋轉(zhuǎn)(圍繞坐標原點) + 平移(效果同2) // matrixsetRotate(45f); // matrixpreTranslate(-1f * viewgetImageBitmap()getWidth() / 2f, -1f * viewgetImageBitmap()getHeight() / 2f); // matrixpostTranslate((float)viewgetImageBitmap()getWidth() / 2f, (float)viewgetImageBitmap()getHeight() / 2f); // // // 做下面的平移變換,純粹是為了讓變換后的圖像和原圖像不重疊 // matrixpostTranslate((float)viewgetImageBitmap()getWidth() * 5f, 0f); // viewsetImageMatrix(matrix); // // // 下面的代碼是為了查看matrix中的元素 // float[] matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // 縮放 // matrixsetScale(2f, 2f); // // 下面的代碼是為了查看matrix中的元素 // float[] matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // // 做下面的平移變換,純粹是為了讓變換后的圖像和原圖像不重疊 // matrixpostTranslate(viewgetImageBitmap()getWidth(), viewgetImageBitmap()getHeight()); // viewsetImageMatrix(matrix); // // // 下面的代碼是為了查看matrix中的元素 // matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // 錯切 - 水平 // matrixsetSkew(5f, 0f); // // 下面的代碼是為了查看matrix中的元素 // float[] matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // // 做下面的平移變換,純粹是為了讓變換后的圖像和原圖像不重疊 // matrixpostTranslate(viewgetImageBitmap()getWidth(), 0f); // viewsetImageMatrix(matrix); // // // 下面的代碼是為了查看matrix中的元素 // matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // 錯切 - 垂直 // matrixsetSkew(0f, 5f); // // 下面的代碼是為了查看matrix中的元素 // float[] matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // // 做下面的平移變換,純粹是為了讓變換后的圖像和原圖像不重疊 // matrixpostTranslate(0f, viewgetImageBitmap()getHeight()); // viewsetImageMatrix(matrix); // // // 下面的代碼是為了查看matrix中的元素 // matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // 錯切 - 水平 + 垂直 // matrixsetSkew(5f, 5f); // // 下面的代碼是為了查看matrix中的元素 // float[] matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // // 做下面的平移變換,純粹是為了讓變換后的圖像和原圖像不重疊 // matrixpostTranslate(0f, viewgetImageBitmap()getHeight()); // viewsetImageMatrix(matrix); // // // 下面的代碼是為了查看matrix中的元素 // matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // 對稱 (水平對稱) // float matrix_values[] = {1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f}; // matrixsetValues(matrix_values); // // 下面的代碼是為了查看matrix中的元素 // float[] matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // // 做下面的平移變換,純粹是為了讓變換后的圖像和原圖像不重疊 // matrixpostTranslate(0f, viewgetImageBitmap()getHeight() * 2f); // viewsetImageMatrix(matrix); // // // 下面的代碼是為了查看matrix中的元素 // matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // 對稱 - 垂直 // float matrix_values[] = {-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f}; // matrixsetValues(matrix_values); // // 下面的代碼是為了查看matrix中的元素 // float[] matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // // 做下面的平移變換,純粹是為了讓變換后的圖像和原圖像不重疊 // matrixpostTranslate(viewgetImageBitmap()getWidth() * 2f, 0f); // viewsetImageMatrix(matrix); // // // 下面的代碼是為了查看matrix中的元素 // matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // 對稱(對稱軸為直線y = x) // float matrix_values[] = {0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f}; // matrixsetValues(matrix_values); // // 下面的代碼是為了查看matrix中的元素 // float[] matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } // // // 做下面的平移變換,純粹是為了讓變換后的圖像和原圖像不重疊 // matrixpostTranslate(viewgetImageBitmap()getHeight() + viewgetImageBitmap()getWidth(), // viewgetImageBitmap()getHeight() + viewgetImageBitmap()getWidth()); // viewsetImageMatrix(matrix); // // // 下面的代碼是為了查看matrix中的元素 // matrixValues = new float[9]; // matrixgetValues(matrixValues); // for(int i = 0; i < 3; ++i) // { // String temp = new String(); // for(int j = 0; j < 3; ++j) // { // temp += matrixValues[3 * i + j ] + "\t"; // } // Loge("TestTransformMatrixActivity", temp); // } viewinvalidate(); } return true; } }
下面給出上述代碼中,各種變換的具體結(jié)果及其對應(yīng)的相關(guān)變換矩陣
1. 平移
輸出的結(jié)果:
請對照第一部分中的“一、平移變換”所講的情形,考察上述矩陣的正確性。
2. 旋轉(zhuǎn)(圍繞圖像的中心點)
輸出的結(jié)果:
它實際上是
matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f); matrix.postTranslate(view.getImageBitmap().getWidth()* 1.5f, 0f);
這兩條語句綜合作用的結(jié)果。根據(jù)第一部分中“二、旋轉(zhuǎn)變換”里面關(guān)于圍繞某點旋轉(zhuǎn)的公式,
matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);
所產(chǎn)生的轉(zhuǎn)換矩陣就是:
而matrix.postTranslate(view.getImageBitmap().getWidth()* 1.5f, 0f);的意思就是在上述矩陣的左邊再乘以下面的矩陣:
關(guān)于post是左乘這一點,我們在前面的理論部分曾經(jīng)提及過,后面我們還會專門討論這個問題。
所以它實際上就是:
出去計算上的精度誤差,我們可以看到我們計算出來的結(jié)果,和程序直接輸出的結(jié)果是一致的。
3. 旋轉(zhuǎn)(圍繞坐標原點旋轉(zhuǎn),在加上兩次平移,效果同2)
根據(jù)第一部分中“二、旋轉(zhuǎn)變換”里面關(guān)于圍繞某點旋轉(zhuǎn)的解釋,不難知道:
matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);
等價于
matrix.setRotate(45f); matrix.preTranslate(-1f* view.getImageBitmap().getWidth() / 2f, -1f *view.getImageBitmap().getHeight() / 2f); matrix.postTranslate((float)view.getImageBitmap().getWidth()/ 2f, (float)view.getImageBitmap().getHeight() / 2f);
其中matrix.setRotate(45f)對應(yīng)的矩陣是:
matrix.preTranslate(-1f* view.getImageBitmap().getWidth() / 2f, -1f * view.getImageBitmap().getHeight()/ 2f)對應(yīng)的矩陣是:
由于是preTranslate,是先乘,也就是右乘,即它應(yīng)該出現(xiàn)在matrix.setRotate(45f)所對應(yīng)矩陣的右側(cè)。
matrix.postTranslate((float)view.getImageBitmap().getWidth()/ 2f, (float)view.getImageBitmap().getHeight() / 2f)對應(yīng)的矩陣是:
這次由于是postTranslate,是后乘,也就是左乘,即它應(yīng)該出現(xiàn)在matrix.setRotate(45f)所對應(yīng)矩陣的左側(cè)。
所以綜合起來,
matrix.setRotate(45f); matrix.preTranslate(-1f* view.getImageBitmap().getWidth() / 2f, -1f *view.getImageBitmap().getHeight() / 2f); matrix.postTranslate((float)view.getImageBitmap().getWidth()/ 2f, (float)view.getImageBitmap().getHeight() / 2f);
對應(yīng)的矩陣就是:
這和下面這個矩陣(圍繞圖像中心順時針旋轉(zhuǎn)45度)其實是一樣的:
因此,此處變換后的圖像和2中變換后的圖像時一樣的。
4. 縮放變換
程序所輸出的兩個矩陣分別是:
其中第二個矩陣,其實是下面兩個矩陣相乘的結(jié)果:
大家可以對照第一部分中的“三、縮放變換”和“一、平移變換”說法,自行驗證結(jié)果。
5. 錯切變換(水平錯切)
代碼所輸出的兩個矩陣分別是:
其中,第二個矩陣其實是下面兩個矩陣相乘的結(jié)果:
大家可以對照第一部分中的“四、錯切變換”和“一、平移變換”的相關(guān)說法,自行驗證結(jié)果。
6. 錯切變換(垂直錯切)
代碼所輸出的兩個矩陣分別是:
其中,第二個矩陣其實是下面兩個矩陣相乘的結(jié)果:
大家可以對照第一部分中的“四、錯切變換”和“一、平移變換”的相關(guān)說法,自行驗證結(jié)果。
7. 錯切變換(水平+垂直錯切)
代碼所輸出的兩個矩陣分別是:
其中,后者是下面兩個矩陣相乘的結(jié)果:
大家可以對照第一部分中的“四、錯切變換”和“一、平移變換”的相關(guān)說法,自行驗證結(jié)果。
8. 對稱變換(水平對稱)
代碼所輸出的兩個各矩陣分別是:
其中,后者是下面兩個矩陣相乘的結(jié)果:
大家可以對照第一部分中的“五、對稱變換”和“一、平移變換”的相關(guān)說法,自行驗證結(jié)果。
9. 對稱變換(垂直對稱)
代碼所輸出的兩個矩陣分別是:
其中,后者是下面兩個矩陣相乘的結(jié)果:
大家可以對照第一部分中的“五、對稱變換”和“一、平移變換”的相關(guān)說法,自行驗證結(jié)果。
10. 對稱變換(對稱軸為直線y = x)
代碼所輸出的兩個矩陣分別是:
其中,后者是下面兩個矩陣相乘的結(jié)果:
大家可以對照第一部分中的“五、對稱變換”和“一、平移變換”的相關(guān)說法,自行驗證結(jié)果。
11. 關(guān)于先乘和后乘的問題
由于矩陣的乘法運算不滿足交換律,我們在前面曾經(jīng)多次提及先乘、后乘的問題,即先乘就是矩陣運算中右乘,后乘就是矩陣運算中的左乘。其實先乘、后乘的概念是針對變換操作的時間先后而言的,左乘、右乘是針對矩陣運算的左右位置而言的。以第一部分“二、旋轉(zhuǎn)變換”中圍繞某點旋轉(zhuǎn)的情況為例:
越靠近原圖像中像素的矩陣,越先乘,越遠離原圖像中像素的矩陣,越后乘。事實上,圖像處理時,矩陣的運算是從右邊往左邊方向進行運算的。這就形成了越在右邊的矩陣(右乘),越先運算(先乘),反之亦然。
當然,在實際中,如果首先指定了一個matrix,比如我們先setRotate(),即指定了上面變換矩陣中,中間的那個矩陣,那么后續(xù)的矩陣到底是pre還是post運算,都是相對這個中間矩陣而言的。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android中利用matrix 控制圖片的旋轉(zhuǎn)、縮放、移動
- 深入理解Android Matrix理論與使用的詳解
- Android變形(Transform)之Matrix用法
- Android中Matrix用法實例分析
- android高仿小米時鐘(使用Camera和Matrix實現(xiàn)3D效果)
- Android中使用Matrix控制圖形變換和制作倒影效果的方法
- android Matrix實現(xiàn)圖片隨意放大縮小或拖動
- Android 矩陣ColorMatrix
- Android使用Matrix旋轉(zhuǎn)圖片模擬碟片加載過程
- 詳談Android中Matrix的set、pre、post的區(qū)別
- android.graphics.Matrix類用法分析
相關(guān)文章
Android?JetPack組件的支持庫Databinding詳解
DataBinding是Google發(fā)布的一個數(shù)據(jù)綁定框架,它能夠讓開發(fā)者減少重復(fù)性非常高的代碼,如findViewById這樣的操作。其核心優(yōu)勢是解決了數(shù)據(jù)分解映射到各個view的問題,在MVVM框架中,實現(xiàn)的View和Viewmode的雙向數(shù)據(jù)綁定2022-08-08Android CalendarView,DatePicker,TimePicker,以及NumberPicker的使
這篇文章主要介紹了Android CalendarView,DatePicker,TimePicker,以及NumberPicker的使用的相關(guān)資料,需要的朋友可以參考下2016-12-12Android編程開發(fā)之NotiFication用法詳解
這篇文章主要介紹了Android編程開發(fā)之NotiFication用法,結(jié)合實例形式較為詳細的分析了NotiFication的功能、使用技巧與注意事項,需要的朋友可以參考下2015-12-12Android中新引進的Google Authenticator驗證系統(tǒng)工作原理淺析
這篇文章主要介紹了Android中新引進的Google Authenticator驗證系統(tǒng)工作原理淺析,需要的朋友可以參考下2014-10-10Android之scrollview滑動使標題欄漸變背景色的實例代碼
這篇文章主要介紹了Android之scrollview滑動使標題欄漸變背景色的實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05