内容简介:最近在做图片和视频编辑时,大量使用了Matrix,这里记录下相关知识点,希望可以起到抛砖引玉的作用。Matrix的使用范围非常广泛,我们平时使用的Tween Animation,其在进行位移、缩放、旋转时,都是通过Matrix来实现的。除此之外,在进行图像变换操作时,Matrix也是最佳选择。Matrix是一个3*3的矩阵,如下所示:
最近在做图片和视频编辑时,大量使用了Matrix,这里记录下相关知识点,希望可以起到抛砖引玉的作用。
概述
Matrix的使用范围非常广泛,我们平时使用的Tween Animation,其在进行位移、缩放、旋转时,都是通过Matrix来实现的。除此之外,在进行图像变换操作时,Matrix也是最佳选择。
Matrix是一个3*3的矩阵,如下所示:
我们可以直接通过 Matrix.getValues
方法获取Matrix的矩阵值(浮点型数组类型),然后修改矩阵值(Matrix类为每一个矩阵值提供了固定索引,如:MSCALE_X、MSKEW_X等),最后通过 Matrix.setValues
方法重新设置Matrix值,以达到修改Matrix的目的。这种方式要求我们对Matrix每一个值的作用都要十分了解,操作起来比较繁琐,但却是最灵活、最彻底的操作方式。
具体要修改哪些Matrix值,则取决于要实现什么效果,从本质上这是一个数学问题,这里给出几种比较常见的方案:
- 实现Translate操作 位移操作在Matrix中对应是
MTRANS_X
和MTRANS_Y
值,分别表示X和Y轴上的位移量,假设在X和Y轴上分别位移100px,那么对应的Matrix就是
- 实现Scale操作 缩放操作在Matrix中对应的是
MSCALE_X
和MSCALE_Y
值,分别表示X和Y轴上的缩放比例,假设在X和Y轴上分别放大2倍,那么对应的Matrix就是
- 实现Rotate操作 旋转操作在Matrix中对应是
MSCALE_X
、MSCALE_Y
、MSKEW_X
和MSKEW_Y
值,假设我们要以坐标原点为中心,旋转A
度(顺时针),那么对应的Matrix就是
- 实现Skew操作 错切操作在Matrix中对应的是
MSKEW_X
和MSKEW_Y
,分别表示X和Y轴上的错切系数,假设在X轴上错切系数为0.5,Y轴上为2,那么对应的Matrix就是
其他3种操作都比较常见,但是错切操作我们可能不是很熟悉。
错切可分为水平错切和垂直错切。 水平错切表示变换后,Y坐标不变,X坐标则按比例发生平移,且平移的大小和Y坐标成正比,即新的坐标为 (X+Matrix[MSKEW_X] * Y,Y)
。 垂直错切表示变换后,X坐标不变,Y坐标则按比例发生平移,且平移的大小和X坐标成正比,即新的坐标为 (X,Y+Matrix[MSKEW_Y] * X)
。 当然,我们也可以同时实现水平错切和垂直错切。
关于为什么修改Matrix的这些值后,就实现了位移、缩放、旋转和错切操作,就主要是数学推导过程了,可以参考这篇文章—— Android中图像变换Matrix的原理 ,讲解的非常详细,强烈推荐。
实践
除了可以直接修改Matrix值,Matrix类还提供了一些API来操作Matrix。这里主要介绍几类比较常用的API。
setXXX
、 preXXX
和 postXXX
XXX可以是Translate、Rotate、Scale、Skew和Concat(表示直接操作Matrix矩阵)。我们主要搞清楚这3种API的区别就OK了。
setXXX preXXX postXXX
当这些API同时使用时,又会出现什么效果那,我们来看个例子:
Matrix matrix = new Matrix(); float[] points = new float[] { 10.0f, 10.0f }; matrix.postScale(2.0f, 3.0f);// 第1步 matrix.preRotate(90);// 第2步 matrix.setScale(2f, 3f);// 第3步 matrix.preTranslate(8.0f, 7.0f);// 第5步 matrix.postTranslate(18.0f, 17.0f);// 第4步 matrix.mapPoints(points); Log.i("test", points[0] + " : " + points[1]); 复制代码
最后得到的结果是: 54.0 : 68.0
可以发现,在第3步setScale之前的第1、2步根本就没有用,直接被第3步setScale覆盖了。所以最终的矩阵运算为
Translate(18,17) * Scale(2,3) * Translate(8,7) * (10,10)
这样,就很容易得出最后的结果了。
这里也许会有一个疑问,为什么坐标点(10,10)会被结果矩阵(矩阵运算虽然不满足交换律,但是满足结合律)左乘,而不是右乘。这一点我们看一下矩阵运算就会明白。
等号左边是变换后的坐标点,等号右边是Matrix矩阵左乘原始坐标点。因为Matrix是3行3列,坐标点是3行1列,所以正好可以相乘,但如果反过来,就不满足矩阵相乘的条件了( 左边矩阵的列数等于右边矩阵的行数
)。所以,就可以理解为什么是结果矩阵左乘原始坐标点了。
也正因为这一点以及矩阵的结合律,所以我们可以理解上面矩阵运算的流程:
先对原始坐标点(10,10)进行Translate(8,7)位移,然后再对中间坐标点(18,17)进行Scale(2,3)放大,最后再次对中间坐标点(36,51)进行Translate(18,17)操作,就得到了最后的坐标点(54,68)。
这里还有一个小Tips: 当需要对Matrix矩阵进行比较复杂的设置时,可以把这些复杂的设置,拆分为多个步骤,每一个步骤都是一个简单的Matrix,然后再依据这些步骤的先后顺序,决定是通过左乘 or 右乘得到结果矩阵,最后通过结果矩阵左乘原始坐标就OK了(设计时,可以拆分之后理解,但最终运算时还是要得到一个结果矩阵,再去操作原始坐标)。
还有一点需要了解:Canvas里的scale、translate、rotate和concat都是preXXX方法,如果要进行更多的变换可以先从Canvas获得Matrix, 变换后再设置回Canvas.
mapPoints
mapRect
mapVectors
这些API很简单,主要是根据当前Matrix矩阵对点、矩形区域和向量进行变换,以得到变换后的点、矩形区域和向量。经常和下面的 invert
方法结合使用。
invert
通过上面的mapXXX方法,可以获取变换后的坐标或者矩形。但假设我们知道了变换后的坐标,如何计算Matrix变换前的坐标那?! 此时通过 invert
方法获取的逆矩阵就派上用场了。所谓逆矩阵,就是Matrix旋转了30度,逆Matrix就反向旋转30度,Matrix放大n倍,逆Matrix就缩小n倍。 假设逆矩阵是invertMatrix,那么Matrix.preConcat(invertMatrix) 和 Matrix.postConcat(invertMatrix) 都应该等于单位矩阵(但实际上会有一些误差)。 所以,通过Matrix和invertMatrix对坐标进行变换的规则可总结如下:
逆矩阵在进行自定义View Touch事件处理时很有用,假设我们在自定义View中,通过Matrix(包含了旋转、缩放和位移操作)绘制了Bitmap,现在想要判断Touch事件是否在变换后的Bitmap范围内,应该如何操作那?! 首先想到的可能是下面的方案:
RectF rect = new RectF(bitmap.getWidth(),bitmap.getHeight()); //假设matrix就是对bitmap进行变换的矩阵 matrix.mapRect(rect); boolean isTouchBitmap = rect.contains(touchX,touchY); 复制代码
但是这种方式实际上不是非常的准确,通过matrix变换后的矩形区域并不是真实的Bitmap区域,而是包含bitmap的矩形区域(很难描述啊),看下图就知道了:
图中的绿色矩形区域就是我们进行判断的rect区域,很明显误差很大哈。既然正向操作不可行,那就只能试下逆向操作了:
RectF rect = new RectF(bitmap.getWidth(),bitmap.getHeight()); float eventFloat[] = new float[]{touchX,touchY}; //假设invertMatrix是matrix的逆矩阵,这里对Touch坐标进行逆向操作。 invertMatrix.mapPoints(eventFloat); boolean isTouchBitmap = rect.contains(eventFloat[0],eventFloat[1]); 复制代码
通过这种方式,首先会对Touch坐标进行逆矩阵操作,然后再判断是否落在原始bitmap矩形区域内(上图中的小企鹅),就比较精确了。精妙哈!!!
典型问题
这次在实现以双指中心为中心点进行缩放时,遇到一个问题:因为用户每次的双指中心都是不同的,但是最后Bitmap上屏时,只能有一个Matrix,那最终怎么处理缩放的中心点那?
这个问题可以简化成下面的模型:定义一个矩形区域:
val originRectF = RectF(0f, 0f, 4f, 4f) 复制代码
依次实现下面的Scale变换,得到最终的矩形区域。
先以 (2,1)
为中心点,放大2倍,再以 (2,3)
为中心点,放大2倍
实际上有两种方式,都可以实现上述的变换: 指定中心点的缩放
和 不指定中心点的缩放
指定中心点的缩放
首先,以 (2,1)
为中心点,放大2倍,即:
FirstScaleMatrix.setScale(2f, 2f, 2f, 1f) 复制代码
得到的Matrix如下所示:
然后,以 (2,3)
为中心点,放大2倍,即:
SecondScaleMatrix.setScale(2f, 2f, 2f, 3f) 复制代码
得到的Matrix如下所示:
最后,得到的效果就是:先以 (2,1)
为中心点,放大2倍,再以 (2,3)
为中心点,放大2倍,即:
ResultMatrix = SecondScaleMatrix * FirstScaleMatrix 复制代码
得到的Matrix如下所示:
最后,通过ResultMatrix矩阵实现对上述矩形区域的变换:
ResultMatrix.mapRect(tempRectf, originRectF) 复制代码
得到最后的矩形区域:
tempRectf = RectF(-6, -5, 10, 11) 复制代码
不指定中心点的缩放
通过不带中心点的缩放 + 位移来模拟指定中心点的缩放
具体的映射公式如下所示:
TranslateX = TranslateX * ScaleX + PivotX * (1 - ScaleX) TranslateY = TranslateY * ScaleY + PivotY * (1 - ScaleY) 复制代码
还是针对上面的变换:先以 (2,1)
为中心点,放大2倍,再以 (2,3)
为中心点,放大2倍。按照不指定中心点的缩放,如下所示:
var translateX = 0f var translateY = 0f // 1. 先以(2,1)为中心点,放大2倍 translateX = translateX * 2f + 2 * (1f - 2f) translateY = translateY * 2f + 1 * (1f - 2f) // 2. 再以(2,3)为中心点,放大2倍 translateX = translateX * 2f + 2 * (1f - 2f) translateY = translateY * 2f + 3 * (1f - 2f) // 3. 得到最后的Matrix val resultMatrix = Matrix() resultMatrix.setScale(4f, 4f) resultMatrix.postTranslate(translateX, translateY) resultMatrix.mapRect(tempRectf, originRectF) 复制代码
其中,resultMatrix如下所示:
得到最后的矩形区域:
tempRectf = RectF(-6, -5, 10, 11) 复制代码
可见,通过上述指定中心点的缩放和不指定中心点的缩放+位移,最后的Matrix都是相同的。
错误样例
因为在Canvas中Draw Bitmap时,是不考虑过程的,只考虑结果:最终生成的Matrix。所以上述先以 (2,1)
为中心点,放大2倍,再以 (2,3)
为中心点,放大2倍,其中是有 (2,1)
和 (2,3)
两个中心点的。如果我们单纯以最后一个中心点缩放累计的倍数,是不行的。
还是以上述的缩放过程为例:
val resultMatrix = Matrix() // 累积的倍数是4f,最后的中心点是(2,3) resultMatrix.setScale(4f, 4f, 2f, 3f) resultMatrix.mapRect(tempRectf, originRectF) 复制代码
其中,resultMatrix如下所示:
得到最后的矩形区域:
tempRectf = RectF(-6, -9, 10, 7) 复制代码
可见,通过这种方式计算出的ResultMatrix和之前计算出来的是不同的,在界面上的现象就是Bitmap会跳动。
小结
总之,就是通过不带中心点的缩放 + 位移,可以实现指定中心点的缩放。 例如:对下图Bitmap,以它的中心点(width/2,height/2)为缩放中心,对X轴放大一定的倍数。可以通过以下两种方式实现:
总结
关于Matrix的介绍到此就结束了,关键还是要多实践、实践、实践!!!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Spring 缓存大法
- phpmyadmin getshell大法
- Echarts 系列之复制粘贴大法
- 安卓APP测试之HOOK大法
- iOS 模拟器调试大法了解一下?
- 重启大法好!线上常见问题排查手册
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java常用算法手册
2012-5 / 59.00元
《Java常用算法手册》分三篇,共13章,分别介绍了算法基础、算法应用和算法面试题。首先介绍了算法概述,然后重点分析了数据结构和基本算法思想;接着,详细讲解了算法在排序、查找、数学计算、数论、历史趣题、游戏、密码学等领域中的应用;最后,列举了算法的一些常见面试题。书中知识点覆盖全面,结构安排紧凑,讲解详细,实例丰富。全书对每一个知识点都给出了相应的算法及应用实例,虽然这些例子都是以Java语言来编......一起来看看 《Java常用算法手册》 这本书的介绍吧!