继上一篇《Android开发之图像处理那点事——滤镜》,这次我们来讲一下图片的变换操作,本篇主要是介绍常见的图像变换4种操作(平移、缩放、旋转、错切)
对于图像的颜色处理,Andriod系统给我们提供一个很方便的颜色矩阵类ColorMatrix,同样的,Android系统也给图像的变换处理提供了矩阵类Matrix,相比颜色矩阵的4 * 5,变换矩阵来得更加简单,它是由一个3 * 3的数字矩阵组成,我们来了解一下变换原理。
变换矩阵
在上一篇文章中,我们讲到像素点的颜色是由RGBA1组成的,分别代表红、绿、蓝、透明度各通道值还有颜色偏移量,在变换矩阵中也是类似的,像素点的位置是由矩阵C(X、Y、L)组成的,分别代表像素点在X、Y轴的位置还有偏移量。
根据矩阵的乘法,我们可以得出下面的等式:
X1 = a * X + b * Y + c
Y1 = d * X + e * Y + f
L = g * X + h * Y + i
当a=1,b和c=0,可以知道X1=1X+0+0=1X,
当e=1,d和f=0,可以知道Y1=0+1Y+0=1Y
当i=1,g和h=0,可以知道1=0+0+1=1
由此我们可以得到变换矩阵的单位矩阵:
在日常开发中,我们涉及到的图像变换大致有平移、缩放、旋转、错切(很少),这些操作就是对这3 * 3的矩阵做数值上的调整,和上一篇讲颜色矩阵变化是类似的,不清楚的朋友,具体可以参考上一篇文章的讲解,下面我们快速过一下流程。
平移操作
像素点的平移其实就是对像素点的x和y坐标进行移动,如下图,从点p1移动到点p,假设p1(x1,y1)分别平移了x0和y0距离得到p(x,y),
得到的公式就是:
x = x1 + x0
y = y1 + y0
矩阵所表现出来的形式:
缩放操作
对于单个像素点是不存在缩放操作的,但如果是一系列的像素点,我们就可以根据x和y轴做一定比例的缩放,假设缩放比例为K1,得到的公式是:
x = K1 * x0
y = k1 * y0
矩阵所表现出来的形式:
旋转操作
像素点的旋转是围绕一个点旋转一定的角度得到新的像素点位置,为了便于理解,这里带上一张图:
如上图以原点为中心旋转,点(x0,y0)旋转了β°角度到了p(x,y),假设斜边为r,根据三角函数我们可以知道:
x0 = r * cosα
y0 = r * sinα
那么x和y就是x0和y0旋转了α角度后,再旋转了β角度,根据三角函数可以推出:
x = r * cos(α + β) = r * cosα * cosβ - r * sinα * sinβ = x0 * cosβ - y0 * sinβ
y = r * sin(α + β) = r * sinα * cosβ + r * cosα * sinβ = y0 * cosβ + x0 * sinβ
将上面的式子代入,根据推导公式,我们可以知道矩阵的表现形式为:
错切操作
这个平时用的比较少,大概了解一下就可以,错切分为水平错切和垂直错切。
水平错切效果就是让所有像素点的Y轴坐标不变,X轴坐标按照比例进行平移,且平移的大小与该点到Y轴的距离成成正比,计算公式为:
x = x0 + k1 * y0
y = y0
矩阵变现形式为:
垂直错切让所有像素点的X轴坐标不变,Y轴坐标按照比例进行平移,且平移的大小与该点到X轴的距离成成正比,计算公式为:
x = x0
y = y0+ k2 * x0
矩阵变现形式为:
两个方向都错切,公式为:
x = x0 + k1 * y0
y = k2 * x0 * y0
根据以上的矩阵表现方式,我们可以推出图像的变换规律为:
Matrix
了解了各种变换原理后,我们来看下Matrix类,这是Android系统给我们提供的图像变换矩阵类,打开源码,我们可以看到这9个常量:
public static final int MSCALE_X = 0; //!< use with getValues/setValues
public static final int MSKEW_X = 1; //!< use with getValues/setValues
public static final int MTRANS_X = 2; //!< use with getValues/setValues
public static final int MSKEW_Y = 3; //!< use with getValues/setValues
public static final int MSCALE_Y = 4; //!< use with getValues/setValues
public static final int MTRANS_Y = 5; //!< use with getValues/setValues
public static final int MPERSP_0 = 6; //!< use with getValues/setValues
public static final int MPERSP_1 = 7; //!< use with getValues/setValues
public static final int MPERSP_2 = 8; //!< use with getValues/setValues
我们把它按3 * 3的矩阵形式排开,可以得到:
对比下刚才上面我们推导的矩阵表现形式,怎么样,是不是更有感觉了,我们可以得到如下信息:
MTRANS_X、MTRANS_Y 决定了平移(Translate)
MSCALE_X、MSCALE_Y 决定了缩放( Scale)
MSCALE_X、MSKEW_X、MSCALE_Y、MSKEW_Y 决定了旋转( Rotate)
MSKEW_X、MSKEW_Y 决定了错切( Skew)
其实图像的变换操作无非就是对这个3 * 3的矩阵进行值的变化,而Matrix类只是帮我们封装好了这些操作,让我们无需关心细节,更加专注业务的实现,我们可以在Matrix中找到各操作对应的调用方法:
简单的看个小Demo:
上面分别对图像进行了平移、缩放、旋转、错切操作,贴一下核心代码:
Matrix matrix = new Matrix();
matrix.setTranslate(50, 50);//x,y坐标平移50像素
canvas.drawBitmap(mBitmap, matrix, null);
Matrix matrix = new Matrix();
matrix.setScale(2, 2);//x,y坐标放大原来2倍
canvas.drawBitmap(mBitmap, matrix, null);
Matrix matrix = new Matrix();
matrix.setRotate(20);//x,y坐标旋转20度
canvas.drawBitmap(mBitmap, matrix, null);
Matrix matrix = new Matrix();
matrix.setSkew(2, 2);//x,y坐标按比例错切平移2
canvas.drawBitmap(mBitmap, matrix, null);
当然这些操作也是可以组合起来的,Matrix里提供了一系列的postXXX,preXXX方法:
其实就是线性代数中矩阵的左乘和右乘,由于矩阵的乘法是不满足乘法交换律的,所以变换操作的执行顺序是对结果有影响的,继续来个小Demo:
实现代码:
Matrix matrix = new Matrix();
matrix.setTranslate(200, 200);
matrix.postScale(2, 2);
matrix.postRotate(20);
canvas.drawBitmap(mBitmap, matrix, null);
实现代码:
Matrix matrix = new Matrix();
matrix.setTranslate(200, 200);
matrix.preScale(2, 2);
matrix.preRotate(20);
canvas.drawBitmap(mBitmap, matrix, null);
简单粗暴可以这样理解:Matrix操作是一系列的任务存放在一个队列里,pre是把当前任务插入队列前,post是插入队列后,set则是清空队列所有任务,再入队。
这些看似很简单很基础的东西却恰恰是最重要的,它可以创造出很多东西,比如图片的自由手势缩放,微博上面的自定义贴纸,美图秀秀里的相框拼接等效果都离不开它。
源码下载:
这里附上源码地址(欢迎Star,欢迎Fork):BeautyImageDemo