这里我们会详细讲解matrix的各个方法,以及它的用法。matrix叫做矩阵,在前面讲解 ColorFilter 的文章中,我们讲解了ColorMatrix,他是一个4*5的矩阵。而这里,我们讲解的Matrix不是用于处理颜色的,而是处理图形的。他是一个3*3的矩阵。
先看看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轴中来,会不可控。
讲解完了matrix作用于像素点的原理之后,我们逐个讲解它的方法。
public Matrix()
public Matrix(Matrix src)
构造函数有两个,第一个是直接创建一个单位矩阵,第二个是根据提供的矩阵创建一个新的矩阵(采用deep copy)
单位矩阵如下:
public boolean isIdentity()//判断是否是单位矩阵
public boolean isAffine()//判断是否是仿射矩阵
是否是单位矩阵很简单,就不做讲解了,这里是否是仿射矩阵可能大家不好理解。
首先来看看什么是仿射变换。仿射变换其实就是二维坐标到二维坐标的线性变换,保持二维图形的“平直性”(即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(指保持二维图形间的相对位置关系不变,平行线还是平行线,而直线上点的位置顺序不变),可以通过一系列的原子变换的复合来实现,原子变换就包括:平移、缩放、翻转、旋转和错切。这里除了透视可以改变z轴以外,其他的变换基本都是上述的原子变换,所以,只要最后一行是0,0,1则是仿射矩阵。
public boolean rectStaysRect()
判断该矩阵是否可以将一个矩形依然变换为一个矩形。当矩阵是单位矩阵,或者只进行平移,缩放,以及旋转90度的倍数的时候,返回true。
public void reset()
重置矩阵为单位矩阵。
public void setTranslate(float dx, float dy)
设置平移效果,参数分别是x,y上的平移量。
效果图如下:
代码如下:
Matrix matrix = new Matrix();
canvas.drawBitmap(bitmap, matrix, paint);
matrix.setTranslate(100, 1000);
canvas.drawBitmap(bitmap, matrix, paint);
public void setScale(float sx, float sy, float px, float py)
public void setScale(float sx, float sy)
两个方法都是设置缩放到matrix中,sx,sy代表了缩放的倍数,px,py代表缩放的中心。这里跟上面比较类似不做讲解了。
public void setRotate(float degrees, float px, float py)
public void setRotate(float degrees)
和上面类似,不再讲解。
public void setSinCos(float sinValue, float cosValue, float px, float py)
public void setSinCos(float sinValue, float cosValue)
这个方法乍一看可能有点蒙,其实在前面的原理中,我们讲解了一个旋转的例子,他最终的矩阵效果是这样的:
其实旋转,就是使用了这样的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);
public void setSkew(float kx, float ky, float px, float py)
public void setSkew(float kx, float ky)
错切,这里kx,ky分别代表了x,y上的错切因子,px,py代表了错切的中心。不了解错切了在前面canvas变换中去查看,这里不再讲解。
public boolean setConcat(Matrix a,Matrix b)
将当前matrix的值变为a和b的乘积,它的意义在下面的 进阶方法中来探讨。
上面的基本方法中,有关于变换的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);
这里matrix前乘了一个scale矩阵,换算成数学式如下:
从上面可以看出,最终得出的matrix既包含了缩放信息也有平移信息。
后乘自然就是matrix在后面,而缩放矩阵在前面,由于矩阵前后乘并不等价,也就导致了他们的效果不同。我们来看看后乘的结果:
可以看到,结果跟上面不同,并且这也不是我们想要的结果,这里缩放没有更改,但是平移被减半了,换句话说,平移的距离也被缩放了。所以需要注意前后乘法的关系。
来看看他们对应的效果图:
前乘:
后乘:
可以明显看到,后乘的平移距离受了影响。
了解清除了前后乘的意义,在使用的过程中,多个效果的叠加时,一样要注意,否则效果达不到预期。
后面一篇我们继续讲解matrix的高阶用法