Android中圖像變換Matrix的原理、代碼驗證和應用

第一部分 Matrix的數學原理java

在Android中,若是你用Matrix進行過圖像處理,那麼必定知道Matrix這個類。Android中的Matrix是一個3 x 3的矩陣,其內容以下:android

 

Matrix的對圖像的處理可分爲四類基本變換:編程

Translate           平移變換canvas

Rotate                旋轉變換app

Scale                  縮放變換ide

Skew                  錯切變換post

 

從字面上理解,矩陣中的MSCALE用於處理縮放變換,MSKEW用於處理錯切變換,MTRANS用於處理平移變換,MPERSP用於處理透視變換。實際中固然不能徹底按照字面上的說法去理解Matrix。同時,在Android的文檔中,未見到用Matrix進行透視變換的相關說明,因此本文也不討論這方面的問題。this

 

針對每種變換,Android提供了pre、set和post三種操做方式。其中spa

set用於設置Matrix中的值。.net

pre是先乘,由於矩陣的乘法不知足交換律,所以先乘、後乘必需要嚴格區分。先乘至關於矩陣運算中的右乘。

post是後乘,由於矩陣的乘法不知足交換律,所以先乘、後乘必需要嚴格區分。後乘至關於矩陣運算中的左乘。

 

除平移變換(Translate)外,旋轉變換(Rotate)、縮放變換(Scale)和錯切變換(Skew)均可以圍繞一箇中心點來進行,若是不指定,在默認狀況下是圍繞(0, 0)來進行相應的變換的。

 

下面咱們來看看四種變換的具體情形。因爲全部的圖形都是有點組成,所以咱們只須要考察一個點相關變換便可。

 

1、 平移變換

假定有一個點的座標是 ,將其移動到 ,再假定在x軸和y軸方向移動的大小分別爲:


以下圖所示:


不難知道:


若是用矩陣來表示的話,就能夠寫成:

 


2、 旋轉變換

 

2.1    圍繞座標原點旋轉:

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


那麼,


若是用矩陣,就能夠表示爲:


 

2.2    圍繞某個點旋轉

若是是圍繞某個點順時針旋轉,那麼能夠用矩陣表示爲:


能夠化爲:


很顯然,

1.   

  是將座標原點移動到點後, 的新座標。

2.     


是將上一步變換後的,圍繞新的座標原點順時針旋轉 。

3.     


通過上一步旋轉變換後,再將座標原點移回到原來的座標原點。

 

因此,圍繞某一點進行旋轉變換,能夠分紅3個步驟,即首先將座標原點移至該點,而後圍繞新的座標原點進行旋轉變換,再而後將座標原點移回到原先的座標原點。

 

3、 縮放變換

理論上而言,一個點是不存在什麼縮放變換的,但考慮到全部圖像都是由點組成,所以,若是圖像在x軸和y軸方向分別放大k1k2倍的話,那麼圖像中的全部點的x座標和y座標均會分別放大k1k2倍,即


用矩陣表示就是:


縮放變換比較好理解,就很少說了。

 

4、 錯切變換

錯切變換(skew)在數學上又稱爲Shear mapping(可譯爲「剪切變換」)或者Transvection(縮並),它是一種比較特殊的線性變換。錯切變換的效果就是讓全部點的x座標(或者y座標)保持不變,而對應的y座標(或者x座標)則按比例發平生移,且平移的大小和該點到x軸(或y軸)的垂直距離成正比。錯切變換,屬於等面積變換,即一個形狀在錯切變換的先後,其面積是相等的。

好比下圖,各點的y座標保持不變,但其x座標則按比例發生了平移。這種狀況將水平錯切。


下圖各點的x座標保持不變,但其y座標則按比例發生了平移。這種狀況叫垂直錯切。

 

假定一個點通過錯切變換後獲得,對於水平錯切而言,應該有以下關係:


用矩陣表示就是:


擴展到3 x 3的矩陣就是下面這樣的形式:

 

同理,對於垂直錯切,能夠有:


在數學上嚴格的錯切變換就是上面這樣的。在Android中除了有上面說到的狀況外,還能夠同時進行水平、垂直錯切,那麼形式上就是:


 

5、 對稱變換

除了上面講到的4中基本變換外,事實上,咱們還能夠利用Matrix,進行對稱變換。所謂對稱變換,就是通過變化後的圖像和原圖像是關於某個對稱軸是對稱的。好比,某點 通過對稱變換後獲得

若是對稱軸是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這樣的狀況,只須要在上面的基礎上增長兩次平移變換便可,即先將座標原點移動到(0, b),而後作上面的關於y = kx的對稱變換,再而後將座標原點移回到原來的座標原點便可。用矩陣表示大體是這樣的:


須要特別注意:在實際編程中,咱們知道屏幕的y座標的正向和數學中y座標的正向恰好是相反的,因此在數學上y = x和屏幕上的y = -x纔是真正的同一個東西,反之亦然。也就是說,若是要使圖片在屏幕上看起來像按照數學意義上y = x對稱,那麼需使用這種轉換:


要使圖片在屏幕上看起來像按照數學意義上y = -x對稱,那麼需使用這種轉換:
 

關於對稱軸爲y = kx y = kx + b的狀況,一樣須要考慮這方面的問題。

 

 

第二部分 代碼驗證

在第一部分中講到的各類圖像變換的驗證代碼以下,一共列出了10種狀況。若是要驗證其中的某一種狀況,只需將相應的代碼反註釋便可。試驗中用到的圖片:


其尺寸爲162 x 251。

 

每種變換的結果,請見代碼以後的說明。

<span style="font-size:13px;"></span><pre name="code" class="java">package com.pat.testtransformmatrix;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnTouchListener;
import android.widget.ImageView;

public class TestTransformMatrixActivity extends Activity
implements
OnTouchListener
{
	private TransformMatrixView view;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        view = new TransformMatrixView(this);
        view.setScaleType(ImageView.ScaleType.MATRIX);
        view.setOnTouchListener(this);
        
        setContentView(view);
    }
    
    class TransformMatrixView extends ImageView
    {
    	private Bitmap bitmap;
    	private Matrix matrix;
		public TransformMatrixView(Context context)
		{
			super(context);
			bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sophie);
			matrix = new Matrix();
		}

		@Override
		protected void onDraw(Canvas canvas)
		{
			// 畫出原圖像
			canvas.drawBitmap(bitmap, 0, 0, null);
			// 畫出變換後的圖像
			canvas.drawBitmap(bitmap, matrix, null);
			super.onDraw(canvas);
		}

		@Override
		public void setImageMatrix(Matrix matrix)
		{
			this.matrix.set(matrix);
			super.setImageMatrix(matrix);
		}
		
		public Bitmap getImageBitmap()
		{
			return bitmap;
		}
    }

	public boolean onTouch(View v, MotionEvent e)
	{
		if(e.getAction() == MotionEvent.ACTION_UP)
		{
			Matrix matrix = new Matrix();
			// 輸出圖像的寬度和高度(162 x 251)
			Log.e("TestTransformMatrixActivity", "image size: width x height = " +  view.getImageBitmap().getWidth() + " x " + view.getImageBitmap().getHeight());
			// 1. 平移
			matrix.postTranslate(view.getImageBitmap().getWidth(), view.getImageBitmap().getHeight());
			// 在x方向平移view.getImageBitmap().getWidth(),在y軸方向view.getImageBitmap().getHeight()
			view.setImageMatrix(matrix);
			
			// 下面的代碼是爲了查看matrix中的元素
			float[] matrixValues = new float[9];
			matrix.getValues(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";
				}
				Log.e("TestTransformMatrixActivity", temp);
			}
			

//			// 2. 旋轉(圍繞圖像的中心點)
//			matrix.setRotate(45f, view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);
//			
//			// 作下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊
//			matrix.postTranslate(view.getImageBitmap().getWidth() * 1.5f, 0f);
//			view.setImageMatrix(matrix);
//
//			// 下面的代碼是爲了查看matrix中的元素
//			float[] matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
			
			
//			// 3. 旋轉(圍繞座標原點) + 平移(效果同2)
//			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.postTranslate((float)view.getImageBitmap().getWidth() * 1.5f, 0f);
//			view.setImageMatrix(matrix);
//			
//			// 下面的代碼是爲了查看matrix中的元素
//			float[] matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}			
			
//			// 4. 縮放
//			matrix.setScale(2f, 2f);
//			// 下面的代碼是爲了查看matrix中的元素
//			float[] matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
//			
//			// 作下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊
//			matrix.postTranslate(view.getImageBitmap().getWidth(), view.getImageBitmap().getHeight());
//			view.setImageMatrix(matrix);
//			
//			// 下面的代碼是爲了查看matrix中的元素
//			matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}

			
//			// 5. 錯切 - 水平
//			matrix.setSkew(0.5f, 0f);
//			// 下面的代碼是爲了查看matrix中的元素
//			float[] matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
//			
//			// 作下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊			
//			matrix.postTranslate(view.getImageBitmap().getWidth(), 0f);
//			view.setImageMatrix(matrix);
//			
//			// 下面的代碼是爲了查看matrix中的元素
//			matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
			
//			// 6. 錯切 - 垂直
//			matrix.setSkew(0f, 0.5f);
//			// 下面的代碼是爲了查看matrix中的元素
//			float[] matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
//			
//			// 作下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊				
//			matrix.postTranslate(0f, view.getImageBitmap().getHeight());
//			view.setImageMatrix(matrix);
//			
//			// 下面的代碼是爲了查看matrix中的元素
//			matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}			
			
//			7. 錯切 - 水平 + 垂直
//			matrix.setSkew(0.5f, 0.5f);
//			// 下面的代碼是爲了查看matrix中的元素
//			float[] matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
//			
//			// 作下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊				
//			matrix.postTranslate(0f, view.getImageBitmap().getHeight());
//			view.setImageMatrix(matrix);
//			
//			// 下面的代碼是爲了查看matrix中的元素
//			matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
			
//			// 8. 對稱 (水平對稱)
//			float matrix_values[] = {1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f};
//			matrix.setValues(matrix_values);
//			// 下面的代碼是爲了查看matrix中的元素
//			float[] matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
//			
//			// 作下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊	
//			matrix.postTranslate(0f, view.getImageBitmap().getHeight() * 2f);
//			view.setImageMatrix(matrix);
//			
//			// 下面的代碼是爲了查看matrix中的元素
//			matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}			
			
//			// 9. 對稱 - 垂直
//			float matrix_values[] = {-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f};
//			matrix.setValues(matrix_values);
//			// 下面的代碼是爲了查看matrix中的元素
//			float[] matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}	
//			
//			// 作下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊	
//			matrix.postTranslate(view.getImageBitmap().getWidth() * 2f, 0f);
//			view.setImageMatrix(matrix);
//			
//			// 下面的代碼是爲了查看matrix中的元素
//			matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}

			
//			// 10. 對稱(對稱軸爲直線y = x)
//			float matrix_values[] = {0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f};
//			matrix.setValues(matrix_values);
//			// 下面的代碼是爲了查看matrix中的元素
//			float[] matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
//			
//			// 作下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊				
//			matrix.postTranslate(view.getImageBitmap().getHeight() + view.getImageBitmap().getWidth(), 
//					view.getImageBitmap().getHeight() + view.getImageBitmap().getWidth());
//			view.setImageMatrix(matrix);
//			
//			// 下面的代碼是爲了查看matrix中的元素
//			matrixValues = new float[9];
//			matrix.getValues(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";
//				}
//				Log.e("TestTransformMatrixActivity", temp);
//			}
			
			view.invalidate();
		}
		return true;
	}
}


下面給出上述代碼中,各類變換的具體結果及其對應的相關變換矩陣

1.     平移

輸出的結果:

請對照第一部分中的「1、平移變換」所講的情形,考察上述矩陣的正確性。

 

2.     旋轉(圍繞圖像的中心點)

輸出的結果:

它其實是

matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);

matrix.postTranslate(view.getImageBitmap().getWidth()* 1.5f, 0f);

這兩條語句綜合做用的結果。根據第一部分中「2、旋轉變換」裏面關於圍繞某點旋轉的公式,

matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);

所產生的轉換矩陣就是:

而matrix.postTranslate(view.getImageBitmap().getWidth()* 1.5f, 0f);的意思就是在上述矩陣的左邊再乘如下面的矩陣:

關於post是左乘這一點,咱們在前面的理論部分曾經說起過,後面咱們還會專門討論這個問題。

 

因此它實際上就是:

出去計算上的精度偏差,咱們能夠看到咱們計算出來的結果,和程序直接輸出的結果是一致的。

 

3.     旋轉(圍繞座標原點旋轉,在加上兩次平移,效果同2)

根據第一部分中「2、旋轉變換」裏面關於圍繞某點旋轉的解釋,不難知道:

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)對應的矩陣是:

matrix.preTranslate(-1f* view.getImageBitmap().getWidth() / 2f, -1f * view.getImageBitmap().getHeight()/ 2f)對應的矩陣是:

因爲是preTranslate,是先乘,也就是右乘,即它應該出如今matrix.setRotate(45f)所對應矩陣的右側。

 

matrix.postTranslate((float)view.getImageBitmap().getWidth()/ 2f, (float)view.getImageBitmap().getHeight() / 2f)對應的矩陣是:

此次因爲是postTranslate,是後乘,也就是左乘,即它應該出如今matrix.setRotate(45f)所對應矩陣的左側。

 

因此綜合起來,

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

對應的矩陣就是:

這和下面這個矩陣(圍繞圖像中心順時針旋轉45度)實際上是同樣的:

所以,此處變換後的圖像和2中變換後的圖像時同樣的。

 

4.     縮放變換

程序所輸出的兩個矩陣分別是:

其中第二個矩陣,實際上是下面兩個矩陣相乘的結果:

 

你們能夠對照第一部分中的「3、縮放變換」和「1、平移變換」說法,自行驗證結果。

 

5.     錯切變換(水平錯切)

代碼所輸出的兩個矩陣分別是:

其中,第二個矩陣實際上是下面兩個矩陣相乘的結果:

 

你們能夠對照第一部分中的「4、錯切變換」和「1、平移變換」的相關說法,自行驗證結果。

 

6.     錯切變換(垂直錯切)

代碼所輸出的兩個矩陣分別是:

其中,第二個矩陣實際上是下面兩個矩陣相乘的結果:

你們能夠對照第一部分中的「4、錯切變換」和「1、平移變換」的相關說法,自行驗證結果。

 

7.     錯切變換(水平+垂直錯切)

代碼所輸出的兩個矩陣分別是:

其中,後者是下面兩個矩陣相乘的結果:

你們能夠對照第一部分中的「4、錯切變換」和「1、平移變換」的相關說法,自行驗證結果。

 

8.     對稱變換(水平對稱)

代碼所輸出的兩個各矩陣分別是:

其中,後者是下面兩個矩陣相乘的結果:

 

你們能夠對照第一部分中的「5、對稱變換」和「1、平移變換」的相關說法,自行驗證結果。

 

9.     對稱變換(垂直對稱)

代碼所輸出的兩個矩陣分別是:

其中,後者是下面兩個矩陣相乘的結果:

 

你們能夠對照第一部分中的「5、對稱變換」和「1、平移變換」的相關說法,自行驗證結果。

 

10.   對稱變換(對稱軸爲直線y = x)

代碼所輸出的兩個矩陣分別是:

其中,後者是下面兩個矩陣相乘的結果:

 

你們能夠對照第一部分中的「5、對稱變換」和「1、平移變換」的相關說法,自行驗證結果。

 

11.   關於先乘和後乘的問題

因爲矩陣的乘法運算不知足交換律,咱們在前面曾經屢次說起先乘、後乘的問題,即先乘就是矩陣運算中右乘,後乘就是矩陣運算中的左乘。其實先乘、後乘的概念是針對變換操做的時間前後而言的,左乘、右乘是針對矩陣運算的左右位置而言的。以第一部分「2、旋轉變換」中圍繞某點旋轉的狀況爲例:

 

越靠近原圖像中像素的矩陣,越先乘,越遠離原圖像中像素的矩陣,越後乘。事實上,圖像處理時,矩陣的運算是從右邊往左邊方向進行運算的。這就造成了越在右邊的矩陣(右乘),越先運算(先乘),反之亦然。

 

固然,在實際中,若是首先指定了一個matrix,好比咱們先setRotate(),即指定了上面變換矩陣中,中間的那個矩陣,那麼後續的矩陣究竟是pre仍是post運算,都是相對這個中間矩陣而言的。

 

全部這些,其實都是很天然的事情。

 

第三部分 應用

在這一部分,咱們會將前面兩部分所瞭解到的內容和Android手勢結合起來,利用各類不一樣的手勢對圖像進行平移、縮放和旋轉,前面兩項都是在實踐中常常須要用到的功能,後一項聽說蘋果也是最近才加上的,而實際上在Android中,我們經過本身的雙手,也能夠很輕鬆地實現之。

 

首先建立一個Android項目PatImageView,同時建立一個Activity:PatImageViewActivity。完成這一步後, 記得在AndroidManifest.xml中增長以下許可:

<uses-permissionandroid:name="android.permission.VIBRATE"/>

由於咱們將要經過短按仍是長按,來肯定將圖片究竟是縮放仍是旋轉。

 

如今來建立一個ImageView的派生類:PatImageView,其代碼(PatImageView.java)以下(2011-11-22 revised):

package com.pat.imageview;  
  
import android.app.Service;  
import android.content.Context;  
import android.graphics.Matrix;  
import android.graphics.PointF;  
import android.os.Vibrator;  
import android.util.FloatMath;  
import android.view.GestureDetector;  
import android.view.MotionEvent;  
import android.view.View;  
import android.widget.ImageView;  
  
public class PatImageView extends ImageView  
{  
    private Matrix matrix;  
    private Matrix savedMatrix;  
      
    private boolean long_touch = false;  
    private static int NONE = 0;  
    private static int DRAG = 1;    // 拖動  
    private static int ZOOM = 2;    // 縮放  
    private static int ROTA = 3;    // 旋轉  
    private int mode = NONE;  
      
    private PointF startPoint;  
    private PointF middlePoint;  
      
    private float oldDistance;  
    private float oldAngle;  
  
    private Vibrator vibrator;  
      
    private GestureDetector gdetector;  
      
    public PatImageView(final Context context)  
    {  
        super(context);  
  
        matrix = new Matrix();  
        savedMatrix = new Matrix();  
          
        matrix.setTranslate(0f, 0f);  
        setScaleType(ScaleType.MATRIX);  
        setImageMatrix(matrix);  
          
        startPoint = new PointF();  
        middlePoint = new PointF();  
          
        oldDistance = 1f;  
          
        gdetector = new GestureDetector(context, new GestureDetector.OnGestureListener()  
        {  
            @Override  
            public boolean onSingleTapUp(MotionEvent e)  
            {  
                return true;  
            }  
              
            @Override  
            public void onShowPress(MotionEvent e)  
            {  
            }  
              
            @Override  
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)  
            {  
                return true;  
            }  
              
            @Override  
            public void onLongPress(MotionEvent e)  
            {  
                long_touch = true;  
                vibrator = (Vibrator) context.getSystemService(Service.VIBRATOR_SERVICE);  
                // 振動50ms,提示後續的操做將是旋轉圖片,而非縮放圖片  
                vibrator.vibrate(50);  
            }  
              
            @Override  
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)  
            {  
                return true;  
            }  
              
            @Override  
            public boolean onDown(MotionEvent e)  
            {  
                return true;  
            }  
        });  
          
        setOnTouchListener(new OnTouchListener()  
        {  
            public boolean onTouch(View view, MotionEvent event)  
            {  
                switch(event.getAction() & MotionEvent.ACTION_MASK)  
                {  
                case MotionEvent.ACTION_DOWN:           // 第一個手指touch  
                    savedMatrix.set(matrix);  
                    startPoint.set(event.getX(), event.getY());  
                    mode = DRAG;  
                    long_touch = false;  
                    break;  
                case MotionEvent.ACTION_POINTER_DOWN:   // 第二個手指touch  
                    oldDistance = getDistance(event);   // 計算第二個手指touch時,兩指之間的距離  
                    oldAngle = getDegree(event);        // 計算第二個手指touch時,兩指所造成的直線和x軸的角度  
                    if(oldDistance > 10f)  
                    {  
                        savedMatrix.set(matrix);  
                        middlePoint = midPoint(event);  
                        if(!long_touch)  
                        {  
                            mode = ZOOM;  
                        }  
                        else  
                        {  
                            mode = ROTA;  
                        }  
                    }  
                    break;  
                case MotionEvent.ACTION_UP:  
                    mode = NONE;  
                    break;  
                case MotionEvent.ACTION_POINTER_UP:  
                    mode = NONE;  
                    break;  
                case MotionEvent.ACTION_MOVE:  
                    if(vibrator != null)    vibrator.cancel();  
                    if(mode == DRAG)  
                    {  
                        matrix.set(savedMatrix);  
                        matrix.postTranslate(event.getX() - startPoint.x, event.getY() - startPoint.y);  
                    }  
                      
                    if(mode == ZOOM)  
                    {  
                        float newDistance = getDistance(event);  
                          
                        if(newDistance > 10f)  
                        {  
                            matrix.set(savedMatrix);  
                            float scale = newDistance / oldDistance;  
                            matrix.postScale(scale, scale, middlePoint.x, middlePoint.y);  
                        }  
                    }  
                      
                    if(mode == ROTA)  
                    {  
                        float newAngle = getDegree(event);  
                        matrix.set(savedMatrix);  
                        float degrees = newAngle - oldAngle;  
                        matrix.postRotate(degrees, middlePoint.x, middlePoint.y);  
                    }  
                    break;  
                }  
                setImageMatrix(matrix);  
                invalidate();  
                gdetector.onTouchEvent(event);  
                return true;  
            }  
        });  
    }  
  
    // 計算兩個手指之間的距離  
        private float getDistance(MotionEvent event)  
        {  
            float x = event.getX(0) - event.getX(1);  
            float y = event.getY(0) - event.getY(1);  
            return FloatMath.sqrt(x * x + y * y);  
        }  
      
        // 計算兩個手指所造成的直線和x軸的角度  
        private float getDegree(MotionEvent event)  
        {  
            return (float)(Math.atan((event.getY(1) - event.getY(0)) / (event.getX(1) - event.getX(0))) * 180f);  
        }  
  
        // 計算兩個手指之間,中間點的座標  
        private PointF midPoint( MotionEvent event)  
        {  
            PointF point = new PointF();  
            float x = event.getX(0) + event.getX(1);  
            float y = event.getY(0) + event.getY(1);  
            point.set(x / 2, y / 2);  
          
            return point;  
        }  
}

下面完善PatImageViewActivity.java的代碼,使之以下:

package com.pat.imageview;  
  
import android.app.Activity;  
import android.graphics.Bitmap;  
import android.graphics.BitmapFactory;  
import android.os.Bundle;  
import android.view.Window;  
import android.view.WindowManager;  
  
public class PatImageViewActivity extends Activity  
{  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
          
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,   
                WindowManager.LayoutParams.FLAG_FULLSCREEN);  
          
        PatImageView piv = new PatImageView(this);  
        Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.sophie);  
  
        piv.setImageBitmap(bmp);  
          
        setContentView(piv);  
    }  
}



 

因爲有些手勢在模擬器上沒法模擬,因此就不上運行結果的圖片了。本人在真機上運行後(照片就不拍了,有點累啦),能夠輕鬆作到:

1.     很方便地拖動圖片(好比,單指按住屏幕進行拖動)

2.     很方便地縮放圖片(好比,雙指按住屏幕進行分開或者併攏操做,可分別實現放大或者縮小圖片的功能)

3.     長按出現振動後,能夠很方便地旋轉圖片(一個手指固定,另一個手指圍繞那個固定的手指運動)。
相關文章
相關標籤/搜索