关于Android(安卓)Matrix pre post 的理解
16lz
2021-01-26
前言
最近由于项目需要,接触到矩阵相关的知识较多,在实践后于此做知识记录,方便下次用到的时候可以快速的捡起来。
正文
- 理论上,矩阵是按照长方形阵列排列的复数或实数集合
- 实际上,从源码看Matrix是用于坐标变换的3x3的矩阵【本文讨论的是graphics包下的矩阵,非opengl包下的矩阵】
- 单个Matrix对象在Java层保留一个Native层对象的指针,在Native堆上进行实际内存分配、使用、回收
- Java层内存是个【壳】,Native层内存才是【核】。
关键字段
3x3矩阵中各个字段
以(0,0)点为轴心旋转一定的角度,则实际改变的是左上角的四个数据
- 平移,右上角2个元素
- 旋转和缩放,左上角4个元素
数据结构
float数组,长度9
单位矩阵
⎡⎣⎢⎢100010001⎤⎦⎥⎥
第0、4、8号地址数值为1,其它地址为0的矩阵。单位矩阵乘法满足交换律。
变换过程
假设原坐标点为(x0, y0),作用于矩阵M,则变换后的坐标点为(x1, y1)。
(x1, y1, 1) = M * (x0, y0, 1)
这里的难点在于【M = M1 * M2 … * Mn】,即M可以为多个矩阵变换之后的结果。
计算顺序
M = M1 * M2 * ... * Mn 后 <------------------------------------ 先
- 从右边开始向左边进行矩阵的乘法运算,最后得出结果矩阵M
- M(n-1) * Mn 结果为矩阵Nn
- M(n-2) * Nn结果为矩阵N(n-1)
- M(n-3) * N(n-1)结果为N(n-2)
- …
- 为什么从右向左计算?个人理解是一种栈结构,每次做变换的时候都会在栈顶压入一个变换,最后退栈时依次从栈顶做矩阵乘法,故形成一种从右向左计算
先乘
M’ = M * X
- 当前矩阵为M
- 变换矩阵为X
- 在当前矩阵的【右边】乘以矩阵X,此处X可以为平移矩阵T, 旋转矩阵R, 缩放矩阵S
- 由于矩阵从右向左运算,故右边称之为【先】乘。
后乘
M’ = X * M
- 当前矩阵为M
- 变换矩阵为X
- 在当前矩阵的【左边】乘以矩阵X
- 由于矩阵从右边向左边运算,故左边称之为【后】乘。
Canvas中的矩阵变换
- Canvas中的矩阵变换都是先乘,也就是在当前矩阵的右边乘以一个矩阵
- 在ViewRootImpl从上向下遍历的过程中,在draw阶段作用在同一个Canvas对象的不同矩阵将不断放在右边
- 最后从右向左计算出整个变换矩阵M
- 然后拿着M矩阵对原坐标(x0, y0)进行矩阵变换
- 利用这个属性可以对某个View进行缩放截图等。
例如,Canvas中相对某点做缩放的代码
/**572 * Preconcat the current matrix with the specified scale.573 *574 * @param sx The amount to scale in X575 * @param sy The amount to scale in Y576 * @param px The x-coord for the pivot point (unchanged by the scale)577 * @param py The y-coord for the pivot point (unchanged by the scale)578 */579 public final void scale(float sx, float sy, float px, float py) {580 translate(px, py);581 scale(sx, sy);582 translate(-px, -py);583 }
translate
551 /**552 * Preconcat the current matrix with the specified translation553 *554 * @param dx The distance to translate in X555 * @param dy The distance to translate in Y556 */557 public void translate(float dx, float dy) {558 native_translate(mNativeCanvasWrapper, dx, dy);559 }
scale
561 /**562 * Preconcat the current matrix with the specified scale.563 *564 * @param sx The amount to scale in X565 * @param sy The amount to scale in Y566 */567 public void scale(float sx, float sy) {568 native_scale(mNativeCanvasWrapper, sx, sy);569 }
上面以(px, py)为轴心进行缩放(sx, sy)的scale API内部做的事情:
假如当前矩阵为M1,则
- translate(px, py)
- M1 * T1 先乘平移矩阵T1
- scale(sx, sy)
- M1 * T1 * S 先乘缩放矩阵S
- translate(-px, -py)
- M1 * T1 * S * T2 先乘平移矩阵T2
- 这三步的结果M = M1 * (T1 * (S * T2))
- 把M作用在原来的坐标点 (x1, y1, 1) = M * (x0, y0, 1)
- 从感官上理解经过以下步骤
- 整个图像先做相对于轴点的逆向平移变换
- 再做缩放变换
- 最后把图像平移回轴点,这样轴点的坐标不会受到缩放的影响。
pre和post混合使用
在理解了pre和post实际做的事情之后,就可以分析别人代码在干什么以及合理使用矩阵来达到你想要的效果。
例如我们要做变换M = T1 * R1 * S1 则写法可以是:
【注】
sin 30° = 0.5
cos 30° = 0.8660254
2 * sin 30° = 1.0
2 * cos 30° = 1.7320508
- 第一种写法
Matrix matrix = new Matrix(); matrix.preTranslate(1,1); matrix.preRotate(30); matrix.preScale(2, 2);
结果矩阵为:
Matrix{[1.7320508, -1.0, 1.0][1.0, 1.7320508, 1.0][0.0, 0.0, 1.0]}
- 第二种写法
Matrix matrix = new Matrix(); matrix.postScale(2, 2); matrix.postRotate(30); matrix.postTranslate(1,1);
结果矩阵为:
Matrix{[1.7320508, -1.0, 1.0][1.0, 1.7320508, 1.0][0.0, 0.0, 1.0]}
- 第三种写法
Matrix matrix = new Matrix(); matrix.preScale(2, 2); matrix.postRotate(30); matrix.postTranslate(1,1);
结果矩阵为:
Matrix{[1.7320508, -1.0, 1.0][1.0, 1.7320508, 1.0][0.0, 0.0, 1.0]}
- 第四种写法
Matrix matrix = new Matrix(); matrix.preRotate(30); matrix.postTranslate(1,1); matrix.preScale(2, 2);
结果矩阵为:
Matrix{[1.7320508, -1.0, 1.0][1.0, 1.7320508, 1.0][0.0, 0.0, 1.0]}
- …
总结
上文通过对Matrix的一些基本概念和pre、post变换的原理进行记录,方便根据需求做相应的变换来处理Bitmap和Canvas。因本人水平有限,难免理解不到位,请大家多多指正。
参考
- android matrix 最全方法详解与进阶(完整篇)
- Android UI学习|对Canvas和Matrix的理解
更多相关文章
- Android处理大图,如一张30M的大图,如何预防OOM
- Android通过多点触控的方式对图片进行缩放的实例代码
- Android图片裁剪实现(EnjoyCrop)
- Android9.0万年历毕业设计H5小应用webview应用源码分析已运行通
- android 开发之动画Activity
- android 显示16色的图片:输入用颜色矩阵,显示对应的16色位图
- Android之ObjectAnimator使用记录
- android对大图片压缩的方法
- android matrix camera处理图片绕X轴Y轴翻转(类似3D效果)