在上一篇文章中,我們實作了通過不同的渲染器設定來實作圖像的圖形變換,沒有讀過的朋友,可以點選下面的連結:
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的道路上越走越遠。