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

注:本篇文章爲轉載文章,因爲原文格式排版較亂,但是內容非常棒,所以整理一下,方便以後查看。
查看原文請戳:http://blog.csdn.net/pathuang68/article/details/6991988
Matrix介紹文章請戳:http://blog.csdn.net/pathuang68/article/details/6991867

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

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

//          // 4. 縮放
//          matrix.setScale(2f, 2f);
//          // 做下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊
//          matrix.postTranslate(view.getImageBitmap().getWidth(), view.getImageBitmap().getHeight());
//          view.setImageMatrix(matrix);

//          // 5. 錯切 - 水平
//          matrix.setSkew(0.5f, 0f);
//          // 做下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊         
//          matrix.postTranslate(view.getImageBitmap().getWidth(), 0f);
//          view.setImageMatrix(matrix);

//          // 6. 錯切 - 垂直
//          matrix.setSkew(0f, 0.5f);
//          // 做下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊             
//          matrix.postTranslate(0f, view.getImageBitmap().getHeight());
//          view.setImageMatrix(matrix);

//          7. 錯切 - 水平 + 垂直
//          matrix.setSkew(0.5f, 0.5f);
//          // 做下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊             
//          matrix.postTranslate(0f, view.getImageBitmap().getHeight());
//          view.setImageMatrix(matrix);

//          // 8. 對稱 (水平對稱)
//          float matrix_values[] = {1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f};
//          matrix.setValues(matrix_values);
//          // 做下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊 
//          matrix.postTranslate(0f, view.getImageBitmap().getHeight() * 2f);
//          view.setImageMatrix(matrix);

//          // 9. 對稱 - 垂直
//          float matrix_values[] = {-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f};
//          matrix.setValues(matrix_values);
//          // 做下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊 
//          matrix.postTranslate(view.getImageBitmap().getWidth() * 2f, 0f);
//          view.setImageMatrix(matrix);

//          // 10. 對稱(對稱軸爲直線y = x)
//          float matrix_values[] = {0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f};
//          matrix.setValues(matrix_values);
//          // 做下面的平移變換,純粹是爲了讓變換後的圖像和原圖像不重疊             
//          matrix.postTranslate(view.getImageBitmap().getHeight() + view.getImageBitmap().getWidth(), 
//                  view.getImageBitmap().getHeight() + view.getImageBitmap().getWidth());
//          view.setImageMatrix(matrix);

            view.invalidate();
        }
        return true;
    }
}

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

  • 平移

    輸出的結果:

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

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

    輸出的結果:

    它實際上是
    matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);
    matrix.postTranslate(view.getImageBitmap().getWidth()* 1.5f, 0f);
    這兩條語句綜合作用的結果。根據第一部分中“二、旋轉變換”裏面關於圍繞某點旋轉的公式,
    matrix.setRotate(45f,view.getImageBitmap().getWidth() / 2f, view.getImageBitmap().getHeight() / 2f);
    所產生的轉換矩陣就是:

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

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

所以它實際上就是:

出去計算上的精度誤差,我們可以看到我們計算出來的結果,和程序直接輸出的結果是一致的。


  • 旋轉(圍繞座標原點旋轉,在加上兩次平移,效果同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中變換後的圖像時一樣的。


  • 縮放變換

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

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

    大家可以對照第一部分中的“三、縮放變換”和“一、平移變換”說法,自行驗證結果。

  • 錯切變換(水平錯切)

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

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

    大家可以對照第一部分中的“四、錯切變換”和“一、平移變換”的相關說法,自行驗證結果。

  • 錯切變換(垂直錯切)

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

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

    大家可以對照第一部分中的“四、錯切變換”和“一、平移變換”的相關說法,自行驗證結果。

  • 錯切變換(水平+垂直錯切)

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

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

    大家可以對照第一部分中的“四、錯切變換”和“一、平移變換”的相關說法,自行驗證結果。

  • 對稱變換(水平對稱)

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

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

    大家可以對照第一部分中的“五、對稱變換”和“一、平移變換”的相關說法,自行驗證結果。

  • 對稱變換(垂直對稱)

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

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

    大家可以對照第一部分中的“五、對稱變換”和“一、平移變換”的相關說法,自行驗證結果。

  • 對稱變換(對稱軸爲直線)

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

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

    大家可以對照第一部分中的“五、對稱變換”和“一、平移變換”的相關說法,自行驗證結果。

  • 關於先乘和後乘的問題
    由於矩陣的乘法運算不滿足交換律,我們在前面曾經多次提及先乘、後乘的問題,即先乘就是矩陣運算中右乘,後乘就是矩陣運算中的左乘。其實先乘、後乘的概念是針對變換操作的時間先後而言的,左乘、右乘是針對矩陣運算的左右位置而言的。以第一部分“二、旋轉變換”中圍繞某點旋轉的情況爲例:

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

當然,在實際中,如果首先指定了一個matrix,比如我們先setRotate(),即指定了上面變換矩陣中,中間的那個矩陣,那麼後續的矩陣到底是pre還是post運算,都是相對這個中間矩陣而言的。

所有這些,其實都是很自然的事情。


再次感謝原博主的文章

轉載請註明出處:http://blog.csdn.net/zhaokaiqiang1992

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