Android绘图机制与处理技巧(四)Android图像处理之图形特效处理

Android变形矩阵——Matrix

对于图像的图形变换,Android系统也通过矩阵来进行处理,每个像素点都表达了其坐标的X、Y信息。Android的图形变换矩阵是一个3x3的矩阵,如下图所示:

 A=adgbehcfi C=XY1 R=X1Y11=AC

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

X1=a*X+b*Y+c
Y1=d*X+e*Y+f
1=g*X+h*Y+i

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

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

100010001

图像的变形处理通常包含以下四类基本变换:
- Translate——平移变换
- Rotate——旋转变换
- Scale——缩放变换
- Skew——错切变换

平移变换

平移变换的坐标值变换过程就是将每个像素点都进行平移变换,当从P(x0,y0)平移到P(x,y)时,坐标值发生了如下变换:

X=X0+△X
Y=Y0+△Y

平移矩阵如下所示:

XY1 =100010xy1X0Y01

旋转变换

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

x0=r*cosα
y0=r*sinα
x1=r*cos(α+θ)=r*cosα*cosθ−r*sinα*sinθ=x0*cosθ−y0*sinθ
y1=r*sin(α+θ)=r*sinα*cosθ+r*cosα*sinθ=y0*cosθ+x0*sinθ

矩阵形式如下图所示:

XY1 =cosθsinθ0sinθcosθ0001X0Y01

前面是以坐标原点为旋转中心的旋转变换,如果以任意点O为旋转中心来进行旋转变换,通常需要以下三个步骤:
- 将坐标原点平移到O点
- 使用前面讲的以坐标原点为中心的旋转方法进行旋转变换
- 将坐标原点还原

缩放变换

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

x=K1*x0
y=K2*y0

矩阵形式如下图所示:

XY1 =K1000K20001X0Y01

错切变换

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

错切变换通常包含两种——水平错切与垂直错切。

水平/垂直错切变换的计算公式如下:

  • 水平错切
x1=x0+K1*y0
y1=y0
  • 垂直错切
x1=x0
y1=K2*x0+y0

矩阵形式如下:

XY1 =100K110001X0Y01

XY1 =0K20010001X0Y01

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

ScaleXSkewY0SkewYScaleY0TransXTransX1

可以发现,A、B、C、D、E、F这六个矩阵元素分别对应以下变换:
- A和E控制Scale——缩放变换
- B和D控制Skew——错切变换
- C和F控制Trans——平移变换
- A、B、C、D共同控制Rotate——旋转变换

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

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

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

canvas.drawBitmap(mBitmap, matrix, null);

运行程序后,初始界面以及平移变换、缩放变换和错切变换如下所示:

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

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

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

Matrix类的set方法会重置矩阵中的值,而post和pre方法不会,这两个方法常用来实现矩阵的混合作用。不过要注意的是,矩阵运算不满足乘法的交换律,所以矩阵乘法的前乘和后乘是两种不同的运算方式。举例来说,比如需要实现以下效果:
- 先平移到(300, 100)
- 先旋转45度
- 再平移到(200, 200)

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

matrix.setRotate(45);
matrix.postTranslate(200, 200);

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

matrix.setTranslate(200, 200);
matrix.preRotate(45);

像素块分析

前面分析过了矩阵处理图像变换,现在分析下使用drawBitmapMesh()方法来进行图像特效处理。drawBitmapMesh()与操纵像素点来改变色彩的原理类似,只不过是把图像分成了一个个的小块,然后通过改变每一个图像块来修改整个图像。
该方法代码如下:

public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)

该方法关键的参数如下:

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

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

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

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

首先获取交叉点的坐标,并将坐标保存到orig数组中,其获取交叉点坐标的原理就是通过循环遍历所有的交叉线,并按比例获取其坐标,代码如下所示:

private void initView(Context context) {
    setFocusable(true);
    bitmap = BitmapFactory.decodeResource(context.getResources(),
            R.mipmap.shape_test);
    float bitmapWidth = bitmap.getWidth();
    float bitmapHeight = bitmap.getHeight();
    int index = 0;
    for (int y = 0; y <= HEIGHT; y++) {
        float fy = bitmapHeight * y / HEIGHT;
        for (int x = 0; x <= WIDTH; x++) {
            float fx = bitmapWidth * x / WIDTH;
            orig[index * 2 + 0] = verts[index * 2 + 0] = fx;
            orig[index * 2 + 1] = verts[index * 2 + 1] = fy + 100;
            index += 1;
        }
    }
    A = 50;
}

/*
接下来,在onDraw()方法中改变交叉点的纵坐标的值,为了实现旗帜飘扬的效果,使用一个正弦函数sinx来改变交叉点纵坐标的值,而横坐标不变,并将变化后的值保存到verts数组中
*/
@Override
protected void onDraw(Canvas canvas) {
    flagWave();
    //每次重绘时,通过改变相位来改变偏移量,实现动态效果
    k += 0.1F;
    canvas.drawBitmapMesh(bitmap, WIDTH, HEIGHT,
            verts, 0, null, 0, null);
    invalidate();
}

/*
 通过正弦函数,按当前点所在的横坐标的位置来确定纵坐标的偏移量,其中A代表正弦函数中的振幅大小
 */
private void flagWave() {
   for (int j = 0; j <= HEIGHT; j++) {
       for (int i = 0; i <= WIDTH; i++) {
           verts[(j * (WIDTH + 1) + i) * 2 + 0] += 0;
          //在获取纵坐标的偏移量时,可以利用正选函数的周期性来让图像动起来
           float offsetY = (float) Math.sin((float) i / WIDTH * 2 * Math.PI + Math.PI * k);
           verts[(j * (WIDTH + 1) + i) * 2 + 1] =
                   orig[(j * WIDTH + i) * 2 + 1] + offsetY * A;
       }
   }
}

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

这里写图片描述

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

代码下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值