andriod 矩阵操作

1 概述

这里我们会详细讲解matrix的各个方法,以及它的用法。matrix叫做矩阵,在前面讲解 ColorFilter 的文章中,我们讲解了ColorMatrix,他是一个4*5的矩阵。而这里,我们讲解的Matrix不是用于处理颜色的,而是处理图形的。他是一个3*3的矩阵。

2 原理

先看看matrix的矩阵是什么样子的:

这里写图片描述

这里可以查看Matrix的代码得到。那么这个矩阵分别代表了什么呢,这里通过他们的名字可以看出,scale是缩放,skew是错切(canvas变换中有讲过),trans是平移,persp代表透视(官方文档中,也没有详细讲解,透视在这里只做简单介绍)。这里需要把矩阵根据他们的作用划分为4块:

这里写图片描述

如上图所示,这四块区域各有作用。后面会详细讲解各个作用,先来看看这个矩阵是如何影响图像的。先看看屏幕的坐标系:

这里写图片描述

看上图,这里表示了屏幕的坐标系,其中的x,y轴是大家所熟知的,但是其实,一个物体他是存在于一个三维空间的,所以必然会有z轴。我们的屏幕,就像是一个窗口,透过它,我们看到了屏幕后面的世界,那里面有各种物体,我们看到的是映射在x,y平面上的一个投射图像。屏幕就像是一个镜头一样,将里面的物体映射到x,y平面上,成为一个二维的图像。那么如果,我们把屏幕这个镜头沿着z轴,拉远或者拉进,那么图像会有什么变化呢,肯定会变小或者变大。就好比坐在飞机上透过窗口看地面的汽车,和在地面上看到的大小是不同的。

结论就是,在屏幕上显示的像素,不仅仅有x,y坐标,其实还有z轴的影响。所以这里对应的像素描述由一个3行一列的矩阵来表示:

这里写图片描述

x,y分别代表x,y轴上的坐标,而1代表屏幕在z轴上的坐标为默认的。如果将1变大,那么屏幕会拉远, 图形会变小。

现在我们来看看matrix怎么作用于每个像素的值。这里需要用到矩阵的乘法,首先需要明确的是,矩阵的前乘和后乘是不相同的,也就是说不满足乘法交换律。

这里我们通过一个旋转变换来看看原理,其实一张图片围绕一个点旋转,也就是所有的点都围绕一个点旋转,所以只需要关注一个点的情况即可:

假定有一个点 ,相对坐标原点顺时针旋转后的情形,同时假定P点离坐标原点的距离为r,如下图:

这里写图片描述

那么就有:

这里写图片描述

换做矩阵运算就如下图:

这里写图片描述

从这里就可以看出,矩阵中的值,是如何作用于像素点的x,y坐标以及z轴远近。

同时,可以看到,上面的矩阵四块区域的切分也是因为矩阵乘法的操作决定的,由于这里的乘法运算中,左上角的四个值,可以和x,y值做乘法运算,所以可以影响到旋转等操作,而右上角的模块,只能做加法,所以只能影响到平移。右下角的模块主要管z轴,自然就可以进行等比的缩放了,左下角的模块一般不去动他,否则会把x,y值加入到z轴中来,会不可控。

3 基本方法解析

讲解完了matrix作用于像素点的原理之后,我们逐个讲解它的方法。

(1) 构造函数

public Matrix()
public Matrix(Matrix src)
  • 1
  • 2
  • 3

构造函数有两个,第一个是直接创建一个单位矩阵,第二个是根据提供的矩阵创建一个新的矩阵(采用deep copy)

单位矩阵如下:

这里写图片描述

(2) isIdentity与isAffine

public boolean isIdentity()//判断是否是单位矩阵
public boolean isAffine()//判断是否是仿射矩阵
  • 1
  • 2
  • 3

是否是单位矩阵很简单,就不做讲解了,这里是否是仿射矩阵可能大家不好理解。

首先来看看什么是仿射变换。仿射变换其实就是二维坐标到二维坐标的线性变换,保持二维图形的“平直性”(即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(指保持二维图形间的相对位置关系不变,平行线还是平行线,而直线上点的位置顺序不变),可以通过一系列的原子变换的复合来实现,原子变换就包括:平移、缩放、翻转、旋转和错切。这里除了透视可以改变z轴以外,其他的变换基本都是上述的原子变换,所以,只要最后一行是0,0,1则是仿射矩阵。

(3) rectStaysRect

public boolean rectStaysRect()
  • 1
  • 2

判断该矩阵是否可以将一个矩形依然变换为一个矩形。当矩阵是单位矩阵,或者只进行平移,缩放,以及旋转90度的倍数的时候,返回true。

(4) reset

public void reset()
  • 1
  • 2

重置矩阵为单位矩阵。

(5) setTranslate

public void setTranslate(float dx, float dy)
  • 1
  • 2

设置平移效果,参数分别是x,y上的平移量。 
效果图如下:

这里写图片描述

代码如下:

Matrix matrix = new Matrix();
canvas.drawBitmap(bitmap, matrix, paint);

matrix.setTranslate(100, 1000);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5

(6) setScale

public void setScale(float sx, float sy, float px, float py)
public void setScale(float sx, float sy)
  • 1
  • 2
  • 3

两个方法都是设置缩放到matrix中,sx,sy代表了缩放的倍数,px,py代表缩放的中心。这里跟上面比较类似不做讲解了。

(7) setRotate

 public void setRotate(float degrees, float px, float py)
 public void setRotate(float degrees)
  • 1
  • 2
  • 3

和上面类似,不再讲解。

(8) setSinCos

public void setSinCos(float sinValue, float cosValue, float px, float py)
public void setSinCos(float sinValue, float cosValue)
  • 1
  • 2
  • 3

这个方法乍一看可能有点蒙,其实在前面的原理中,我们讲解了一个旋转的例子,他最终的矩阵效果是这样的:

这里写图片描述

其实旋转,就是使用了这样的matrix,显而易见,这里的参数就清晰了。 
sinValue:对应图中的sin值 
cosValue:对应cos值 
px:中心的x坐标 
py:中心的y坐标

看一个示例,我们把图像旋转90度,那么90度对应的sin和cos分别是1和0。

这里写图片描述

看代码如下:

Matrixmatrix = new Matrix();
matrix.setSinCos(1, 0, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3

(9) setSkew

public void setSkew(float kx, float ky, float px, float py)
public void setSkew(float kx, float ky)
  • 1
  • 2
  • 3

错切,这里kx,ky分别代表了x,y上的错切因子,px,py代表了错切的中心。不了解错切了在前面canvas变换中去查看,这里不再讲解。

(10) setConcat

public boolean setConcat(Matrix a,Matrix b)
  • 1
  • 2

将当前matrix的值变为a和b的乘积,它的意义在下面的 进阶方法中来探讨。

4 进阶方法解析

上面的基本方法中,有关于变换的set方法都可以带来不同的效果,但是每个set都会把上个效果清除掉,例如依次调用了setSkew,setTranslate,那么最终只有setTranslate会起作用,那么如何才和将两种效果复合呢。Matrix给我们提供了很多方法。但是主要都是2类:

preXXXX:以pre开头,例如preTranslate 
postXXXX:以post开头,例如postScale

他们分别代表了前乘,和后乘。看一段代码:

Matrix matrix = new Matrix();
matrix.setTranslate(100, 1000);
matrix.preScale(0.5f, 0.5f);
  • 1
  • 2
  • 3

这里matrix前乘了一个scale矩阵,换算成数学式如下:

这里写图片描述

从上面可以看出,最终得出的matrix既包含了缩放信息也有平移信息。 
后乘自然就是matrix在后面,而缩放矩阵在前面,由于矩阵前后乘并不等价,也就导致了他们的效果不同。我们来看看后乘的结果:

这里写图片描述

可以看到,结果跟上面不同,并且这也不是我们想要的结果,这里缩放没有更改,但是平移被减半了,换句话说,平移的距离也被缩放了。所以需要注意前后乘法的关系。

来看看他们对应的效果图:

前乘:

这里写图片描述

后乘:

这里写图片描述

可以明显看到,后乘的平移距离受了影响。

了解清除了前后乘的意义,在使用的过程中,多个效果的叠加时,一样要注意,否则效果达不到预期。

5 其他方法解析

matrix除了上面的方法外,还有一些其他的方法,这里依次解析

(1) setRectToRect

public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf)
  • 1
  • 2

将rect变换成rect,上面的rectStaysRect已经说过,要保持rect只能做缩放平移和选择90度的倍数,那么这里其实也是一样,只是这几种变化,这里通过stf参数来控制。

ScaleToFit 有如下四个值:

FILL: 可能会变换矩形的长宽比,保证变换和目标矩阵长宽一致。 
START:保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。左上对齐。 
CENTER: 保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。 
END:保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。右下对齐。

这里使用谷歌的api demo的图片作为例子:

这里写图片描述

(2) setPolyToPoly

public boolean setPolyToPoly(float[] src, int srcIndex,float[] dst, int dstIndex,int pointCount)
  • 1
  • 2

通过指定的0-4个点,原始坐标以及变化后的坐标,来得到一个变换矩阵。如果指定0个点则没有效果。

下面通过例子分别说明1到4个点的可以达到的效果:

这里写代码片##### 1个点,平移 
只指定一个点,可以达到平移效果:

这里写图片描述

代码如下:

float[] src = {0, 0};
int DX = 300;
float[] dst = {0 + DX, 0 + DX};
matrix.setPolyToPoly(src, 0, dst, 0, 1);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
2个点,旋转或者缩放

两个点,可以达到旋转效果或者缩放效果,缩放比较简单,这里我们来看旋转效果,一个点指定中心,一点指出旋转的效果

这里写图片描述

代码如下

int bw = bitmap.getWidth();
int bh = bitmap.getHeight();
float[] src = {bw / 2, bh / 2, bw, 0};
float[] dst = {bw / 2, bh / 2, bw / 2 + bh / 2, bh / 2 + bw / 2};
matrix.setPolyToPoly(src, 0, dst, 0, 2);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

图片的中心点作为旋转的中心,前后不变,右上角变化到了下方,所以导致图片旋转了90度。

3个点,错切

使用3个点,可以产生错切效果,指定3个顶点,一个固定,另外两个移动。

看图:

这里写图片描述

代码如下:

Matrix matrix = new Matrix();
int bw = bitmap.getWidth();
int bh = bitmap.getHeight();
float[] src = {0,0, 0, bh,bw,bh};
float[] dst = {0, 0, 200, bh, bw + 200, bh};
matrix.setPolyToPoly(src, 0, dst, 0, 3);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
4个点,透视

透视就是观察的角度变化了。导致投射到平面上的二维图像变化了。

我们看下面的例子,更容易理解:

这里写图片描述

图片看起来好像倾斜了,实现特别简单:

Matrix matrix = new Matrix();
int bw = bitmap.getWidth();
int bh = bitmap.getHeight();
float[] src = {0, 0, 0, bh, bw, bh, bw, 0};
int DX = 100;
float[] dst = {0 + DX, 0, 0, bh, bw, bh, bw - DX, 0};
matrix.setPolyToPoly(src, 0, dst, 0, 4);
canvas.drawBitmap(bitmap, matrix, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以看到,只是把左右两个顶点往里面收拢了,这样就得出了一个有3d效果的透视图。

(3) invert

public boolean invert(Matrix inverse)
  • 1
  • 2

反转当前矩阵,如果能反转就返回true并将反转后的值写入inverse,否则返回false。当前矩阵*inverse=单位矩阵。

反转前后有什么效果,我们来看看示例:

这里写图片描述

可以看到,反转之后,其实是对效果的一种反转。

(4) mapPoints

public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,int pointCount)
public void mapPoints(float[] dst, float[] src)
public void mapPoints(float[] pts)
  • 1
  • 2
  • 3
  • 4

映射点的值到指定的数组中,这个方法可以在矩阵变换以后,给出指定点的值。 
dst:指定写入的数组 
dstIndex:写入的起始索引,x,y两个坐标算作一对,索引的单位是对,也就是经过两个值才加1 
src:指定要计算的点 
srcIndex:要计算的点的索引 
pointCount:需要计算的点的个数,每个点有两个值,x和y。

(5) mapVectors

public void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex,int vectorCount)
public void mapVectors(float[] dst, float[] src)
public void mapVectors(float[] vecs)
  • 1
  • 2
  • 3
  • 4

与上面的mapPoionts基本类似,这里是将一个矩阵作用于一个向量,由于向量的平移前后是相等的,所以这个方法不会对translate相关的方法产生反应,如果只是调用了translate相关的方法,那么得到的值和原本的一致。

(6) mapRect

public boolean mapRect(RectF dst, RectF src)
public boolean mapRect(RectF rect)
  • 1
  • 2
  • 3

返回值即是调用的rectStaysRect(),这个方法前面有讲过,这里把src中指定的矩形的左上角和右下角的两个点的坐标,写入dst中。

(7) mapRadius

public float mapRadius(float radius)
  • 1
  • 2

返回一个圆圈半径的平均值,将matrix作用于一个指定radius半径的圆,随后返回的平均半径。

猜你喜欢

转载自blog.csdn.net/liudongdong19/article/details/80322278