Android Matrix图像变换处理

时间:2023-02-07 14:16:55

Canvas类中drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)方法中有个参数类型是Matrix,从字面上理解是矩阵的意思,而实际上它也确实是个3x3的矩阵。Matrix在Android中的主要作用是图像变换,如平移、旋转、缩放、扭曲等。 关于图像如何通过矩阵进行变化可参考这篇文章图像处理—关于像素坐标矩阵变换(平移,旋转,缩放,错切)

Matrix内部通过维护一个float[9]的数组来构成3x3矩阵的形式,而实际上所有的变换方法说到底就是通过更改数组中某个或某几个位置的数值。Matrix提供了setValues()和getValues()方法来操作数组。

Android Matrix图像变换处理

显然这两个方法使用起来很不方便,如果只有这样,那Matrix估计就不会有人使用了。Google轻易不会辜负我们的信任,Matrix提供了若干简单易用的变换方法和映射方法供开发者使用。

Matrix变换方法

1、Translate(平移)、Scale(缩放)、Rotate(旋转)、Skew(扭曲)

Matrix提供了Translate(平移)、Scale(缩放)、Rotate(旋转)、Skew(扭曲)四中变换操作,这四种操作实质上是调用了setValues()方法来设置矩阵数组来达到变换效果。

除Translate(平移)外,Scale(缩放)、Rotate(旋转)、Skew(扭曲)都可以围绕一个中心点来进行,如果不指定,在默认情况下是围绕(0, 0)来进行相应的变换的。

  • Translate操作中

    • 在x轴上使用正数进行平移将向右移动图像,而使用负数将向左移动图像。
    • 在y轴上使用正数进行平移将向下移动图像,而使用负数将向上移动图像。
  • Scale操作中,

    • 在x轴上使用正数进行缩放将在中心点的右边缩放图像,而使用负数将在中心点的左边缩放图像。
    • 在y轴上使用正数进行缩放将在中心点的下边缩放图像,而使用负数将在中心点的上边缩放图像。

2、pre、set、post

Matrix提供的四种操作,每一种都有pre、set、post三种形式。原因是矩阵乘法不满足乘法交换律,因此左乘还是右乘最终的效果都不一样。左乘或者右乘是针对变换矩阵的,得到最终的变换矩阵后,与图像矩阵想成,图像矩阵在右边。

pre方法表示矩阵左乘,例如:变换矩阵为A,原始矩阵为B,pre方法的含义即是A*B

post方法表示矩阵右乘,例如:变换矩阵为A,原始矩阵为B,post方法的含义即是B*A

我们可以把Matrix变换想象成一个队列,队列里面包含了若干个变换操作,队列中每个操作按照先后顺序操作变换目标完成变换,pre相当于向队首增加一个操作,post相当于向队尾增加一个操作,set相当于清空当前队列重新设置。

///这段代码只有translate(100, 100)生效,因为第二个set会把之前队列中的操作清除。

Matrix m = new Matrix();
m.setRotate(100);
m.setTranslate(100, 100);
//这段代码先执行translate(100, 100),后执行rotate(100)

Matrix m = new Matrix();
m.setTranslate(100, 100);
m.postRotate(100);
///这段代码先执行rotate(100),后执行translate(100, 100)
Matrix m = new Matrix();
m.setTranslate(100, 100);
m.preRotate(100);
///这段代码的执行顺序: translate(100f, 100f) -> scale(2f, 2f) -> scale(0.5f, 0.5f) -> translate(50f, 50f)

Matrix m = new Matrix();
m.preScale(2f, 2f);
m.preTranslate(100f, 100f);
m.postScale(0.5f, 0.5f);
m.postTranslate(50f, 50f);
//这段代码的执行顺序:translate(50f, 50f) -> scale(0.8f, 0.8f) -> scale(3f, 3f)
Matrix m = new Matrix();
m.postTranslate(100, 100);
m.preScale(0.5f, 0.5f);
m.setScale(0.8f, 0.8f);
m.postScale(3f, 3f);
m.preTranslate(50f, 50f);

3、Matrix 映射方法

Matrix提供了mapPoints(),mapRects(),mapVectors()等映射方法,用来获取经Matrix映射后的值

//这段代码的作用是获取经过平移后该bitmap四个点的坐标
Matrix m = new Matrix();
m.postTranslate(100f, 100f);

float[] src = {
0, 0,
0, bitmap.getHeight(),
bitmap.getWidth(), 0,
bitmap.getWidth(), bitmap.getHeight()
};
float[] dst = new float[8];

m.mapPoints(dst, src);

常用的矩阵变化方法

下面是常用变换的例子,除了变换参数,其他代码也有一些差异,可以多认识几种实现方法

平移Translate

/**
* 图片移动
*/

protected void bitmapTranslate(float dx, float dy) {
// 需要根据移动的距离来创建图片的拷贝图大小
Bitmap afterBitmap = Bitmap.createBitmap(
(int) (baseBitmap.getWidth() + dx),
(int) (baseBitmap.getHeight() + dy), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(afterBitmap);
Matrix matrix = new Matrix();
// 设置移动的距离
matrix.setTranslate(dx, dy);
canvas.drawBitmap(baseBitmap, matrix, paint);
iv_after.setImageBitmap(afterBitmap);
}

缩放Scale

/**
* 缩放图片
*/

protected void bitmapScale(float x, float y) {
// 因为要将图片放大,所以要根据放大的尺寸重新创建Bitmap
Bitmap afterBitmap = Bitmap.createBitmap(
(int) (baseBitmap.getWidth() * x),
(int) (baseBitmap.getHeight() * y), baseBitmap.getConfig());
Canvas canvas = new Canvas(afterBitmap);
// 初始化Matrix对象
Matrix matrix = new Matrix();
// 根据传入的参数设置缩放比例
matrix.setScale(x, y);
// 根据缩放比例,把图片draw到Canvas上
canvas.drawBitmap(baseBitmap, matrix, paint);
iv_after.setImageBitmap(afterBitmap);
}

注意观察下面两个镜像的实现方法,

X轴镜像

/**
* x轴镜像
*/

protected void bitmapXMirror() {
// 因为要将图片放大,所以要根据放大的尺寸重新创建Bitmap
Bitmap afterBitmap = Bitmap.createBitmap(
baseBitmap.getWidth() ,
baseBitmap.getHeight() , baseBitmap.getConfig());
Canvas canvas = new Canvas(afterBitmap);
// 初始化Matrix对象
Matrix matrix = new Matrix();
// 根据传入的参数设置缩放比例
matrix.postScale(-1, 1);
matrix.postTranslate(baseBitmap.getWidth(), 0);
// 根据缩放比例,把图片draw到Canvas上
canvas.drawBitmap(baseBitmap, matrix, paint);
iv_after.setImageBitmap(afterBitmap);
}

Y轴镜像

/**
* y轴镜像
*/

protected void bitmapYMirror() {

// 初始化Matrix对象
Matrix matrix = new Matrix();
// 根据传入的参数设置缩放比例
matrix.postScale(1, -1);
//根据变换矩阵,绘制新的图片
Bitmap afterBitmap = Bitmap.createBitmap(baseBitmap, 0, 0,baseBitmap.getWidth(),baseBitmap.getHeight(), matrix, true);
iv_after.setImageBitmap(afterBitmap);
}

旋转Rotate

/**
* 图片旋转
*/

protected void bitmapRotate(float degrees) {
// 创建一个和原图一样大小的图片
Bitmap afterBitmap = Bitmap.createBitmap(baseBitmap.getWidth(),
baseBitmap.getHeight(), baseBitmap.getConfig());
Canvas canvas = new Canvas(afterBitmap);
Matrix matrix = new Matrix();
// 根据原图的中心位置旋转
matrix.setRotate(degrees, baseBitmap.getWidth() / 2,
baseBitmap.getHeight() / 2);
canvas.drawBitmap(baseBitmap, matrix, paint);
iv_after.setImageBitmap(afterBitmap);
}

扭曲Skew

/**
* 倾斜图片
*/

protected void bitmapSkew(float dx, float dy) {
// 根据图片的倾斜比例,计算变换后图片的大小,
Matrix matrix = new Matrix();
// 设置图片倾斜的比例
matrix.setSkew(dx, dy);
//根据变换矩阵,绘制新的图片
Bitmap afterBitmap = Bitmap.createBitmap(baseBitmap, 0, 0,baseBitmap.getWidth(),baseBitmap.getHeight(), matrix, true);
iv_after.setImageBitmap(afterBitmap);
}

源码

完整的Demo

MainActivity.java

public class MainActivity extends AppCompatActivity {
private Button btn_scale, btn_rotate, btn_translate, btn_skew,btn_XMirror,btn_YMirror;
private ImageView iv_base, iv_after;
private Bitmap baseBitmap;
private Paint paint;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

btn_scale = (Button) findViewById(R.id.btn_scale);
btn_rotate = (Button) findViewById(R.id.btn_rotate);
btn_translate = (Button) findViewById(R.id.btn_translate);
btn_skew = (Button) findViewById(R.id.btn_skew);
btn_XMirror = (Button) findViewById(R.id.btn_x_mirror);
btn_YMirror = (Button) findViewById(R.id.btn_y_mirror);

btn_scale.setOnClickListener(click);
btn_rotate.setOnClickListener(click);
btn_translate.setOnClickListener(click);
btn_skew.setOnClickListener(click);
btn_XMirror.setOnClickListener(click);
btn_YMirror.setOnClickListener(click);

iv_base = (ImageView) findViewById(R.id.iv_base);
iv_after = (ImageView) findViewById(R.id.iv_after);

baseBitmap = BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher);
iv_base.setImageBitmap(baseBitmap);

// 设置画笔,消除锯齿
paint = new Paint();
paint.setAntiAlias(true);
}

private View.OnClickListener click = new View.OnClickListener() {

@Override
public void onClick(View v) {

switch (v.getId()) {
case R.id.btn_scale:
bitmapScale(2.0f, 4.0f);
break;
case R.id.btn_rotate:
bitmapRotate(180);
break;
case R.id.btn_translate:
bitmapTranslate(20f, 20f);
break;
case R.id.btn_skew:
bitmapSkew(0.2f, 0.4f);
break;
case R.id.btn_x_mirror:
bitmapXMirror();
break;
case R.id.btn_y_mirror:
bitmapYMirror();
break;
default:
break;
}

}
};

/**
* 缩放图片
*/

protected void bitmapScale(float x, float y) {
// 因为要将图片放大,所以要根据放大的尺寸重新创建Bitmap
Bitmap afterBitmap = Bitmap.createBitmap(
(int) (baseBitmap.getWidth() * x),
(int) (baseBitmap.getHeight() * y), baseBitmap.getConfig());
Canvas canvas = new Canvas(afterBitmap);
// 初始化Matrix对象
Matrix matrix = new Matrix();
// 根据传入的参数设置缩放比例
matrix.setScale(x, y);
// 根据缩放比例,把图片draw到Canvas上
canvas.drawBitmap(baseBitmap, matrix, paint);
iv_after.setImageBitmap(afterBitmap);
}


/**
* x轴镜像
*/

protected void bitmapXMirror() {
// 因为要将图片放大,所以要根据放大的尺寸重新创建Bitmap
Bitmap afterBitmap = Bitmap.createBitmap(
baseBitmap.getWidth() ,
baseBitmap.getHeight() , baseBitmap.getConfig());
Canvas canvas = new Canvas(afterBitmap);
// 初始化Matrix对象
Matrix matrix = new Matrix();
// 根据传入的参数设置缩放比例
matrix.postScale(-1, 1);
matrix.postTranslate(baseBitmap.getWidth(), 0);
// 根据缩放比例,把图片draw到Canvas上
canvas.drawBitmap(baseBitmap, matrix, paint);
iv_after.setImageBitmap(afterBitmap);
}


/**
* y轴镜像
*/

protected void bitmapYMirror() {

// 初始化Matrix对象
Matrix matrix = new Matrix();
// 根据传入的参数设置缩放比例
matrix.postScale(1, -1);
//根据变换矩阵,绘制新的图片
Bitmap afterBitmap = Bitmap.createBitmap(baseBitmap, 0, 0,baseBitmap.getWidth(),baseBitmap.getHeight(), matrix, true);
iv_after.setImageBitmap(afterBitmap);
}




/**
* 倾斜图片
*/

protected void bitmapSkew(float dx, float dy) {
// 根据图片的倾斜比例,计算变换后图片的大小,
Matrix matrix = new Matrix();
// 设置图片倾斜的比例
matrix.setSkew(dx, dy);
//根据变换矩阵,绘制新的图片
Bitmap afterBitmap = Bitmap.createBitmap(baseBitmap, 0, 0,baseBitmap.getWidth(),baseBitmap.getHeight(), matrix, true);
iv_after.setImageBitmap(afterBitmap);
}

/**
* 图片移动
*/

protected void bitmapTranslate(float dx, float dy) {
// 需要根据移动的距离来创建图片的拷贝图大小
Bitmap afterBitmap = Bitmap.createBitmap(
(int) (baseBitmap.getWidth() + dx),
(int) (baseBitmap.getHeight() + dy), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(afterBitmap);
Matrix matrix = new Matrix();
// 设置移动的距离
matrix.setTranslate(dx, dy);
canvas.drawBitmap(baseBitmap, matrix, paint);
iv_after.setImageBitmap(afterBitmap);
}

/**
* 图片旋转
*/

protected void bitmapRotate(float degrees) {
// 创建一个和原图一样大小的图片
Bitmap afterBitmap = Bitmap.createBitmap(baseBitmap.getWidth(),
baseBitmap.getHeight(), baseBitmap.getConfig());
Canvas canvas = new Canvas(afterBitmap);
Matrix matrix = new Matrix();
// 根据原图的中心位置旋转
matrix.setRotate(degrees, baseBitmap.getWidth() / 2,
baseBitmap.getHeight() / 2);
canvas.drawBitmap(baseBitmap, matrix, paint);
iv_after.setImageBitmap(afterBitmap);
}
}

xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >


<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >


<Button
android:id="@+id/btn_scale"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="缩放" />


<Button
android:id="@+id/btn_rotate"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="旋转" />


<Button
android:id="@+id/btn_translate"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="平移" />


<Button
android:id="@+id/btn_skew"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="倾斜" />


<Button
android:id="@+id/btn_x_mirror"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="X轴镜像" />


<Button
android:id="@+id/btn_y_mirror"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Y轴镜像" />


</LinearLayout>
<!-- 原始图片 -->
<ImageView
android:id="@+id/iv_base"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<!-- 处理之后的图片 -->
<ImageView
android:id="@+id/iv_after"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />


</LinearLayout>

参考:
Android Matrix
Android—Matrix类的使用
Android中Matrix的pre post set方法理解
Android–Matrix图片变换处理
android bitmap翻转180,镜像的简单实现方法
图像处理—关于像素坐标矩阵变换(平移,旋转,缩放,错切)