天天看點

終極大招之通過像素塊調整實作圖像變換

      在上一篇文章中,我們實作了通過不同的渲染器設定來實作圖像的圖形變換,沒有讀過的朋友,可以點選下面的連結:

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的道路上越走越遠。