Android图片处理一:Matrix与手势

1,113 阅读4分钟

1 矩阵基础

A=[k1k2k3k4k5k6k7k8k9]B=[x0x1y0y111]C=AB=[k1x0+k2y0+k3k1x1+k2y1+k3k4x0+k5y0+k6k4x1+k5y1+k6k7x0+k8y0+k9k7x1+k8y1+k9]\begin{aligned} A&=\begin{bmatrix} k_1 & k_2 & k_3\\ k_4 & k_5 & k_6\\ k_7 & k_8 & k_9 \end{bmatrix}\\ B&=\begin{bmatrix} x_0 & x_1\\ y_0 & y_1\\ 1 & 1 \end{bmatrix}\\ C=AB&=\begin{bmatrix} k_1x_0 + k_2y_0 + k_3 & k_1x_1 + k_2y_1 + k_3\\ k_4x_0 + k_5y_0 + k6 & k_4x_1 + k_5y_1 + k6\\ k_7x_0 + k_8y_0 + k9 & k_7x_1 + k_8y_1 + k9 \end{bmatrix} \end{aligned}

用 A(3行3列) 的行去乘以 B(3行2列) 的所有列,得出一个3行2列的矩阵。 1、当矩阵A的列数(column)等于矩阵B的行数(row)时,A与B可以相乘。 2、矩阵C的行数等于矩阵A的行数,C的列数等于B的列数。 3、乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。 1、2点可总结为:A(m×p)×B(p×n)=C(m×n)

1.1 左乘和右乘

以下解释是为了方便记忆,不保证科学性。

左乘以:C = AB 称为A左乘以B,“以” 表示用,A左乘以B表示用B去乘A,A在左边,跟普通乘法顺序一致,从左到右。 左乘:与左乘以相反,C = AB 称为B左乘A(B左边乘A) 右乘以:C = BA 称为A右乘以B,用B去乘A,A在右边 右乘:与右乘以相反,C = BA 称为B右乘A(B右边乘A) 所以,C = AB可以称为:A左乘以B,B左乘A,B右乘以A,A右乘B。得出结论:左乘以 == 右乘,右乘以 == 左乘。

2 Matrix基础

Matrix 是 Android 图形库里的一个坐标转换类,它里面保存了一个 3 × 3 的矩阵,矩阵里各元素的对应关系如下所示:

[缩放_X错切_X平移_X错切_Y缩放_Y平移_Y透视_0透视_1透视_2]\begin{bmatrix} 缩放\_X & 错切\_X & 平移\_X\\ 错切\_Y & 缩放\_Y & 平移\_Y\\ 透视\_0 & 透视\_1 & 透视\_2 \end{bmatrix}

具体为何这样对应请参考安卓自定义View进阶-Matrix原理。这里引用几个重要的变换。

2.1 平移

x=x0+Δxy=y0+Δy\begin{aligned} x = x_0 + \Delta x \\ y = y_0 + \Delta y \end{aligned}

用矩阵表示:

[xy1]=[10Δx01Δy001][x0y01]\begin{aligned} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & \Delta x\\ 0 & 1 & \Delta y\\ 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} x_0 \\ y_0 \\ 1 \end{bmatrix} \end{aligned}

2.2 缩放

x=k1x0y=k2y0\begin{aligned} x = k_1x_0 \\ y = k_2y_0 \end{aligned}

用矩阵表示:

[xy1]=[k1000K20001][x0y01]\begin{aligned} \begin{bmatrix} x \\ y \\1 \end{bmatrix} = \begin{bmatrix} k_1 & 0 & 0 \\ 0 & K_2 & 0 \\ 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} x_0 \\ y_0 \\ 1 \end{bmatrix} \end{aligned}

2.3 旋转

x0=rcosαy0=rsinαx=rcos(α+θ)=rcosαcosθrsinαsinθ=x0cosθy0sinθy=rsin(α+θ)=rsinαcosθ+rcosαsinθ=y0cosθ+x0sinθ\begin{aligned} x_0 &= r\cos\alpha \\ y_0 &= r\sin\alpha \\ x = r\cos(\alpha + \theta) &= r\cos\alpha\cos\theta - r\sin\alpha\sin\theta = x_0\cos\theta - y_0\sin\theta \\ y = r\sin(\alpha + \theta) &= r\sin\alpha\cos\theta + r\cos\alpha\sin\theta = y_0\cos\theta + x_0\sin\theta \end{aligned}

用矩阵表示:

[xy1]=[cosθsinθ0sinθcosθ0001][x0y01]\begin{aligned} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} = \begin{bmatrix} \cos\theta & -\sin\theta & 0\\ \sin\theta & \cos\theta & 0\\ 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} x_0 \\ y_0 \\ 1 \end{bmatrix} \end{aligned}

旋转是一个矩阵元素组合实现。 Matrix 里有很多 prepost 方法。pre 表示前乘(原始Matrix放前面),A pre B 输出 AB,按照我们刚才对矩阵左乘右乘的解释,前乘相当于矩阵的A左乘以B、A右乘B。post 表示后乘(原始Matrix放后面),A post B 输出 BA,后乘相当于矩阵的A右乘以B、A左乘B。 Matrix 里常用的变换有 平移(Translate)、旋转(Rotate)、缩放(Scale)。

2.4 Matrix常用方法

方法类别相关API摘要
基本方法equals hashCode toString toShortString比较、 获取哈希值、 转换为字符串
数值操作set reset setValues getValues设置、 重置、 设置数值、 获取数值
数值计算mapPoints mapRadius mapRect mapVectors计算变换后的数值
设置(set)setConcat setRotate setScale setSkew setTranslate设置变换
前乘(pre)preConcat preRotate preScale preSkew preTranslate前乘变换
后乘(post)postConcat postRotate postScale postSkew postTranslate后乘变换
特殊方法setPolyToPoly setRectToRect rectStaysRect setSinCos一些特殊操作
矩阵相关invert isAffine isIdentity求逆矩阵、 是否为仿射矩阵、 是否为单位矩阵 …

具体各个方法使用参考:安卓自定义View进阶-Matrix详解,各方法在 Matrix 类里的注解都非常详细, 很多方法基本一看注解就能懂。 这里放几个典型的。

2.4.1 setRectToRect

public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {}

这个方法是 srcdst 的变换,可以理解为把 src 放进 dst 里去的的变换,第三个参数 stf 的缩放填充模式(充满FILL、保持比例左上START、居中CENTER、右下END)。

RectF src= new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight() );
RectF dst = new RectF(0, 0, mViewWidth, mViewHeight );
mRectMatrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
canvas.drawBitmap(mBitmap, mRectMatrix, new Paint());

代码很容易理解,把 bitmap 通过 matrix 变换绘制到 view 上。

2.4.2 mapRect

mapRect有两个方法,具体含义写在注释中。

/**
* 对 rect 进行 matrix 变换,并将结果存在 rect 中
*/
public boolean mapRect(RectF rect) {}
/**
* 对 src 进行 matrix 变换,并将结果存在 dst中
*/
public boolean mapRect(RectF dst, RectF src) {}

2.4.3 setPolyToPoly

这个比较复杂,建议详细看下安卓自定义View进阶-Matrix详解里的 setPolyToPoly 详细讲解。这里摘录下重点: 先看代码和效果:

float[] src = {0, 0,                                    // 左上
        mBitmap.getWidth(), 0,                          // 右上
        mBitmap.getWidth(), mBitmap.getHeight(),        // 右下
        0, mBitmap.getHeight()};                        // 左下
float[] dst = {0, 0,                                    // 左上
        mBitmap.getWidth(), 400,                        // 右上
        mBitmap.getWidth(), mBitmap.getHeight() - 200,  // 右下
        0, mBitmap.getHeight()};                        // 左下
// 核心要点
mPolyMatrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); // src.length >> 1 为位移运算 相当于处以2

效果图里我们看到了图片右边产生了上下都往里缩的形变,再看代码,src 数组存放了原图的四个边界点,在 dst 数组里存放了变换后的四个边界点,src.length >> 1 即为边界点个数,很容易看出 setPolyToPoly 是能精确到数组里各个点的变换。再看方法定义就好理解很多了:

boolean setPolyToPoly (
        float[] src,    // 原始数组 src [x,y],存储内容为一组点
        int srcIndex,   // 原始数组开始位置
        float[] dst,    // 目标数组 dst [x,y],存储内容为一组点
        int dstIndex,   // 目标数组开始位置
        int pointCount) // 测控点的数量 取值范围是: 0到4

测控点数量对功能的影响:

pointCount摘要
0相当于reset
1相当于translate
2可以进行 缩放、旋转、平移 变换
3可以进行 缩放、旋转、平移、错切 变换
4可以进行 缩放、旋转、平移、错切以及任何形变