在上一篇文章中,我们实现了通过不同的渲染器设置来实现图像的图形变换,没有读过的朋友,可以点击下面的链接:
http://www.cnblogs.com/fuly550871915/p/4886651.html
在这一篇文章,迎来了图像图形变换系列的最终章。所以要讲一个终极大招,利用像素块调整从来实现不同的图像效果。这个将更加细致的实现图像变换。
一、基础知识
配合下面两张图,来解释一下什么叫通过像素快来调整图像变换。如下:
左侧是原图,右侧是被画了小格子的图。我们想象一下,一张图片被分成了许多像素块,正如右侧图片一样,每一个小格子就是一个像素块。而如果获取到这些小格子的交点坐标,并随着改变这些坐标的值,就可以改变对应像素块的图像效果,比如某个像素块被拉伸了,某个被旋切了,那么整张图片就可以呈现更加丰富的图像效果了。
那么android中是怎么将这个想法变为现实的呢?android提供了一个drawBitmapMesh方法,只要我将调整好的这些格子的交点坐标传递进去,就会按照这个调整变换出相应的图形效果。具体方法如下:
/***
* 第一个参数为绘制的图片
* 第二个参数为绘制的mesh横向格子数目
* 第三个参数为绘制的mesh纵向格子数目
* 第四个参数为绘制的mesh的格子交点坐标,为一个相应的数组
* 第五个参数为绘制的mesh的格子交点坐标的偏移量,一般设置为0
* 第六个参数为颜色,一般设置为null
* 第七个参数为颜色偏移量,设置为0
* 最后一个为画笔,设置为null即可
*/
canvas.drawBitmapMesh(bmp, WIDTH, HEIGHT, verts, 0, null, 0 , null);
解释一下,其中bmp就是原来的图像,而我们分隔的纵向格子数目比如说为200,纵向格子数目比如说为300.那么我就在这个图像上就有200*300个小格子。而WIDTH是我们想绘制出的横向格子数目,必须小于等于200,同理HEIGHT就是我们想绘制出的纵向格子数目,必须小于等于300.而verts是一个数值数组,存储的就是这200*300个小格子的交点的坐标值。其他的注释里解释的很清楚了。
或许你还对这个drawBitmapMesh方法很陌生,没关系,看下面的实战代码,一起做,就会加深理解了。
二、实战
废话不多说,自定义view。在这个view里面,我们要实现这个drawBitmapMesh的逻辑。而其中的难点就是怎么遍历取出这个小格子的交点坐标。希望你能好好研究这些代码,必要时可以直接拿来使用。核心的技巧就是两个for循环方法。还有,为了存储坐标,我们将数组的偶数位置存储为x,奇数位置存储为y。注意是怎么存储和再取出来的。注释很详细。如下:
1 package com.fuly.image;
2
3 import android.content.Context;
4 import android.graphics.Bitmap;
5 import android.graphics.BitmapFactory;
6 import android.graphics.BitmapShader;
7 import android.graphics.Canvas;
8 import android.graphics.Paint;
9 import android.graphics.Shader;
10 import android.util.AttributeSet;
11 import android.view.View;
12 /**
13 * 利用MeshView改变图形变换
14 * @author fuly1314
15 *
16 */
17 public class MeshView extends View{
18
19 private Bitmap bmp;//原始图片
20 private int WIDTH = 200;//表示图片横向有200个格子
21 private int HEIGHT = 200;//纵向有200个格子
22 private int COUNT = (WIDTH+1)*(HEIGHT+1);//表示这样分割图片上共有多少个坐标点
23 24 /*
25 * 用来存储图像变换后的坐标点的坐标
26 * 偶数位置存储x坐标
27 * 奇数位置存储y坐标
28 */
29 private float[] verts = new float[COUNT*2];
30 /*
31 * 用来存储图像原始的坐标点的坐标
32 * 偶数位置存储x坐标
33 * 奇数数位置存储y坐标
34 */
35 private float[] orign = new float[COUNT*2];
36
37
38 public MeshView(Context context) {
39 super(context);
40 initView();
41 }
42 public MeshView(Context context, AttributeSet attrs) {
43 super(context, attrs);
44 initView();
45 }
46 public MeshView(Context context, AttributeSet attrs, int defStyleAttr) {
47 super(context, attrs, defStyleAttr);
48 initView();
49 }
50
51
52 public void initView(){
53 bmp = BitmapFactory.decodeResource(getResources(), R.drawable.test4);
54 int width = bmp.getWidth();
55 int height = bmp.getHeight();
56 int index = 0;//标记位,用来计数
57 /*
58 * 这个循环用来存储坐标,必须牢牢掌握,是核心
59 */
60 for(int i=0;i<(HEIGHT+1);i++){//遍历横向坐标点
61 float y = height*i/HEIGHT;//坐标为(i,j)的点的纵坐标
62
63 for(int j=0;j<(WIDTH+1);j++){//遍历纵向坐标点
64 float x = width*j/WIDTH;//坐标为(i,j)的点的横坐标
65
66 //下面将坐标点存储到相应的数组中
67 orign[2*index+0]=verts[2*index+0] = x;
68 orign[2*index+1]=verts[2*index+1] = y;
69
70 index++;
71
72 }
73 }
74 }
75
76 protected void onDraw(Canvas canvas) {
77 super.onDraw(canvas);
78 //在这个循环层中,我们可以改变verts的值,来实现不同的效果
79 /*
80 * 这个循环用来去除坐标,必须牢牢掌握,是核心
81 */
82 for (int i = 0; i < HEIGHT + 1; i++) {
83 for (int j = 0; j < WIDTH + 1; j++) {
84 //取出相应的坐标x,不改变值就加0吧干脆
85 verts[(i * (WIDTH + 1) + j) * 2 + 0] += 0;
86 //K是用来控制周期的,这样子再加上invalidate()方法可以实现动画效果
87 float offsetY = (float) Math.sin((float) j / WIDTH * 2 * Math.PI );
88 //给Y坐标在原来的位置上加上一个正弦偏移量
89 verts[(i * (WIDTH + 1) + j) * 2 + 1] =
90 orign[(i * (WIDTH + 1) + j) * 2 + 1] + offsetY * 50;
91 }
92 }
93 94
95 /***
96 * 第一个参数为绘制的图片
97 * 第二个参数为绘制的mesh横向格子数目
98 * 第三个参数为绘制的mesh纵向格子数目
99 * 第四个参数为绘制的mesh的格子交点坐标,为一个相应的数组
100 * 第五个参数为绘制的mesh的格子交点坐标的偏移量,一般设置为0
101 * 第六个参数为颜色,一般设置为null
102 * 第七个参数为颜色偏移量,设置为0
103 * 最后一个为画笔,设置为null即可
104 */
105 canvas.drawBitmapMesh(bmp, WIDTH, HEIGHT, verts, 0, null, 0 , null);
106
107
108
109
110 }
111
112 }
好了,最难的部分完成了,下面快速完成。新建mesh.xml将这个view装进去。如下:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent"
5 android:orientation="vertical"
6 android:gravity="center">
7
8 <com.fuly.image.MeshView
9 android:layout_marginLeft="20dp"
10 android:layout_marginTop="20dp"
11 android:layout_width="wrap_content"
12 android:layout_height="wrap_content"/>
13
14 </LinearLayout>
然后新建活动显示这个view,注意不要忘记给这个互动注册。如下:
1 package com.fuly.image;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5
6 public class MeshActivity extends Activity{
7
8
9 protected void onCreate(Bundle savedInstanceState) {
10
11 super.onCreate(savedInstanceState);
12 setContentView(R.layout.mesh);
13
14 }
15
16 }
最后就是最后一个按钮的点击事件,修改MainActivity,如下:
1 package com.fuly.image;
2
3 import android.os.Bundle;
4 import android.view.View;
5 import android.app.Activity;
6 import android.content.Intent;
7
8
9 public class MainActivity extends Activity {
10
11
12 protected void onCreate(Bundle savedInstanceState) {
13 super.onCreate(savedInstanceState);
14 setContentView(R.layout.activity_main);
15 }
16
17 //下面是按钮事件
18 public void btnMatrix(View v){
19 Intent intent = new Intent(this,MatrixActivity.class);
20 startActivity(intent);
21 }
22 public void btnXFermode(View v){
23 Intent intent = new Intent(this,XFermodeActivity.class);
24 startActivity(intent);
25 }
26 public void btnShader(View v){
27 Intent intent = new Intent(this,ShaderActivity.class);
28 startActivity(intent);
29 }
30 public void btnLShader(View v){
31 Intent intent = new Intent(this,LinearShaderActivity.class);
32 startActivity(intent);
33 }
34 public void btnMesh(View v){
35 Intent intent = new Intent(this,MeshActivity.class);
36 startActivity(intent);
37 }
38
39
40 }
然后运行程序,实现效果如下:
我们发现实现了一个正弦的效果。
其实更近一步的,我们还可以实现动画效果。不信,哈哈,其实很简单,修改自定义的MeshView,红色部分为修改的。你一看就明白了。如下:
1 package com.fuly.image;
2
3 import android.content.Context;
4 import android.graphics.Bitmap;
5 import android.graphics.BitmapFactory;
6 import android.graphics.BitmapShader;
7 import android.graphics.Canvas;
8 import android.graphics.Paint;
9 import android.graphics.Shader;
10 import android.util.AttributeSet;
11 import android.view.View;
12 /**
13 * 利用MeshView改变图形变换
14 * @author fuly1314
15 *
16 */
17 public class MeshView extends View{
18
19 private Bitmap bmp;//原始图片
20 private int WIDTH = 200;//表示图片横向有200个格子
21 private int HEIGHT = 200;//纵向有200个格子
22 private int COUNT = (WIDTH+1)*(HEIGHT+1);//表示这样分割图片上共有多少个坐标点
23 private float K;
24 /*
25 * 用来存储图像变换后的坐标点的坐标
26 * 偶数位置存储x坐标
27 * 奇数位置存储y坐标
28 */
29 private float[] verts = new float[COUNT*2];
30 /*
31 * 用来存储图像原始的坐标点的坐标
32 * 偶数位置存储x坐标
33 * 奇数数位置存储y坐标
34 */
35 private float[] orign = new float[COUNT*2];
36
37
38 public MeshView(Context context) {
39 super(context);
40 initView();
41 }
42 public MeshView(Context context, AttributeSet attrs) {
43 super(context, attrs);
44 initView();
45 }
46 public MeshView(Context context, AttributeSet attrs, int defStyleAttr) {
47 super(context, attrs, defStyleAttr);
48 initView();
49 }
50
51
52 public void initView(){
53 bmp = BitmapFactory.decodeResource(getResources(), R.drawable.test4);
54 int width = bmp.getWidth();
55 int height = bmp.getHeight();
56 int index = 0;//标记位,用来计数
57 /*
58 * 这个循环用来存储坐标,必须牢牢掌握,是核心
59 */
60 for(int i=0;i<(HEIGHT+1);i++){//遍历横向坐标点
61 float y = height*i/HEIGHT;//坐标为(i,j)的点的纵坐标
62
63 for(int j=0;j<(WIDTH+1);j++){//遍历纵向坐标点
64 float x = width*j/WIDTH;//坐标为(i,j)的点的横坐标
65
66 //下面将坐标点存储到相应的数组中
67 orign[2*index+0]=verts[2*index+0] = x;
68 orign[2*index+1]=verts[2*index+1] = y;
69
70 index++;
71
72 }
73 }
74 }
75
76 protected void onDraw(Canvas canvas) {
77 super.onDraw(canvas);
78 //在这个循环层中,我们可以改变verts的值,来实现不同的效果
79 /*
80 * 这个循环用来去除坐标,必须牢牢掌握,是核心
81 */
82 for (int i = 0; i < HEIGHT + 1; i++) {
83 for (int j = 0; j < WIDTH + 1; j++) {
84 //取出相应的坐标x,不改变值就加0吧干脆
85 verts[(i * (WIDTH + 1) + j) * 2 + 0] += 0;
86 //K是用来控制周期的,这样子再加上invalidate()方法可以实现动画效果
87 float offsetY = (float) Math.sin((float) j / WIDTH * 2 * Math.PI + K * 2 * Math.PI);
88 //给Y坐标在原来的位置上加上一个正弦偏移量
89 verts[(i * (WIDTH + 1) + j) * 2 + 1] =
90 orign[(i * (WIDTH + 1) + j) * 2 + 1] + offsetY * 50;
91 }
92 }
93 K += 0.1F;
94
95 /***
96 * 第一个参数为绘制的图片
97 * 第二个参数为绘制的mesh横向格子数目
98 * 第三个参数为绘制的mesh纵向格子数目
99 * 第四个参数为绘制的mesh的格子交点坐标,为一个相应的数组
100 * 第五个参数为绘制的mesh的格子交点坐标的偏移量,一般设置为0
101 * 第六个参数为颜色,一般设置为null
102 * 第七个参数为颜色偏移量,设置为0
103 * 最后一个为画笔,设置为null即可
104 */
105 canvas.drawBitmapMesh(bmp, WIDTH-100, HEIGHT-100, verts, 0, null, 0 , null);
106
107 invalidate();//刷新,这样就可以造成onDraw方法的循环
108
109
110 }
111
112 }
是不是思路很简单,只要每次画完后让onDraw自己调用刷新方法即可。这样就可以造成无限循环了。这个技巧要记得。运行程序,效果如下:
怎么样,是不是很炫啊!只要你足够大神,drawBitmapMesh方法将是你使用好多花样效果的利器。好了,至此,本篇结束。相信你对像素块调整图片的运用更加熟悉了,同时图形变换的基础系列也到此结束了,相信你对图形变换也有了更加深刻的认知。共同学习,一起进步,希望在android的道路上越走越远。