Android中的矩阵(Matrix)变换

写在前面:

本篇博文将结合一个简单的Demo,讲解一下如何通过Matrix实现对Bitmap对象的一些简单处理,例如平移,旋转,放缩等。此外还会讲解一下矩阵乘法中的左乘右乘在Android中的代码实现。

一、Matrix类

这里说的Matrix类是位于"android.graphics.Matrix"包下的。它是Android提供的一个矩阵工具类,它本身不能对图像或View进行变换,但它可与其他API结合来控制图形、View的变换,如Canvas。在Matrix类中,提供了一些方法来控制图片变换:

  • setTranslate(float dx,float dy):控制Matrix进行位移。
  • setSkew(float kx,float ky):控制Matrix进行倾斜,kx、ky为X、Y方向上的比例。
  • setSkew(float kx,float ky,float px,float py):控制Matrix以px、py为轴心进行倾斜,kx、ky为X、Y方向上的倾斜比例。
  • setRotate(float degrees):控制Matrix进行depress角度的旋转,轴心为(0,0)。
  • setRotate(float degrees,float px,float py):控制Matrix进行depress角度的旋转,轴心为(px,py)。
  • setScale(float sx,float sy):设置Matrix进行缩放,sx、sy为X、Y方向上的缩放比例。
  • setScale(float sx,float sy,float px,float py):设置Matrix以(px,py)为轴心进行缩放,sx、sy为X、Y方向上的缩放比例。

其中每一个功能又以三个方法为一组,以位移的Translate为例:

  •  setTranslate(float dx,float dy)
  •  preTranslate(float dx,float dy)
  •  postTranslate(float dx,float dy)

其中pre前缀和post前缀分别对应了矩阵乘法中的右乘(前乘) 和左乘(后乘),至于更加详细的部分我会与本文后半部分讲解。

二、通过Matrix实现单矩阵变换

所谓单矩阵变换说的玄乎,其实就是单次变换,比如平移一次或者旋转一次。

2.1.对Bitmap进行放缩

因为单矩阵变换思路都一样,只是采用了不同的set方法去设置矩阵,我就在这里统一说下变换思路:

1.首先要根据变换的情况计算变换后的位图大小,以避免图片变换之后跑出原区域导致显示不全的情况

2.用计算之后的位图创建Canvas实例

3.设置矩阵Matrix

4.开始绘图

5.最后把绘制好的位图设置到ImageView上展示

/**
 * 图片缩放
 * */
private void bitmapScale(float x,float y){
    newBitmap = Bitmap.createBitmap((int) (mBitmap.getWidth() * x),(int) (mBitmap.getHeight() * y), mBitmap.getConfig());
    Canvas canvas = new Canvas(newBitmap);
    Matrix matrix = new Matrix();
    matrix.setScale(x, y);
    //第一个参数为待绘制的bitmap,第二个参数为矩阵,第三个参数为画笔
    canvas.drawBitmap(mBitmap, matrix, mPaint);
    imageView.setImageBitmap(newBitmap);
    mBitmap = newBitmap;
}

上述代码中newBitmap是变换之后的图片,mBitmap是变换之前的图片。Bitmap.createBitmap()方法接收三个参数,依次为宽、高、配置项,这里沿用了原图的配置。最后 mBitmap = newBitmap 是为了使后续变换能够在上一次变换的基础上进行。

2.2.对Bitmap进行旋转

/**
 * 图片旋转
 * */
private void bitmapRotate(float degrees){
    newBitmap = Bitmap.createBitmap(mBitmap.getWidth(),mBitmap.getHeight(),mBitmap.getConfig());
    Canvas canvas = new Canvas(newBitmap);
    Matrix matrix = new Matrix();
    matrix.setRotate(degrees,mBitmap.getWidth()/2,mBitmap.getHeight()/2);
    canvas.drawBitmap(mBitmap, matrix, mPaint);
    imageView.setImageBitmap(newBitmap);
    mBitmap = newBitmap;
}

2.3.对Bitmap进行平移

/**
 * 图片平移
 * */
private void bitmapTranslate(float dx,float dy){
    newBitmap = Bitmap.createBitmap((int)(mBitmap.getWidth()+Math.abs(dx)),(int)(mBitmap.getHeight()+Math.abs(dy)),mBitmap.getConfig());
    Canvas canvas = new Canvas(newBitmap);
    Matrix matrix = new Matrix();
    matrix.setTranslate(dx,dy);
    canvas.drawBitmap(mBitmap,matrix,mPaint);
    imageView.setImageBitmap(newBitmap);
    mBitmap = newBitmap;
}

三、通过Matrix实现多矩阵变换

在讲多矩阵变换之前得明确矩阵乘法的基本性质,需要牢记以下两点:

  • 矩阵乘法满足结合律 (AB)C=A(BC),因此可以任何两个先乘, 自由结合
  • 矩阵乘法大多时候不满足交换律,因此不可以颠倒矩阵的顺序进行乘算

弄清楚矩阵乘法之后,我们再来看set前缀pre前缀以及post前缀变换时的区别,先来看看官方API文档:

public boolean postTranslate (float dx, float dy)

Postconcats the matrix with the specified translation. M' = T(dx, dy) * M

public boolean preTranslate (float dx, float dy)

Preconcats the matrix with the specified translation. M' = M * T(dx, dy)

在图形学中,矩阵M右乘A,表示的是 A * M,而矩阵 M 左乘 A,则表示的是 M * A,一比较,我们可以看出,pre其实执行的就是右乘的操作,而post执行的就是左乘的操作。这是因为,在图像处理中,越靠近右边的矩阵越先执行,所以pre(也就是先的意思)所设置的矩阵T(Scale,Rotation也是一样的)就会先于其一开始设置的Scale执行,而post(后的意思)的因为是左乘,所以它会放在最左边,那么就会最后执行。这时你们肯定会问set呢?set前缀的方法首先会将该Matrix设置为对角矩阵,即相当于调用reset()方法,然后再设置该Matrix的变换矩阵。纯文字的讲解估计说的已经云里雾里了,我们来看看具体的矩阵乘法:

以放缩变换Scale(Sx,Sy)为例,其变换矩阵为\begin{bmatrix} Sx,0,0\\ 0,Sy,0 \\ 0,0,1 \end{bmatrix}

因为是多矩阵变换,这里我们再列举一个平移变换Translate(Tx,Ty),其变换矩阵为\begin{bmatrix} 1,0,Tx\\ 0,1,Ty\\ 0,0,1 \end{bmatrix}

如果我们调用了

matrix.setScale(Sx, Sy);

那么对应的矩阵乘法为:\begin{bmatrix} Sx,0,0\\ 0,Sy,0 \\ 0,0,1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix}

如果我们调用了

matrix.setTranslate(Tx,Ty);

那么对应的矩阵乘法为\begin{bmatrix} 1,0,Tx\\ 0,1,Ty\\ 0,0,1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix}

如果是多矩阵变换的情况,我们调用了

matrix.setScale(Sx,Sy);
matrix.preTranslate(Tx,Ty);

那么对应的矩阵乘法为\begin{bmatrix} Sx,0,0\\ 0,Sy,0 \\ 0,0,1 \end{bmatrix} \begin{bmatrix} 1,0,Tx\\ 0,1,Ty\\ 0,0,1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix}

因为矩阵乘法满足结合律,因此该变换又可以视为先平移变换后放缩变换。

如果我们调用的是

matrix.setScale(Sx,Sy);
matrix.postTranslate(Tx,Ty);

那么对应的矩阵乘法为\begin{bmatrix} 1,0,Tx\\ 0,1,Ty\\ 0,0,1 \end{bmatrix} \begin{bmatrix} Sx,0,0\\ 0,Sy,0 \\ 0,0,1 \end{bmatrix} \begin{bmatrix} x\\ y\\ 1 \end{bmatrix}

同样的,该变换又可以视为先放缩变换后平移变换,显然结果是不同于先平移后放缩的。

具体实现的代码如下:

1.先平移后缩放

/**
 * 平移后缩放
 * */
private void bitmapTranslateAndRScale(float dx,float dy,float sx,float sy){
    newBitmap = Bitmap.createBitmap((int)((mBitmap.getWidth()+Math.abs(dx))*sx),(int)((mBitmap.getHeight()+Math.abs(dy))*sy),mBitmap.getConfig());
    Canvas canvas = new Canvas(newBitmap);
    Matrix matrix = new Matrix();
    matrix.setScale(sx,sy);
    matrix.preTranslate(dx,dy);
    canvas.drawBitmap(mBitmap,matrix,mPaint);
    imageView.setImageBitmap(newBitmap);
    mBitmap = newBitmap;
}

2.先缩放后平移

/**
 * 缩放后平移
 * */
private void bitmapScaleAndTranslate(float dx,float dy,float sx,float sy){
    newBitmap = Bitmap.createBitmap((int)(mBitmap.getWidth()*sx+Math.abs(dx)),(int)(mBitmap.getHeight()*sy+Math.abs(dy)),mBitmap.getConfig());
    Canvas canvas = new Canvas(newBitmap);
    Matrix matrix = new Matrix();
    matrix.setScale(sx,sy);
    matrix.postTranslate(dx,dy);
    canvas.drawBitmap(mBitmap,matrix,mPaint);
    imageView.setImageBitmap(newBitmap);
    mBitmap = newBitmap;
}

运行起来后,我们就会发现这两种变换的结果是不同的,但是“先平移后缩放”的变换又和我们先点击平移按钮再点击缩放按钮最终效果一致,这和我们之前的理论分析又对应上了。

最后贴一下源码:

MainActivity.java

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private ImageView imageView;
    private Button btnReset,btnScale,btnRotate,btnTranslate,btnPostTrans,btnPreTrans;
    private Bitmap mBitmap,newBitmap;
    private Paint mPaint;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
        btnReset.setOnClickListener(this);
        btnScale.setOnClickListener(this);
        btnRotate.setOnClickListener(this);
        btnTranslate.setOnClickListener(this);
        btnPostTrans.setOnClickListener(this);
        btnPreTrans.setOnClickListener(this);
        bitmapReset();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }
    private void initViews(){
        imageView = (ImageView) findViewById(R.id.imageView);
        btnReset = (Button) findViewById(R.id.btn_reset);
        btnScale = (Button) findViewById(R.id.btn_scale);
        btnRotate = (Button) findViewById(R.id.btn_rotate);
        btnTranslate = (Button) findViewById(R.id.btn_translate);
        btnPostTrans = (Button) findViewById(R.id.btn_post_trans);
        btnPreTrans = (Button) findViewById(R.id.btn_pre_trans);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_reset:
                bitmapReset();
                break;
            case R.id.btn_scale:
                bitmapScale(0.5f,0.5f);
                break;
            case R.id.btn_rotate:
                bitmapRotate(45);
                break;
            case R.id.btn_translate:
                bitmapTranslate(100,100);
                break;
            case R.id.btn_post_trans:
                bitmapScaleAndTranslate(100,100,0.5f,0.5f);
                break;
            case R.id.btn_pre_trans:
                bitmapTranslateAndRScale(100,100,0.5f,0.5f);
                break;
            default:
                break;
        }
    }
    private void bitmapReset(){
        mBitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.timg),
                400,400,false);
        imageView.setImageBitmap(mBitmap);
    }
    /**
     * 图片缩放
     * */
    private void bitmapScale(float x,float y){
        newBitmap = Bitmap.createBitmap((int) (mBitmap.getWidth() * x),(int) (mBitmap.getHeight() * y), mBitmap.getConfig());
        Canvas canvas = new Canvas(newBitmap);
        Matrix matrix = new Matrix();
        matrix.setScale(x, y);
        canvas.drawBitmap(mBitmap, matrix, mPaint);
        imageView.setImageBitmap(newBitmap);
        mBitmap = newBitmap;
    }
    /**
     * 图片旋转
     * */
    private void bitmapRotate(float degrees){
        newBitmap = Bitmap.createBitmap(mBitmap.getWidth(),mBitmap.getHeight(),mBitmap.getConfig());
        Canvas canvas = new Canvas(newBitmap);
        Matrix matrix = new Matrix();
        matrix.setRotate(degrees,mBitmap.getWidth()/2,mBitmap.getHeight()/2);
        canvas.drawBitmap(mBitmap, matrix, mPaint);
        imageView.setImageBitmap(newBitmap);
        mBitmap = newBitmap;
    }
    /**
     * 图片平移
     * */
    private void bitmapTranslate(float dx,float dy){
        newBitmap = Bitmap.createBitmap((int)(mBitmap.getWidth()+Math.abs(dx)),(int)(mBitmap.getHeight()+Math.abs(dy)),mBitmap.getConfig());
        Canvas canvas = new Canvas(newBitmap);
        Matrix matrix = new Matrix();
        matrix.setTranslate(dx,dy);
        canvas.drawBitmap(mBitmap,matrix,mPaint);
        imageView.setImageBitmap(newBitmap);
        mBitmap = newBitmap;
    }
    /**
     * 平移后缩放
     * */
    private void bitmapTranslateAndRScale(float dx,float dy,float sx,float sy){
        newBitmap = Bitmap.createBitmap((int)((mBitmap.getWidth()+Math.abs(dx))*sx),(int)((mBitmap.getHeight()+Math.abs(dy))*sy),mBitmap.getConfig());
        Canvas canvas = new Canvas(newBitmap);
        Matrix matrix = new Matrix();
        matrix.setScale(sx,sy);
        matrix.preTranslate(dx,dy);
        canvas.drawBitmap(mBitmap,matrix,mPaint);
        imageView.setImageBitmap(newBitmap);
        mBitmap = newBitmap;
    }
    /**
     * 缩放后平移
     * */
    private void bitmapScaleAndTranslate(float dx,float dy,float sx,float sy){
        newBitmap = Bitmap.createBitmap((int)(mBitmap.getWidth()*sx+Math.abs(dx)),(int)(mBitmap.getHeight()*sy+Math.abs(dy)),mBitmap.getConfig());
        Canvas canvas = new Canvas(newBitmap);
        Matrix matrix = new Matrix();
        matrix.setScale(sx,sy);
        matrix.postTranslate(dx,dy);
        canvas.drawBitmap(mBitmap,matrix,mPaint);
        imageView.setImageBitmap(newBitmap);
        mBitmap = newBitmap;
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        app:layout_constraintBottom_toTopOf="@+id/guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_launcher" />

    <android.support.constraint.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.4" />

    <Button
        android:id="@+id/btn_reset"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:text="Reset"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline" />

    <Button
        android:id="@+id/btn_scale"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:text="Scale"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_reset" />

    <Button
        android:id="@+id/btn_rotate"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:text="Rotate"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_scale" />

    <Button
        android:id="@+id/btn_translate"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:text="Translate"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_rotate" />

    <Button
        android:id="@+id/btn_post_trans"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:text="Scale And Translate"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_translate" />

    <Button
        android:id="@+id/btn_pre_trans"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:text="Translate And Scale"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_post_trans" />

</android.support.constraint.ConstraintLayout>

猜你喜欢

转载自blog.csdn.net/Ein3614/article/details/82189622
今日推荐