在上一篇文章中,我们实现了通过不同的渲染器设置来实现图像的图形变换,没有读过的朋友,可以点击下面的链接:
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的道路上越走越远。