浅谈android中图片处理之图形变换特效Matrix(四)

今天,我们就来谈下android中图片的变形的特效,在上讲博客中我们谈到android中图片中的色彩特效来实现的。改变它的颜色主要通过ColorMatrix类来实现。

现在今天所讲的图片变形的特效主要就是通过Matrix类来实现,我们通过上篇博客知道,改变色彩特效,主要是通过ColorMatrxi矩阵的系数,以及每个像素点上所对应的颜色偏移量。而今天的图形变换与那个也是非常的类似。它是一个3*3矩阵,而颜色矩阵则是一个4*5的矩阵。在这个3*3矩阵中则表述出了每个像素点的XY坐标信息。然后通过修改这个矩阵,就可达到修改图片中的每个像素点的XY坐标,即改变每个像素点的位置信息,通过对特定的矩阵元素值的修改就可以达到实现图片的中的

图形变换特效如:平移变换特效,旋转变换特效,缩放变换特效,错切变换特效。那么接下来我们就通过从原理的角度来一一分析下每个特效对应的矩阵是怎么样的。

默认的图形变换的初始矩阵是:

1  0  0

0  1  0

0  0  1

  第一、平移变换特效:

我们很容易知道这个,在一个平面中,将一个像素点从位置A(x0,y0)移到另一个位置B(x,y)

很容易得到如下的公式:

x=x0+X方向的偏移量(xt);

y=y0+Y方向的偏移量(yt);

依据上面的等式我们很容易得到如下矩阵:

      |x|     |1 0 xt|  |x0|

|y| =  |0 1 yt| ×    |y0|

|1|     |0 0  1|       | 1 |

然后我们通过如上的矩阵乘法计算得到等式(与我们得出的结论一致,所以也就是我们通过改变初始矩阵中那个矩阵元素的值就可以实现图片在X,Y方向上的平移):

x=x0+X方向的偏移量(xt);

 y=y0+Y方向的偏移量(yt);

 第二、旋转变换特效:

所谓旋转变换就相当于一个像素点围绕某个中心点O旋转到一个新的点,在高中学过三角函数的都知道,通过从初始点A(x0,y0)旋转到B点(x,y)

初始点与X轴正方向夹角为a,旋转过的角度为t,通过三角函数的计算得出如下公式:

设:旋转轴长为:r

x0=r*cosa;  y0=r*sina;

x=r*cos(a+t)=r*cosa*cost-rsina*sint=x0*cost-y0*sint;

y=r*sin(a+t)=r*sina*cost+r*cosa*sint=y0*cost+x0*sint;

从而可以得出如下矩阵:

|x|    |cost -sint 0| |x0|

|y| = |sint  cost 0| ×|y0|

|1|    |0      0     1|    | 1 |

然后我们通过如上的矩阵乘法计算得到等式(与我们得出的结论一致,所以也就是我们通过改变初始矩阵中那个矩阵元素的值就可以实现图片旋转):

                  x=x0*cost-y0*sint;

y=y0*cost+x0*sint;

注意:前面所讲的旋转都是以坐标的原点为旋转中心的,我们还可以以任意点O为旋转中心来进行旋转的变换但是通常需要如下三个步骤:

首先第一需要将坐标的原点平移到任意指定的点O,然后再使用上述我们的所讲的旋转方法来进行旋转,最后就需要将我们的原点还原回去。

第三、缩放变换特效:

所谓像素点的缩放,实际上并不会对像素点缩放,因为像素点已经够小了,不存在什么缩放的概念,那我们这里所说的缩放是怎么样的呢?

我们是通过将每个像素点所在的XY坐标按一定的比例缩放,然后使得图片整体看起来的有一个缩放的效果。

                    x=K1*x0

                y=K2*y0 

通过以上公式反映到我们的变换矩阵中的形式:

|x|    |K1 0 0| |x0|

    |y| = |0 K2 0| ×|y0|

    |1|    |0 0   1|   | 1 |

通过矩阵乘法验证得到等式(与我们得出的结论一致,所以也就是我们通过改变初始矩阵中那两个矩阵元素的值就可以实现图片在X,Y方向上的缩放)

第四、错切变换特效:

所谓错切变换的效果很类似数学上的Shear mapping.错切主要分两种形式:

第一水平错切变换:就是在正常图片的基础上,让每个像素点的Y轴坐标保持不变,而让他们的X坐标按一定比例的缩放,第二就是垂直错切变换:就是在原图的基础上,让让每个像素点的X坐标保持不变,让Y坐标按一定比例的缩放。从而可以得到如下的变换公式:

  x=x0+K1*y0;y=x0+K2*y0;

通过以上的公式反应到我们的变换矩阵中的形式:

      |x|    |1    K1  0| |x0|

|y| =  |K2  1   0| ×|y0|

|1|    |0     0    1|  | 1 |

通过矩阵乘法验证得到等式(与我们得出的结论一致,所以也就是我们通过改变初始矩阵中那两个矩阵元素的值就可以实现图片在X,Y方向上的错切)

综合上述:将得出我们最后的矩阵变换公式:

|Scale_X Skew_X  Trans_X|

|Skew_Y  Scale_Y Trans_Y|

| 0               0             1        |

也就是我只需要改变矩阵中对应的元素的值,我们就可以实现各种变换的特效。

如果用A,B,C,D,E,F来一次标示那些区的话,A和E决定缩放变换,B和D决定了错切变换,C和F决定了平移变换

下面我们就通过一个Demo来验证一下我们的观点。

package com.mikyou.matrix;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;

public class MainActivity extends Activity {
	private ImageView iv;
	private Canvas canvas;
	private Paint paint;
	private Bitmap baseBitmap;
	private Bitmap copyBitmap;
	private Matrix matrix;
	private EditText e1,e2,e3,e4,e5,e6,e7,e8,e9;
	private float t1,t2,t3,t4,t5,t6,t7,t8,t9;
	private List<Float> valueList;
	public void ok(View view){
		valueList=new ArrayList<Float>();
		valueList.clear();
		iv.setImageBitmap(null);
		t1=Float.valueOf(e1.getText().toString());
		valueList.add(t1);
		t2=Float.valueOf(e2.getText().toString());
		valueList.add(t2);
		t3=Float.valueOf(e3.getText().toString());
		valueList.add(t3);
		t4=Float.valueOf(e4.getText().toString());
		valueList.add(t4);
		t5=Float.valueOf(e5.getText().toString());
		valueList.add(t5);
		t6=Float.valueOf(e6.getText().toString());
		valueList.add(t6);
		t7=Float.valueOf(e7.getText().toString());
		valueList.add(t7);
		t8=Float.valueOf(e8.getText().toString());
		valueList.add(t8);
		t9=Float.valueOf(e9.getText().toString());
		valueList.add(t9);
		float[] imageMatrix=new float[9];
		for (int i = 0; i <valueList.size(); i++) {
			imageMatrix[i]=valueList.get(i);
		}
		matrix=new Matrix();
		matrix.setValues(imageMatrix);//类似ColorMatrix中的setColorFilter方法
		canvas.drawBitmap(baseBitmap, matrix, paint);
		iv.setImageBitmap(copyBitmap);
	}
	public void reset(View view){
		iv.setImageBitmap(null);
		e1.setText("1");
		e2.setText("0");
		e3.setText("0");
		e4.setText("0");
		e5.setText("1");
		e6.setText("0");
		e7.setText("0");
		e8.setText("0");
		e9.setText("1");
	}
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
		initData();
	}
	private void initView() {
		registerAllViewId();
		registerAllViewEvent();
	}
	private void registerAllViewEvent() {

	}
	private void registerAllViewId() {
		iv=(ImageView) findViewById(R.id.iv);
		e1=(EditText) findViewById(R.id.e1);
		e2=(EditText) findViewById(R.id.e2);
		e3=(EditText) findViewById(R.id.e3);
		e4=(EditText) findViewById(R.id.e4);
		e5=(EditText) findViewById(R.id.e5);
		e6=(EditText) findViewById(R.id.e6);
		e7=(EditText) findViewById(R.id.e7);
		e8=(EditText) findViewById(R.id.e8);
		e9=(EditText) findViewById(R.id.e9);
	}
	private void initData() {
		baseBitmap=BitmapFactory.decodeResource(getResources(), R.drawable.pre);
		copyBitmap=Bitmap.createBitmap(baseBitmap.getWidth(), baseBitmap.getHeight(), baseBitmap.getConfig());
		canvas=new Canvas(copyBitmap);
		paint=new Paint();

	}

}

运行结果:

实际上,除了我们通过底层的方法,直接修改他们的变换矩阵的值来达到某种变换的效果,实际上android已经封装一些变换的API接口

例如:

旋转变换:matrix.setRotate() 平移变换 matrix.setTranslate() 缩放变换 matrix.setScale() 错切变换 matrix.setSkew()

不过android中还提供两个非常重要的方法一个是pre(),一个是post() 提供矩阵的前乘和后乘运算。

注意: 以上的几个set方法都会重新清空重置矩阵中的值,而有时候我需要实现叠加的效果,就单单用set方法是无法完成,因为第二次叠加的变换,会把上一次的

第一次变换的矩阵值给清空,只会保存最近一次的矩阵中的值。所以android 就给我们提供pre和post方法,这两个方法可实现矩阵混合效果,从而实现图形变换的

叠加。例如:

先移动到点(400,400),在旋转45度,最后平移到(200,200)

用以上例子来说明pre(先乘)和post(后乘)运算的区别,因为在矩阵乘法中不支持交换律,所以我们需要用这两个方法来区别实现

pre运算实现:

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

post运算实现:

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

最后我们通过一个案例来实现一个组合叠加变换的效果

package com.mikyou.dealImage;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends Activity {
/**
 *@author zhongqihong
 *图片的绘制的个人总结:
 *如何去仿照一张原图去重新绘制一张图片呢??
 *需要如下材料:
 *1、需要参考的原图
 *2、画纸(实际上就是一张没有任何内容的图片,空白图片)
 *3、画布(就是用于固定画纸的)
 *4、画笔(就是用于绘制图片的工具)
 *其实一般分为如下几步:
 *1、首先必须拿到原图构造出一张空画纸(即空白图片),知道原图的大小,分辨率等信息,从而就可以确定我的画纸的大小,及所确定的分辨率(默认使用原图的分辨率)。
 *		baseBitmap= BitmapFactory.decodeResource(getResources(),R.drawable.img_small_1);//拿到原图对象
 *	   copyBitmap=Bitmap.createBitmap(baseBitmap.getWidth(),baseBitmap.getHeight(), baseBitmap.getConfig());//根据原图对象的相关信息,得到同大小的画纸,包括原图分辨率
 *2、然后,确定了一个空白的画纸后,就需要将我们的画纸固定在我们的画布上
 *	Canvas canvas=new Canvas(copyBitmap);
 *3、接着就是构造一个画笔对象
 * Paint paint=new Paint();
 * 4、接着对图片进行一系列的操作,诸如:缩放、平移、旋转等操作
 * Matrix matrix =new Matrix();
 * 缩放:(缩放中心点:默认是画纸左上角点的坐标)
 * matrix.setScale(1.5f,1.5f);//第一个参数为X轴上的缩放比例为1.5(>1表示放大,<1表示缩小)此处就表示在XY轴上分别放大为原图1.5倍
 * matrix.setScale(-1f,1f);//表示在X轴反向缩放,Y轴不变,即相当于把原图往X轴负方向反向翻转了一下,即相当于处理后的图片与原图位置(即现在的画纸位置)关于Y负半轴对称
 * matrix.setScale(1f,-1f);//表示在X轴不变,Y轴的,即相当于把原图往Y轴负方向反向翻转了一下,即相当于处理后的图片与原图位置(即现在的画纸位置)关于X正半轴对称
 * 5、处理完后,就需要把我们处理好的图片绘制在画布上形成最后的图片
 * canvas.drawBitmap(baseBitmap,matrix,paint);//第一个参数表示原图的Bitmap对象,表示按照原图的样式内容在原来相同规格空白的纸上画出图片内容
		iv.setImageBitmap(copyBitmap);//最后将图片的显示在ImageView控件上
 * 个人理解:感觉整个过程很像PS中置入一张图片到一张空白画纸上,首先我们
 * 需要去设置一张空白的画纸,然后将我们的原图置入,在置入之前我们可以做些对图片的操作
 * 包括缩放,平移,旋转,确定一些操作后,点击确定就相当于调用绘制方法,从而就形成一张处理后的图片
 *
 * */

	private ImageView iv;
	private Bitmap baseBitmap;//原图
	private Bitmap copyBitmap;//画纸
	private Canvas canvas;//画布
	private Paint paint;//画笔
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		iv= (ImageView) findViewById(R.id.iv2);
	}
	public void btn(View view){
		//拿到原图:得到原图大小
		baseBitmap= BitmapFactory.decodeResource(getResources(),R.drawable.img_small_1);
		//1、拿到一张与原图一样大小的纸,并没有内容
		copyBitmap=Bitmap.createBitmap(1200,1300, baseBitmap.getConfig());//拿到原图的分辨率和原图的Config
		//2、将这张纸固定在画布上
		Canvas canvas=new Canvas(copyBitmap);
		//3、画笔
		paint=new Paint();
		paint.setAlpha(100);
		//4、加入一些处理的规则
		Matrix matrix=new Matrix();
		//1、缩放规则
		//matrix.setScale(1.5f,1.5f);
		//2、位移,
		//   matrix.setTranslate(50f,50f);//分别在x轴和Y轴上位移50,针对图片的左上角为原点来移动
		//3、旋转
		//matrix.setRotate(45f);//代表顺时针旋转45度,默认以左上角为原点
		// matrix.setRotate(45, baseBitmap.getWidth()/2, baseBitmap.getHeight()/2);//第二个参数和第三个参数表示旋转中心的点的X,Y坐标
		//4、翻转镜面效果
		// matrix.setScale(-1f, 1f);
		// matrix.setTranslate(baseBitmap.getWidth(), 0);
		//  matrix.postTranslate(baseBitmap.getWidth(), 0);//如果要对图片进行多次操作,就要用post的方法来操作
		//5、倒影效果
		matrix.setScale(1f, -1f);//先把图片在y轴方向反向缩放
		matrix.postTranslate(0, baseBitmap.getHeight());//然后再把反向缩放后的图片移到canvas上显示即可
		matrix.postTranslate(baseBitmap.getWidth()+160, 0);
		matrix.postSkew(-1, 0);
		//5、将处理过的图片画出来
		canvas.drawBitmap(baseBitmap,matrix,paint);//第一个参数表示原图的Bitmap对象,表示按照原图的样式内容在原来相同规格空白的纸上画出图片内容
		iv.setImageBitmap(copyBitmap);//最后将图片的显示在ImageView控件上

	}

}

运行效果:

与ColorMatrix颜色矩阵类似,在图形变换中也有一个针对每一个像素块做处理,而在颜色变换是针对每个像素点。针对像素快的处理我们使用

drawBitmapMesh()方法来处理。

drawBitmapMesh()和处理每像素点类似,只不是将图像分成一个一个的小块然后针对每个小块来改变整个图像

drawBitmapMesh()方法很是重要利用可以实现很多的图片的特效。下面我们就重点来介绍一下这个方法:

drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint)

官方源码是这样介绍这个方法的:

/**

* Draw the bitmap through the mesh, where mesh vertices are evenly

* distributed across the bitmap. There are meshWidth+1 vertices across, and

* meshHeight+1 vertices down. The verts array is accessed in row-major

* order, so that the first meshWidth+1 vertices are distributed across the

* top of the bitmap from left to right. A more general version of this

* method is drawVertices().

*

* @param bitmap The bitmap to draw using the mesh

* @param meshWidth The number of columns in the mesh. Nothing is drawn if

*                  this is 0

* @param meshHeight The number of rows in the mesh. Nothing is drawn if

*                   this is 0

* @param verts Array of x,y pairs, specifying where the mesh should be

*              drawn. There must be at least

*              (meshWidth+1) * (meshHeight+1) * 2 + vertOffset values

*              in the array

* @param vertOffset Number of verts elements to skip before drawing

* @param colors May be null. Specifies a color at each vertex, which is

*               interpolated across the cell, and whose values are

*               multiplied by the corresponding bitmap colors. If not null,

*               there must be at least (meshWidth+1) * (meshHeight+1) +

*               colorOffset values in the array.

* @param colorOffset Number of color elements to skip before drawing

* @param paint  May be null. The paint used to draw the bitmap

*/

它大概的意思的是这样的,通过网格来绘制图片,那么这张图片将会被meshWidth画出横向格子,meshHeight画出纵向的格子

那么在横向上将会产生meshWidth+1个交叉点,在纵向上会产生meshHeight+1个交叉点,最后总的交叉点个数为(meshWidth+1)*(meshHeight+1)

bitmap: 需要绘制在网格上的图像。

meshWidth: 网格的宽度方向的数目(列数),为0时不绘制图像。

meshHeight:网格的高度方向的数目(含数),为0时不绘制图像。

verts: (x,y)对的数组,表示网格顶点的坐标,至少需要有(meshWidth+1) * (meshHeight+1) * 2 + meshOffset 个(x,y)坐标。

vertOffset: verts数组中开始跳过的(x,y)对的数目。

Colors: 可以为空,不为空为没个顶点定义对应的颜色值,至少需要有(meshWidth+1) * (meshHeight+1) * 2 + meshOffset 个(x,y)坐标。

colorOffset: colors数组中开始跳过的(x,y)对的数目。

paint: 可以为空。

下面将通过一个自定义View方法来实现,旗帜飘扬的图片控件。并且添加自定义属性,下次使用可以方便的在XMl中更换图片

修改划分方格数,振幅大小,频率大小

实现整体思路如下:

针对像素块来实现图形扭曲的原理:

它的原理就是通过修改划分后的小方格产生交叉点的坐标,来实现图片的扭曲

本案例实现一个旗帜飘动形状的图片

大致的思路如下:

实现思路整体分两部分,第一部分取得所有扭曲前图片的所有交叉点的坐标

并把这些交叉点坐标保存在orig数组中;第二部分修改原图中方格每个交叉点的坐标,遍历这个数组,然后通过某种算法

使得这些交叉点的坐标,呈某种规律函数曲线变化。这里就以三角函数中的正弦函数

来改变这些交叉点坐标,从而产生每个交叉点新的坐标,然后再将这些新的坐标保存在verts数组中,最后通过drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint)

实现图片的绘制。

自定义属性:attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <attr name="Src" format="reference"></attr>
    <attr name="Amplitude" format="integer"></attr>
    <attr name="RowNum" format="integer"></attr>
    <attr name="ColumnNum" format="integer"></attr>
    <attr name="Frequency" format="float"></attr>

    <declare-styleable name="MikyouBannerView">
        <attr name="Src"></attr>
        <attr name="Amplitude"></attr>
        <attr name="Frequency"></attr>
        <attr name="RowNum"></attr>
        <attr name="ColumnNum"></attr>
    </declare-styleable>

</resources>

自定义View的MyBannerImageView类:

package com.mikyou.myview;

import com.mikyou.piexkuai.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
/**
 * @author mikyou
 * 针对像素块来实现图形扭曲的原理:
 * 它的原理就是通过修改划分后的小方格产生交叉点的坐标,来实现图片的扭曲
 * 本案例实现一个旗帜飘动形状的图片
 * 大致的思路如下:
 * 实现思路整体分两部分,第一部分取得所有扭曲前图片的所有交叉点的坐标
 * 并把这些交叉点坐标保存在orig数组中;第二部分修改原图中方格每个交叉点的坐标,遍历这个数组,然后通过某种算法
 * 使得这些交叉点的坐标,呈某种规律函数曲线变化。这里就以三角函数中的正弦函数
 * 来改变这些交叉点坐标,从而产生每个交叉点新的坐标,然后再将这些新的坐标保存在
 * verts数组中,最后通过drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, paint)
 * 实现图片的绘制。
 * */
public class MyBannerImageView extends View{
	//定义两个常量表示需要将这张图片划分成20*20=400个小方格,//定义两个常量,这两个常量指定该图片横向,纵向上都被划分为20格
	private  int WIDTH=40;//横向划分的方格数目
	private  int HEIGHT=40;//纵向划分的方格数目
	private float FREQUENCY=0.1f;//三角函数的频率大小
	private int AMPLITUDE=60;//三角函数的振幅大小
	//那么将会产生21*21=421个交叉点
	private  int POINT_COUNT=(WIDTH+1)*(HEIGHT+1);
	//由于,我要储存一个坐标信息,一个坐标包括x,y两个值的信息,相邻2个值储存为一个坐标点
	//其实大家应该都认为这样不好吧,还不如直接写一个类来直接保存一个点的信息,但是没办法
	// 但是在drawBitmapMesh方法中传入的是一个verts数组,该数组就是保存所有点的x,y坐标全都放在一起
	//所以,我就只能这样去控制定义orig和verts数组了,
	private Bitmap baseBitmap;
	private float[] orig=new float[POINT_COUNT*2];//乘以2是因为x,y值是一对的。
	private float[] verts=new float[POINT_COUNT*2];
	private float k;
	public MyBannerImageView(Context context, AttributeSet attrs,
			int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		//接收自定义属性值
		TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.MikyouBannerView);
		for (int i = 0; i < array.getIndexCount(); i++) {
			int attr=array.getIndex(i);
			switch (attr) {
			case R.styleable.MikyouBannerView_Src:
				baseBitmap=BitmapFactory.decodeResource(getResources(), array.getResourceId(attr, R.drawable.ic_launcher));
				break;
			case R.styleable.MikyouBannerView_ColumnNum:
				HEIGHT=array.getInt(attr, 40);
				break;
			case R.styleable.MikyouBannerView_RowNum:
				WIDTH=array.getInt(attr, 40);
				break;
			case R.styleable.MikyouBannerView_Amplitude:
				AMPLITUDE=array.getInt(attr, 60);
				break;
			case R.styleable.MikyouBannerView_Frequency:
				FREQUENCY=array.getFloat(attr, 0.1f);
				break;
			default:
				break;
			}
		}
		array.recycle();
		initData();
	}
	public MyBannerImageView(Context context, AttributeSet attrs) {
		this(context, attrs,0);
	}
	public MyBannerImageView(Context context) {
		this(context,null);
	}

	//set,gfanfg
	public int getWIDTH() {
		return WIDTH;
	}
	public void setWIDTH(int wIDTH) {
		WIDTH = wIDTH;
	}
	public int getHEIGHT() {
		return HEIGHT;
	}
	public void setHEIGHT(int hEIGHT) {
		HEIGHT = hEIGHT;
	}
	public float getFREQUENCY() {
		return FREQUENCY;
	}
	public void setFREQUENCY(float fREQUENCY) {
		FREQUENCY = fREQUENCY;
	}
	public int getAMPLITUDE() {
		return AMPLITUDE;
	}
	public void setAMPLITUDE(int aMPLITUDE) {
		AMPLITUDE = aMPLITUDE;
	}
	public Bitmap getBaseBitmap() {
		return baseBitmap;
	}
	public void setBaseBitmap(Bitmap baseBitmap) {
		this.baseBitmap = baseBitmap;
	}
	@Override
	protected void onDraw(Canvas canvas) {
		flagWave();
		k+=FREQUENCY;
		canvas.drawBitmapMesh(baseBitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);
		invalidate();
	}
	private void initData() {
		float baseBitmapWidth=baseBitmap.getWidth();
		float baseBitmapHeight=baseBitmap.getHeight();
		int index=0;
		//通过遍历所有的划分后得到的像素块,得到原图中每个交叉点的坐标,并把它们保存在orig数组中
		for (int i = 0; i <= HEIGHT; i++) {//因为这个数组是采取行优先原则储存点的坐标,所以最外层为纵向的格子数,然后一行一行的遍历
			float fy=baseBitmapHeight*i/HEIGHT;//得到每行中每个交叉点的y坐标,同一行的y坐标一样
			for (int j = 0; j <= WIDTH; j++) {
				float fx=baseBitmapHeight*j/WIDTH;//得到每行中的每个交叉点的x坐标,同一列的x坐标一样
				orig[index*2+0]=verts[index*2+0]=fx;//存储每行中每个交叉点的x坐标,为什么是index*2+0作为数组的序号呢??
				//因为我们之前也说过这个数组既存储x坐标也存储y坐标,所以每个点就占有2个单位数组空间
				orig[index*2+1]=verts[index*2+1]=fy+200;//存储每行中每个交叉点的y坐标.为什么需要+1呢?正好取x坐标相邻的下标的元素的值
				//+200是为了避免等下在正弦函数扭曲下,会把上部分给挡住所以下移200
				index++;
			}
		}
	}
	/**
	 * @author mikyou
	 * 加入三角函数正弦函数Sinx的算法,来修改原图数组保存的交叉点的坐标
	 * 从而得到旗帜飘扬的效果,这里我们只修改y坐标,x坐标保持不变
	 * */
	public void flagWave(){
		for (int i = 0; i <=HEIGHT ; i++) {
			for (int j = 0; j <WIDTH; j++) {
				verts[(i*(WIDTH+1)+j)*2+0]+=0;
				float offSetY=(float)Math.sin((float)j/WIDTH*2*Math.PI+Math.PI*k);
				verts[(i*(WIDTH+1)+j)*2+1]=orig[(i*(WIDTH+1)+j)*2+1]+offSetY*AMPLITUDE;
			}
		}
	}
}

activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:mikyou="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <com.mikyou.myview.MyBannerImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        mikyou:Amplitude="100"
        mikyou:ColumnNum="20"
        mikyou:Frequency="0.1"
        mikyou:RowNum="20"
        mikyou:Src="@drawable/pre" />

</LinearLayout>

运行的结果;

注意:不好意思,因为没有录制GIF所以看不出动态效果,大家可以下载Demo看看.

Demo下载链接

时间: 2024-11-13 05:17:41

浅谈android中图片处理之图形变换特效Matrix(四)的相关文章

浅谈android中图片处理之色彩特效处理ColorMatrix(三)

在android开发中对图片处理很是频繁,其中对图片的颜色处理就是很常见的一种.我们经常看到一些类似美图秀秀,美颜相机的app,为什么那么黑的人拍出来是确实那么地白呢?长的那么那个(丑)的人,用美颜相机拍出来的看起来也有那么回事(拍出来就感觉挺漂亮).就像网上有个段子,有钱的都去韩国了,没钱都用ps了.韩国的就去整形,中国的就用ps.这些话虽然是调侃,但是从某种程度上来说像类似美图秀秀,美颜相机app确实挺受大家欢迎.但是你是否曾想过它这种效果,它是怎么实现的吗?你是否曾想过它的原理是什么吗?所

浅谈android中加载高清大图及图片压缩方式(二)

这一讲就是本系列的第二篇,一起来聊下关于android中加载高清大图的问题,我们都知道如果我们直接加载原图的话,一个是非常慢,需要等待一定时间,如果没有在一定的时间内给用户响应的话,将会极大影响用户的体验.另一个是如果你的手机内存小的话,可能会直接崩溃.这也就是直接加载高清原图问题.遇到这些问题很容易想到的一点就是图片压缩,本篇文章也就是讲述图片压缩方式来实现加载高清大图的效果.但是现在问题就来了,通过上篇博客我们知道,手机的分辨率有很多,如何保证我同一张图片在不同分辨率的手机上能适当的压缩比例

浅谈android中仅仅使用一个TextView实现高仿京东,淘宝各种倒计时

今天给大家带来的是仅仅使用一个TextView实现一个高仿京东.淘宝.唯品会等各种电商APP的活动倒计时.最近公司一直加班也没来得及时间去整理,今天难得休息想把这个分享给大家,只求共同学习,以及自己后续的复习.为什么会想到使用一个TextView来实现呢?因为最近公司在做一些优化的工作,其中就有一个倒计时样式,原来开发的这个控件的同事使用了多个TextView拼接在一起的,实现的代码冗余比较大,故此项目经理就说:小宏这个就交给你来优化了,并且还要保证有一定的扩展性,当时就懵逼了.不知道从何处开始

浅谈android中只使用一个TextView实现高仿京东,淘宝各种倒计时

今天给大家带来的是只使用一个TextView实现一个高仿京东.淘宝.唯品会等各种电商APP的活动倒计时.近期公司一直加班也没来得及时间去整理,今天难得歇息想把这个分享给大家.只求共同学习,以及自己兴许的复习. 为什么会想到使用一个TextView来实现呢?由于近期公司在做一些优化的工作,当中就有一个倒计时样式,原来开发的这个控件的同事使用了多个TextView拼接在一起的.实现的代码冗余比較大.故此项目经理就说:小宏这个就交给你来优化了.而且还要保证有一定的扩展性,当时就懵逼了.不知道从何处開始

浅谈android中手机联系人字母索引表的实现

实际上字母索引表的效果,可以说在现在的众多APP中使用的非常流行,比如支付宝,微信中的联系人,还有购物,买票的APP中选择全国城市,切换城市的时候,这时候的城市也就是按照一个字母索引的顺序来显示,看起来是很方便的.其实这种字母索引表的效果最开始是出现在微信的联系人中.因为觉得这种效果功能在今后的项目中可以说是非常常见,可能会用的上,所以准备来波博客讲述一下实现的原理,一来方便以后自己复习,二来如果能够帮助一些android路上奋斗小伙伴也是蛮有意义的. 下面我们先来看下效果图, 看完效果图后我们

浅谈android中的异步加载一

1.为什么需要异步加载. 因为我们都知道在Android中的是单线程模型,不允许其他的子线程来更新UI,只允许UI线程(主线程更新UI),否则会多个线程都去更新UI会造成UI的一个混乱有些耗时的操纵(例如网络请求等),如果直接放到主线程中去请求的话则会造成主线程阻塞,而我们系统有规定的响应时间,当响应的时间超过了了阻塞的时间就会造成"Application No Response",也就是我们熟知的ANR错误解决上述问题的时候:我们一般使用的是线程或者线程池+Handler机制如果线程

浅谈android中的ListView合集系列之解决ScrollView和ListView嵌套冲突(一)

相信大家都已经可以熟练使用ListView和GridView,大神们估计都在使用RecyclerView了.如果还在使用ListView,你肯定有这样的一个深刻的感受,那就是在做一个APP的时候使用ListView和GridView很频繁,并且经常会遇到一个页面中除了有ListView或GridView可能还有一些其他的内容,但是可能内容很多,你第一时间就会想到让它整体滑动即可,那就是在总的布局外面包裹一个ScrollView.也就是出现了ScrollView中嵌套一个ListView的场景,或

浅谈Android中的MVP

转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/51745798 本文出自:[顾林海的博客] 前言 为什么使用MVP,网上有很多说法,最主要就是减轻了Activity的责任,相比于MVC中的Activity承担的责任太多,因此有必要讲讲MVP. MVP入门 在MVC框架中,View是可以直接读取Model模型中的数据的,Model模型数据发生改变是会通知View数据显示发生相应的改变.而在MVP中Model和View之

浅谈Android中的MVC与MVP模式

使用MVC或者MVP模式会增加很多的类,但是确可以让代码结构变得清晰,方便了后期维护拓展方便.把数据层跟视图层分离,处理事务的逻辑单独的放在一个类中,让Activity仅仅具有展示功能. 下面我们就MVC模式跟MVP模式进行分别讲解,总之来说各有利弊.在实际的开发中,我们根据实际情况进行取舍.个人认为MVP模式更简单一些,因为MVP模式中会把部分逻辑Activity中,但是这就造成了Activity的相对繁琐,没有实现完全的隔离.而我们采用的MVC模式则是更好的处理了这个问题,但是在应用的过程中