上一篇文章《Matrix 原理剖析》 介绍了 Matrix 的基础原理,本文介绍 Matrix 一些常用方法以及具体的使用场景
文中部分内容及图片参考自:blog.csdn.net/gb702250823…
方法类别 | 相关 API | 摘要 |
---|---|---|
数值操作 | set、reset、setValues、getValues | 设置、重置、设置数值、获取数值 |
数值计算 | mapPoints、mapRadius、mapRect、mapVectors | 计算变换后的数值 |
设置 | setConcat、setRotates、setScale、setSkew、setTranslate | 设置变换 |
前乘 | preConcat、preRotate、preScale、preSkew、preTranslate | 前乘变换 |
后乘 | postConcat、postRotate、postScale、postSkew、postTranslate | 后乘变换 |
特殊方法 | setPolyToPoly、setRectToRect | 一些特殊操作 |
深拷贝一份 src
中的数据到当前 Matrix 中, 如果 src
为 null
, 则相当于 reset
将当前 Matrix 重置成一个单位矩阵
将一组浮点数值的前 9 位数据拷贝到 Matrix 中,如果数组长度小于 9,调用该方法会抛出异常
从 Matrix 中拷贝数据到 values
浮点数组中
mapXXX
有很多签名版本,具体区别可以参考源码注释,这里据其中一个常用的签名方法作为说明,下同
把当前 Matrix 应用到 src
所指示的所有坐标上,然后将变换后的坐标复制到 dst
数组上
数组中每两个相邻的点表示一个坐标(x,y)
,因此数组长度一般都是偶数,否则最后一个数值不参与计算
把当前 Matrix 应用到半径为 radius
所指示的圆上,然后返回变换之后的圆的半径,由于圆可能会因为画布变换变成椭圆,所以此处测量的是平均半径
和 mapPoints
类似,把当前 Matrix 应用到 src
所指示的四个顶点上,然后将变换后的四个顶点值写入 dst
中,返回值是判断矩形经过变换后是否仍为矩形
和 mapPoinst
、mapRect
类似,这里测试计算的是向量,不再赘述
这几个方法比较好理解,就是将变换设置给当前 Matrix,得到一个变换后的 Matrix
相当于计算两个矩阵相乘 a × b
,并将结果赋值给当前矩阵,即 c.setConcat(a, b)
表示 c = a × b
这两类计算也比较好理解,就是对应了数学中的矩阵前乘和后乘
boolean setPolyToPoly (
float[] src, // 原始顶点数组 src [x, y]
int srcIndex, // 原始顶点数组开始位置
float[] dst, // 目标顶点数组 dst [x, y]
int dstIndex, // 目标顶点数组开始位置
int pointCount) // 测控顶点的数量 取值范围是: 0 到 4
复制代码
Poly
全称是 Polygon
,多边形的意思。
调用这个方法后,会计算从原始顶点和到目标顶点的变换(意味着 src
和 dst
要一一对应),把这种变换信息存储到当前 Matrix 中;将得到 的 Matrix 应用到任意图形上,可以实现把这个图形进行 Matrix 所表示的形状变换,效果如下图所示
其中 pointCount
不同数值也代表了不同能力的变换,一般我们最常用的 pointCount
都是 4,0~3 我们都可以用更加简单的方法来代替实现
pointCount | 摘要 |
---|---|
0 | 相当于 reset |
1 | 相当于 Translate |
2 | 可以进行 缩放、旋转、平移 变换 |
3 | 可以进行 缩放、旋转、平移、错切 变换 |
4 | 可以进行 缩放、旋转、平移、错切以及任何形变 |
测控点的选取
测控点可以选择任何你认为方便的位置,只要 src
与dst
一一对应即可。不过为了方便,通常会选择一些特殊的点: 图形的四个角,边线的中心点以及图形的中心点等。不过有一点需要注意,测控点选取都应当是不重复的 (src
与 dst
均是如此),如果选取了重复的点会直接导致测量失效,这也意味着,你不允许将一个方形 (四个点) 映射为三角形(四个点,但其中两个位置重叠),但可以接近于三角形。
作用范围
作用范围是设置了 Matrix 的全部区域,如果你将这个 Matrix 赋值给了 Canvas,它的作用范围就是整个画布,如果你赋值给了 Bitmap,它的作用范围就是整张图片
了解了 setPolyToPoly
之后,这个方法就比较好理解,简单来说就是将源矩形的内容填充到目标矩形中,然而在大多数的情况下,源矩形和目标矩形的长宽比是不一致的,到底该如何填充呢,这个填充的模式就由第三个参数 stf
来确定
ScaleToFit
是一个枚举类型,共包含了四种模式:
模式 | 效果 |
---|---|
CENTER | 居中,对 src 等比例缩放,并最大限度的填充变换后的矩形,将其居中放置在 dst 中 |
START | 顶部,对 src 等比例缩放,并最大限度的填充变换后的矩形,将其放置在 dst 的左上角,左上对齐 |
END | 底部,对 src 等比例缩放,并最大限度的填充变换后的矩形,将其放置在 dst 的右下角,右下对齐 |
FILL | 充满,拉伸 src 的宽和高,使其完全填充满 dst |
一图胜千言:
其实我们日常开发中或多或少已经接触了 Matrix,只是大部分我们都还不知道,比如我们使用的 ImageView
的 ScaleType
,实际上内部就是通过 Matrix 实现的
除此之外,Matrix 的应用十分广泛,这里没法一一列举,大家在学习了基本原理和常用 api 之后可以自行想象和实践,网上也有很对 Matrix 应用的有趣的例子。
这里笔者分享一下自己在实际开发中用到 Matrix 的例子 —— 相机扫描识别二维码
当我在开发这个功能的时候,遇到一个棘手的问题:当相机实时预览识别到二维码之后,需要将当前帧截取下来当成静态背景图,然后在识别到二维码的位置上显示一个小黄点,类似微信扫码的效果:
这里首先需要介绍下,相机识别二维码的大致流程
相机取景框实时取景 -> 图像帧预处理(包括裁剪、灰度化等)-> 扫码 SDK 分析预处理后的帧图像 -> 识别到二维码,返回二维码信息(包括在图中的位置等) -> 将当前图像原始帧设置为背景图 -> 在图上二维码位置出绘制小黄点
由于 SDK 分析的是裁剪灰度化过后的图像,因此返回的二维码位置信息也是基于裁剪过后的坐标系,如果我们直接把这个坐标绘制在屏幕上,必然会发现二维码位置不对
因此这里就涉及到坐标映射: 我们需要把裁剪后的坐标映射回手机屏幕坐标
先看看我们当前有哪些数据:
我们可以分析出,这里发生了变化的是两个矩形:取景框和裁剪后的图像
根据之前学到的内容,我们可以使用 setPolyToPoly
或者 setRectToRect
来描述这一变换,这里我们以 setPolyToPoly
举例,伪代码如下:
// 实例化一个原始矩阵(单位矩阵)
val matrix = Matrix()
// cropRect 是裁剪后的图像的矩形
val source = floatArrayOf(
cropRect.left.toFloat(),
cropRect.top.toFloat(),
cropRect.right.toFloat(),
cropRect.top.toFloat(),
cropRect.right.toFloat(),
cropRect.bottom.toFloat(),
cropRect.left.toFloat(),
cropRect.bottom.toFloat()
)
// previewView 是取景框视图
val destination = floatArrayOf(
0f,
0f,
previewView.width.toFloat(),
0f,
previewView.width.toFloat(),
previewView.height.toFloat(),
0f,
previewView.height.toFloat()
)
// 使用 setPolyToPoly 描述这种变换,得到一个矩阵 Matrix
// 这里默认裁剪后的图像没有旋转,否则还需要处理旋转
matrix.setPolyToPoly(source, 0, destination, 0, 4)
复制代码
得到 Matrix 之后,我们就可以使用 mapPointsToPoints 来计算,按照此矩阵变换后的二维码位置坐标了
val srcArray = it.position.toFloatArray() // 表示二维码原始坐标数据
val destArray = FloatArray(srcArray.size) // 表示计算出来的二维码坐标数据,即在相机取景框上的位置
matrix.mapPoints(destArray, srcArray)
复制代码
这样,我们就可以实现准确地在相机屏幕上标出二维码的位置了