6.6 Android图像处理之图形特效处理" class="reference-link">6.6 Android图像处理之图形特效处理

前面我们了解了关于图像色彩处理的相关技巧,下面继续来探讨图形图像方面的处理技巧。

6.6.1 Android变形矩阵——Matrix" class="reference-link">6.6.1 Android变形矩阵——Matrix

对于图像的色彩处理,Android系统提供了ColorMatrix颜色矩阵来帮助我们进行图像处理。而对于图像的图形变换,Android系统也是通过矩阵来进行处理的,每个像素点都表达了其坐标的X、Y信息。Android的图形变换矩阵是一个3×3的矩阵,如图6.45所示。

6.6 Android图像处理之图形特效处理 - 图1 图6.45 图形变换矩阵

当使用变换矩阵去处理每一个像素点的时候,与颜色矩阵的矩阵乘法一样,计算公式如下所示。

6.6 Android图像处理之图形特效处理 - 图2

通常情况下,会让g=h=0,i=1,这样使1=g×X+h×Y+i恒成立。因此,只需要着重关注上面几个参数就可以了。

与色彩变换矩阵的初始矩阵一样,图形变换矩阵也有一个初始矩阵。很明显,就是对角线元素a、e、i为1,其他元素为0的矩阵,如图6.46所示。

6.6 Android图像处理之图形特效处理 - 图3 图6.46 图形变换初始矩阵

图像的变形处理通常包含以下四类基本变换。

  • Translate——平移变换
  • Rotate——旋转变换
  • Scale——缩放变换
  • Skew——错切变换

6.6.1.1 平移变换

平移变换的坐标值变换过程如图6.47所示,即将每个像素点都进行平移变换。

6.6 Android图像处理之图形特效处理 - 图4 图6.47 平移变换

当从p(x0,x0)平移到p(x,y)时,坐标值发生了如下所示的变换。

6.6 Android图像处理之图形特效处理 - 图5

如果写成矩阵形式就如图6.48所示。

6.6 Android图像处理之图形特效处理 - 图6 图6.48 平移变换矩阵

通过计算可以发现如下等式。

6.6 Android图像处理之图形特效处理 - 图7

这也就是前面所说的实现平移过程的平移公式,图6.48所示矩阵也就是平移变换矩阵。

6.6.1.2 旋转变换

选择变换即指一个点围绕一个旋转中心旋转到一个新的点,如图6.49所示。

6.6 Android图像处理之图形特效处理 - 图8 图6.49 旋转变换

当从P(x0,y0)点,以坐标原点为旋转中心旋转到P(x,y)点时,可以将点的坐标都表达成OP与X轴正方向夹角的函数表达式,如下所示。

x0 = r cosα

y0 = r sinα

x = r cos(α + θ) = r cosα cosθ – r sinα sinθ = x0cosθ - y0sinθ

y = r sin(α + θ) = r sinα cosθ + r cosα sinθ = y0cosθ + x0sinθ

如果写成矩阵形式就如图6.50所示。

6.6 Android图像处理之图形特效处理 - 图9 图6.50 旋转变换矩阵

通过计算,可以还原以上等式,图6.50所示矩阵也就是选择变换矩阵。

前面是以坐标原点为旋转中心的旋转变换,如果以任意点O为旋转中心来进行旋转变换,通常需要以下三个步骤。

  • 将坐标原点平移到O点。
  • 使用前面讲的以坐标原点为中心的旋转方法进行旋转变换。
  • 将坐标原点还原。

通过以上三个步骤,实现了以任意点为旋转中心的旋转变换。

6.6.1.3 缩放变换

一个像素点是不存在缩放的概念的,但是由于图像是由很多个像素点组成的,如果将每个点的坐标都进行相同比例的缩放,最终就会形成让整个图像缩放的效果,缩放效果的计算公式如下。

6.6 Android图像处理之图形特效处理 - 图10

如果写成矩阵形式,就如图6.51所示。

6.6 Android图像处理之图形特效处理 - 图11 图6.51 缩放变换矩阵

通过计算,就可以还原到以上等式,因此图6.51所示矩阵即为缩放变换矩阵。

6.6.1.4 错切变换

错切变换(skew)在数学上又称为Shear mapping(可译为“剪切变换”)或者Transvection (缩并),它是一种比较特殊的线性变换。错切变换的效果就是让所有点的X坐标(或者y坐标)保持不变,而对应的y坐标(或者X坐标)则按比例发生平移,且平移的大小和该点到X轴(或y轴)的垂直距离成正比。

错切变换通常包含两种——水平错切与垂直错切,分别如图6.52、图6.53所示。

6.6 Android图像处理之图形特效处理 - 图12 6.6 Android图像处理之图形特效处理 - 图13
图6.52 水平错切 图6.53 垂直错切

错切变换的计算公式如下所示。

6.6 Android图像处理之图形特效处理 - 图14

如果写成矩阵形式,就如图6.54所示。

6.6 Android图像处理之图形特效处理 - 图15 图6.54 错切变换矩阵

通过计算,就可以还原到以上等式。因此,图6.54所示矩阵即为错切变换矩阵。

由上面的分析可以发现,这个3×3的矩阵与色彩变换矩阵一样,每个位置的元素所表示的功能是有规律的,总结的规律如图6.55所示。

6.6 Android图像处理之图形特效处理 - 图16 图6.55 矩阵变换规律

可以发现,A、B、C、D、E、F这六个矩阵元素分别对应以下变换。

  • A和E控制Scale——缩放变换
  • B和D控制Skew——错切变换
  • C和F控制Trans——平移变换
  • A、B、C、D共同控制Rotate——旋转变换

在了解了矩阵变换规律后,通过类似色彩矩阵中模拟矩阵的例子来模拟一下变形矩阵。整个代码与模拟颜色矩阵所使用的代码基本一致。在图形变换矩阵中,同样是通过一个一维数组来模拟矩阵,并通过setValues()方法将一个一维数组转换为图形变换矩阵,代码如下所示。

  1. private float[] mImageMatrix = new float[9];
  2. Matrix matrix = new Matrix();
  3. matrix.setValues(mImageMatrix);

当获得了变换矩阵后,就可以通过以下代码将一个图像以这个变换矩阵的形式绘制出来。

  1. canvas.drawBitmap(mBitmap, matrix, null);

运行程序后,初始界面如图6.56所示。

6.6 Android图像处理之图形特效处理 - 图17 图6.56 模拟变形矩阵

下面的3×3矩阵就是用来模拟图形变换的矩阵,分别改变A、B、C、D、E、F的值,来验证一下前面分析的结论,平移变换如图6.57所示,缩放变换如图6.58所示,错切变换如图6.59所示。

6.6 Android图像处理之图形特效处理 - 图18 6.6 Android图像处理之图形特效处理 - 图19 6.6 Android图像处理之图形特效处理 - 图20
图6.57 平移变换 图6.58 缩放变换 图6.59 错切变换

当然,与色彩矩阵一样,Android系统同样提供了一些API来简化矩阵的运算。我们不必每次都去设置矩阵的每一个元素值。Android中使用Matri类来封装矩阵,并提供了以下几个操作方法来实现上面的四种变换方式。

  • matri X .setRotate()——旋转变换
  • matri X .setTranslate()——平移变换
  • matri X .setScale()——缩放变换
  • matri X .setSkew()——错切变换
  • pre()和post()——提供矩阵的前乘和后乘运算

Matri类的set方法会重置矩阵中的所有值,而post和pre方法不会,这两个方法常用来实现矩阵的混合作用。不过要注意的是,矩阵运算不满足交换率,所以矩阵乘法的前乘和后乘是两种不同的运算方式。举个例子来说,比如需要实现以下效果。

  • 先平移到(300, 100)
  • 再旋转45度
  • 最后平移到(200, 200)

如果使用后乘运算,表示当前矩阵乘上参数代表的矩阵,代码如下所示。

  1. matri X .setRotate(45);
  2. matri X .postTranslate(200, 200);

如果使用前乘运算,表示参数代表的矩阵乘上当前矩阵,代码如下所示。

  1. matri X .setTranslate(200, 200);
  2. matri X .preRotate(45);

6.6.2 像素块分析" class="reference-link">6.6.2 像素块分析

与我们当初了解色彩的处理效果时类似,使用了颜色矩阵与像素点处理两种方式来进行图像色彩的处理,这里在进行图像的特效处理的时候也有两种方式,即前面讲的使用矩阵来进行图像变换和马上就要讲的使用drawBitmapMesh()方法来进行处理。drawBitmapMesh()与操纵像素点来改变色彩的原理类似,只不过是把图像分成了一个个的小块,然后通过改变每一个图像块来修改整个图像。

该方法代码如下所示。

  1. drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts,
  2. int vertOffset, int[] colors, int colorOffset, Paint paint)

这个方法的参数非常多,关键的参数如下。

  • bitmap:将要扭曲的图像。
  • meshWidth:需要的横向网格数目。
  • meshHeight:需要的纵向网格数目。
  • verts:网格交叉点坐标数组。
  • vertOffset: verts数组中开始跳过的(x, y)坐标对的数目。

其中最重要的参数是一个数组——verts。

前面说了,要使用drawBitmapMesh()方法先要将图片分割为若干个图像块。所以,在图像上横纵各画N-1条线,将图像分成N块,而这横纵各N条线就交织成了N×N个点,而每个点的坐标则以x1,y1,x2,y2,…,xn,yn的形式保存在verts数组中。也就是说verts数组的每两位用来保存一个交织点,第一个是横坐标,第二个是纵坐标。而整个drawBitmapMesh()方法改变图像的方式,就是靠这些坐标值的改变来重新定位每一个图像块,从而达到图像效果处理的功能。这里借用API Demo中的一张图来形象地解释一下这个过程,如图6.60所示。

6.6 Android图像处理之图形特效处理 - 图21 图6.60 将图像分成像素块

当改变某些交叉点的坐标时,就会让整个像素块随之发生扭曲,如图6.61所示。

6.6 Android图像处理之图形特效处理 - 图22 图6.61 扭曲像素块

由此可见,drawBitmapMesh()方法的功能非常强大,基本上可以实现所有的图像特效,但使用起来也非常复杂,其关键就是在于计算、确定新的交叉点的坐标。下面来看看该如何使用强大的drawBitmapMesh()方法来实现一个旗帜飞扬的效果。

要想达到旗帜飞扬的效果,只需要让图片中每个交织点的横坐标较之前坐标不发生变化,而纵坐标较之前坐标呈现一个三角函数的周期性变化即可。

首先来获取交叉点的坐标,并将坐标保存到orig数组中,这里的代码基本也可以作为目标代码使用,其获取交叉点坐标的原理就是通过循环遍历所有的交叉线,并按比例获取其坐标,代码如下所示。

  1. bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable. test);
  2.  
  3. float bitmapWidth = bitmap.getWidth();
  4. float bitmapHeight = bitmap.getHeight();
  5. int index = 0;
  6. for (int y = 0; y <= HEIGHT; y++) {
  7. float fy = bitmapHeight * y / HEIGHT;
  8. for (int x = 0; x <= WIDTH; x++) {
  9. float fx = bitmapWidth * x / WIDTH;
  10. orig[index * 2 + 0] = verts[index * 2 + 0] = fx;
  11. //这里人为将坐标+100是为了让图像下移,避免扭曲后被屏幕遮挡
  12. orig[index * 2 + 1] = verts[index * 2 + 1] = fy + 100;
  13. index += 1;
  14. }
  15. }

接下来,在onDraw()方法中改变交叉点的纵坐标的值,为了实现旗帜飘扬的效果,使用一个正弦函数Sin x来改变交叉点纵坐标的值,而横坐标的值不变,并将变化后的值保存到verts数组中,代码如下所示。

  1. private void flagWave() {
  2. for (int j = 0; j <= HEIGHT; j++) {
  3. for (int i = 0; i <= WIDTH; i++) {
  4. verts[(j * (WIDTH + 1) + i) * 2 + 0] += 0;
  5. float offsetY = (float) Math.sin((float) i / WIDTH * 2 * Math.PI );
  6. verts[(j * (WIDTH + 1) + i) * 2 + 1] =
  7. orig[(j * WIDTH + i) * 2 + 1] + offsetY * A;
  8. }
  9. }
  10. }

在以上代码中,通过正弦函数,按当前点所在横坐标的位置来确定纵坐标的偏移量。其中A表示正弦函数中的振幅大小,最后在onDraw()方法中使用以下代码即可将处理后的图像绘制出来。

  1. canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT,
  2. verts, 0, null, 0, null);

运行程序后,就可以得到一个静态的扭曲图像,效果如图6.62所示。

6.6 Android图像处理之图形特效处理 - 图23 图6.62 静态的旗帜效果图像

为了能够让图像动起来,可以充分利用正弦函数的周期性来实现,在获取纵坐标的偏移量时,给函数增加一个周期即可,代码如下所示。

  1. float offsetY = (float) Math.sin((float) i / WIDTH * 2 * Math.PI + Math.PI * k);

而在重绘的时候,将k的值增加,代码如下所示。

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. flagWave();
  4. k += 0.1F;
  5. canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT,
  6. verts, 0, null, 0, null);
  7. invalidate();
  8. }

这样,每次在重绘的时候,通过改变相位来改变偏移量,从而造成一个动态的效果,就好像旗帜在风中飘扬一样。

使用drawBitmapMesh()方法可以创建很多复杂的图像特效,但是对它的使用也相对比较复杂,需要开发者对图像处理有很深厚的功底。同时,对算法的要求也比较高,需要计算各种特效下不同的坐标点变化规律,从而设计出不同的特效。