Matrix使用解析

来源:互联网 发布:直接给mac安装win10 编辑:程序博客网 时间:2024/06/14 18:13

今日科技快讯

昨日,今年全球最大并购案诞生:美国电信业巨头 AT&T 宣布将斥资854亿美元收购美国电视传媒巨头时代华纳。时代华纳集团是全世界最知名的电视媒体企业,旗下拥有一系列知名电视频道,比如CNN、HBO电视网、TNT、卡通网络、TBS等,以及华纳兄弟电影和电视工作室。

专家分析,AT&T 在一年前以490亿美元收购DirecTV(卫星电视)之后,还这么着急鲸吞时代华纳,主要是由于网络流视频越来越多地吞并传统观影模式的用户,电视及电影行业正快速经历快速变革。还不足20年的谷歌、Facebook、亚马逊这样的互联网企业,市值已经超过了控制着“管道”的传统电信运营商,所以传统运营商们需要打通上下游,以期在未来能有一席之地。

作者简介

Hello,大家好,新的一周开始啦!本篇是 leon 的第二篇投稿,带大家了解如何使用Matrix,本文不涉及高深的数学知识,咳咳。。。感兴趣的朋友可自行研究。

leon 的博客地址:

http://ltlovezh.com

进入主题

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_X MTRANS_Y 值,分别表示X和Y轴上的位移量,假设在X和Y轴上分别位移100px,那么对应的Matrix就是:

2. 实现 Scale 操作

缩放操作在Matrix中对应的是 MSCALE_X MSCALE_Y 值,分别表示X和Y轴上的缩放比例,假设在X和Y轴上分别放大2倍,那么对应的Matrix就是:

3. 实现Rotate操作

旋转操作在 Matrix 中对应是 MSCALE_XMSCALE_YMSKEW_X MSKEW_Y 值,假设我们要以坐标原点为中心,旋转A度,那么对应的 Matrix 就是:

4. 实现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的原理

http://blog.csdn.net/pathuang68/article/details/6991867

讲解的非常详细,强烈推荐。

除了可以直接修改 Matrix值Matrix类 还提供了一些API来 操作Matrix。这里主要介绍几类比较常用的API。

setXXX/preXXX/postXXX

XXX 可以是 TranslateRotateScaleSkew Concat (表示直接操作Matrix矩阵)。我们主要搞清楚这3种API的区别就OK了。

    • setXXX,首先会将该 Matrix 设置为单位矩阵,即相当于调用 reset() 方法,然后再设置该 Matrix 的值。

    • preXXX,不会重置 Matrix,而是被当前 Matrix 左乘(矩阵运算中,A左乘B等于A B),即 M’ = M S(XXX)。

    • postXXX,不会重置 Matrix,而是被当前 Matrix 右乘(矩阵运算中,A右乘B等于B A),即 M’ = S(XXX) M。

    当这些API同时使用时,又会出现什么效果那,我们来看个例子:

    最后得到的结果是: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 里的 scaletranslaterotate 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范围内,应该如何操作那?!

    首先想到的可能是下面的方案:


    但是 这种方式实际上不是非常的准确,通过 Matrix 变换后的矩形区域并不是真实的 Bitmap区域,而是包含 bitmap 的矩形区域(很难描述啊),看下图就知道了:

    图中的绿色矩形区域就是我们进行判断的rect区域,很明显误差很大哈。既然正向操作不可行,那就只能试下逆向操作了:


    通过这种方式,首先会对Touch坐标进行逆矩阵操作,然后再判断是否落在原始bitmap矩形区域内(上图中的小企鹅),就比较精确了。精妙哈!

    Canvas.getMatrix的坑

    通过 Canvas 获取 Matrix矩阵 的拷贝,从API16开始,不再推荐使用。至于原因,可参考Google的解释:

    Issue 24517: Canvas getMatrix/setMatrix with hardware acceleration bug

    https://code.google.com/p/android/issues/detail?id=24517

    里面提供了示例代码。

    主要原因是,在开启硬件加速的情况下,Canvas.getMatrix 获取的矩阵是相对于 Canvas 所属 View 的,而 Canvas.setMatrix 则会把所设置的矩阵当做相对于整个屏幕(包括系统栏)。

    所谓相对于 某个View,就是说不管这个 View 在屏幕中的任何位置,通过 这个View 的 Canvas 获取的 Matrix 的 X和Y位移都是0,也就是 Matrix 在 当前View 的本地坐标系中,和 View 的 left 和 top值 无关。

    所谓相对于整个屏幕,也就很好理解了,即获取的 Matrix 是在整个屏幕表示的世界坐标系中的,一般情况下,获取的 矩阵的X位移view.left值Y位移系统栏的高度 + view.top值。并且后续对 Canvas 的变换操作都是基于这个初始矩阵进行的。

    所以在硬件加速的情况下,调用 canvas.setMatrix(canvas.getMatrix()) 后,就会导致 View 默认从屏幕左上角(包含了系统栏)开始绘制,这样就会有一部分内容被系统栏遮挡住。

    这里对Google提供的示例代码进行了简化,就是把一个自定义View添加到带有系统栏的 Activity 中,自定义 View 的 onDraw 方法如下所示:


    在没有开启硬件加速的情况下,矩形区域 和 红点 显示正常。

    Canvas.getMatrix 方法获取的 Matrix 也是在屏幕世界坐标系中的,即 Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 60.0][0.0, 0.0, 1.0]},其中Y轴位移为60,表示的就是状态栏的高度。这样重新 setMatrix 之后,就不会出现问题,如图所示:

    但是在开启硬件加速的情况下,矩形区域有一部分被系统栏遮挡住了,可以对比下红点的位置就就知道了,同时获取的Matrix也是相对于当前View本地坐标系的,即 Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]},没有产生任何位移。这样重新 setMatrix 之后,就会出现问题(因为对Matrix的理解不同),如图所示:

    既然,Canvas.getMatrix 被废弃了,那有什么替换的方法嘛?!这里Google并没有明确的指出。但是我觉得有两种方式可以实现类似功能:

    • 不获取 Matrix,直接通过 Canvas 提供的位移、旋转、缩放等API来实现类似功能。

    • 从API11开始,提供了 View Properties,可以直接对 View 进行位移、旋转、缩放等操作,同时也提供了 View.getMatrix 方法来获取当前 View 的 Matrix(也算是Canvas.getMatrix的替代方案了)。当然,这个 Matrix 也是相对于View本身本地坐标系的。

    最后

    本文简单介绍了 Matrix 的基本使用方法,关于 Matrix 的底层原理,还没有涉猎,后续深入研究后,再补充进来。

    最后推荐几篇比较好的 Matrix 相关的文章(文中提过的一个系列就不在这里赘述):

    深入理解 Android 中的 Matrix

    http://geek.csdn.net/news/detail/89873

    更多

    每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都会有好心情。

    如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

    欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

    原创粉丝点击