图片和视频编辑之Matrix大法好

栏目: IOS · Android · 发布时间: 7年前

内容简介:最近在做图片和视频编辑时,大量使用了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值,则取决于要实现什么效果,从本质上这是一个数学问题,这里给出几种比较常见的方案:

  1. 实现Translate操作 位移操作在Matrix中对应是 MTRANS_XMTRANS_Y 值,分别表示X和Y轴上的位移量,假设在X和Y轴上分别位移100px,那么对应的Matrix就是
  1. 实现Scale操作 缩放操作在Matrix中对应的是 MSCALE_XMSCALE_Y 值,分别表示X和Y轴上的缩放比例,假设在X和Y轴上分别放大2倍,那么对应的Matrix就是
  1. 实现Rotate操作 旋转操作在Matrix中对应是 MSCALE_XMSCALE_YMSKEW_XMSKEW_Y 值,假设我们要以坐标原点为中心,旋转 A 度(顺时针),那么对应的Matrix就是
  1. 实现Skew操作 错切操作在Matrix中对应的是 MSKEW_XMSKEW_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。

setXXXpreXXXpostXXX

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对坐标进行变换的规则可总结如下:

图片和视频编辑之Matrix大法好

逆矩阵在进行自定义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的矩形区域(很难描述啊),看下图就知道了:

图片和视频编辑之Matrix大法好

图中的绿色矩形区域就是我们进行判断的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大法好

总结

关于Matrix的介绍到此就结束了,关键还是要多实践、实践、实践!!!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Host Your Web Site In The Cloud

Host Your Web Site In The Cloud

Jeff Barr / SitePoint / 2010-9-28 / USD 39.95

Host Your Web Site On The Cloud is the OFFICIAL step-by-step guide to this revolutionary approach to hosting and managing your websites and applications, authored by Amazon's very own Jeffrey Barr. "H......一起来看看 《Host Your Web Site In The Cloud》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具