android 特效绘图,Android绘图机制与处理技巧——Android图像处理之图形特效处理...
Android变形矩阵——Matrix
对于图像的图形变换,Android系统是通过矩阵来进行处理的,每个像素点都表达了其坐标的X、Y信息。Android的图形变换矩阵是一个3x3的矩阵,如下图所示:
72F0CAC1-14FB-40F8-A430-8F542B09DC4E.png
当使用变换矩阵去处理每一个像素点的时候,与颜色矩阵的矩阵乘法一样,计算公式如下所示:
X1=aX+bY+c
Y1=dX+eY+f
1=gX+hY+i
通常情况下,会让g=h=0,i=1,这样就使1=gX+hY+i恒成立。因此,只需着重关注上面几个参数即可。
与色彩变换矩阵的初始矩阵一样,图形变换矩阵也有一个初始矩阵。就是对角线元素a、e、i为1,其他元素为0的矩阵,如下图所示:
图形变换初始矩阵
图像的变形处理通常包含以下四类基本变换:
Translate——平移变换
Rotate——旋转变换
Scale——缩放变换
Skew——错切变换
平移变换
平移变换的坐标值变换过程就是将每个像素点都进行平移变换,当从P(x0,y0)平移到P(x1,y1)时,所需的平移矩阵如下所示:
F8CD701F-4C5A-40DF-9B67-E50500B702DC.png
旋转变换
旋转变换即指一个点围绕一个中心旋转到一个新的点。当从P(x0,y0)点,以坐标原点O为旋转中心旋转到P(x1,y1)时,可以将点的坐标都表达成OP与X轴正方向夹角的函数表达式(其中r为线段OP的长度,α为OP(x0,y0)与X轴正方向夹角,θ为OP(x0,y0)与OP(x1,y1)之间夹角),如下所示:
x0=rcosα
y0=rsinα
x1=rcos(α+θ)=rcosαcosθ−rsinαsinθ=x0cosθ−y0sinθ
y1=rsin(α+θ)=rsinαcosθ+rcosαsinθ=y0cosθ+x0sinθ
矩阵形式如下图所示:
旋转变换矩阵
前面是以坐标原点为旋转中心的旋转变换,如果以任意点O为旋转中心来进行旋转变换,通常需要以下三个步骤:
1.将坐标原点平移到O点
2.使用前面讲的以坐标原点为中心的旋转方法进行旋转变换
3.将坐标原点还原
缩放变换
一个像素点是不存在缩放的概念的,但是由于图像是由很多个像素点组成的,如果将每个点的坐标都进行相同比例的缩放,最终就会形成让整个图像缩放的效果,缩放效果的公式如下
x1=K1x0
y1=K2y0
矩阵形式如下图所示:
缩放变换矩阵
错切变换
错切变换(skew)在数学上又称为Shear mapping(可译为“剪切变换“)或者Transvection(缩并),它是一种比较特殊的线性变换。错切变换的效果就是让所有点的X坐标(或者Y坐标)保持不变,而对应的Y坐标(或者X坐标)则按比例发生平移,且平移的大小和该点到Y轴(或者X轴)的距离成正比。错切变换通常包含两种——水平错切与垂直错切。
错切变换的计算公式如下:
水平错切
x1=x0+K1y0
y1=y0
垂直错切
x1=x0
y1=K2x0+y0
矩阵形式如下图
错切变换矩阵
由上面的分析可以发现,这个图形变换3x3的矩阵与色彩变换矩阵一样,每个位置的元素所表示的功能是有规律的,总结如下:
矩阵变换规律
可以发现,a、b、c、d、e、f这六个矩阵元素分别对应以下变换:
a和e控制Scale——缩放变换
b和d控制Skew——错切变换
a和e控制Trans——平移变换
a、b、d、e共同控制Rotate——旋转变换
通过类似色彩矩阵中模拟矩阵的例子来模拟变形矩阵。在图形变换矩阵中,同样是通过一个一维数组来模拟矩阵,并通过setValues()方法将一个一维数组转换为图形变换矩阵,代码如下所示:
private float[] mImageMatrix = new float[9];
Matrix matrix = new Matrix();
matrix.setValues(mImageMatrix);````
当获得了变换矩阵后,就可以通过以下代码将一个图像以这个变换矩阵的形式绘制出来。
canvas.drawBitmap(mBitmap, mMatrix, null);
public class HandleImage1Activity extends BaseActivity {
private ImageView mImageView;
private GridLayout mGroup;
private float mHue, mSaturation, mLum;
private Bitmap mBitmap;
private int mEtWidth, mEtHeight;
private EditText[] mEts = new EditText[9];
private float[] mImageMatrix = new float[9];
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handleimg1);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.iu1);
mImageView = (ImageView) findViewById(R.id.img);
mGroup = (GridLayout) findViewById(R.id.group);
mGroup.post(new Runnable() {
@Override
public void run() {
// 获取宽高信息
mEtWidth = mGroup.getWidth() / 3;
mEtHeight = mGroup.getHeight() / 3;
addEts();
initMatrix();
}
});
mImageView.setImageBitmap(mBitmap);
}
// 初始化颜色矩阵为初始状态
private void initMatrix() {
for (int i = 0; i < 9; i++) {
if (i % 4 == 0)
mEts[i].setText(String.valueOf(1));
else
mEts[i].setText(String.valueOf(0));
}
}
// 添加EditText
private void addEts() {
for (int i = 0; i < 9; i++) {
EditText editText = new EditText(this);
editText.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL);
mEts[i] = editText;
mGroup.addView(mEts[i], mEtWidth, mEtHeight);
}
}
// 获取矩阵值
private void getMatrix() {
for (int i = 0; i < 9; i++) {
mImageMatrix[i] = Float.valueOf(mEts[i].getText().toString());
}
}
// 将矩阵值设置到图像
private void setImageMatrix() {
Bitmap bmp = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
Matrix matrix = new Matrix();
matrix.setValues(mImageMatrix);
canvas.drawBitmap(mBitmap,matrix,null);
mImageView.setImageBitmap(bmp);
}
// 作用矩阵效果
public void btnChange(View view) {
getMatrix();
setImageMatrix();
}
// 重置矩阵效果
public void btnReset(View view) {
initMatrix();
getMatrix();
setImageMatrix();
}
}````
Android系统同样提供了一些API来简化矩阵的运算,我们不必每次都去设置矩阵的每一个元素值。Android中使用Matrix类来封装矩阵,并提供了以下几个操作方法来实现上面的四中变换方式:
matrix.setRotate()——旋转变换
matrix.setTranslate()——平移变换
matrix.setScale()——缩放变换
matrix.setSkew()——错切变换
matrix.preX和matrix.postY——提供矩阵的前乘和后乘运算
Matrix类的set方法会重置矩阵中的值,而post和pre方法不会,这两个方法常用来实现矩阵的混合作用。不过要注意的是,矩阵运算不满足乘法的交换律,所以矩阵乘法的前乘和后乘是两种不同的运算方式。举例说明,比如需要实现以下效果:
先旋转45度
再平移到(200, 200)
如果使用后乘运算,表示当前矩阵乘上参数代表的矩阵,代码如下所示:
matrix.setRotate(45);
matrix.postTranslate(200, 200);
如果使用前乘运算,表示参数代表的矩阵乘上当前矩阵,代码如下所示:
matrix.setTranslate(200, 200);
matrix.preRotate(45);
像素块分析
图像的特效处理有两种方式,即使用矩阵来进行图像变换和使用drawBitmapMesh()方法来进行处理。drawBitmapMesh()与操纵像素点来改变色彩的原理类似,只不过是把图像分成了一个个的小块,然后通过改变每一个图像块来修改整个图像。
drawBitmapMesh()方法代码如下:
public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
关键的参数如下:
bitmap:将要扭曲的图像
meshWidth:需要的横向网格数目
meshHeight :需要的纵向网格数目
verts:网格交叉点坐标数组
vertOffset:verts数组中开始跳过的(x, y)坐标对的数目
要使用drawBitmapMesh()方法就需先将图片分割为若干个图像块。所以,在图像上横纵各画N条线,而这横纵各N条线就交织成了NxN个点,而每个点的坐标则以x1,y1,x2,y2,...,xn,yn的形式保存在verts数组中。也就是说verts数组的每两位用来保存一个交织点,第一个是横坐标,第二个是纵坐标。而整个drawBitmapMesh()方法改变图像的方式,就是靠这些坐标值的改变来重新定义每一个图像块,从而达到图像效果处理的功能。
drawBitmapMesh()方法的功能非常强大,基本上可以实现所有的图像特效,但使用起来也非常复杂,其关键就是在于计算、确定新的交叉点的坐标。下面举例说明如何使用drawBitmapMesh()方法来实现一个旗帜飞扬的效果。
要想达到旗帜飞扬的效果,只需要让图片中每个交叉点的横坐标较之前不发生变化,而纵坐标较之前坐标呈现一个三角函数的周期性变化即可。
首先获取交叉点的坐标,并将坐标保存到orig数组中,其获取交叉点坐标的原理就是通过循环遍历所有的交叉线,并按比例获取其坐标,代码如下所示:
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.test);
float bitmapWidth = mBitmap.getWidth();
float bitmapHeight = mBitmap.getHeight();
int index = 0;
for (int y = 0; y <= HEIGHT ; y++) {
float fy = bitmapHeight * y / HEIGHT;
for (int x = 0; x <= WIDTH; x++) {
float fx = bitmapWidth * x / WIDTH;
orig[index * 2] = verts[ index * 2] = fx;
//这里人为将坐标+100是为了让图像下移,避免扭曲后被屏幕遮挡
orig[index * 2 + 1] = verts[ index * 2 + 1] = fy + 100;
index++;
}
}
接下来,在onDraw()方法中改变交叉点的纵坐标的值,为了实现旗帜飘扬的效果,使用一个正弦函数sinx来改变交叉点纵坐标的值,而横坐标不变,并将变化后的值保存到verts数组中,代码如下所示:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
flagWave();
K += 0.1f;//将K的值增加
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);
invalidate();
}
/**
* 按当前点所在的横坐标的位置来确定纵坐标的偏移量,其中A代表正弦函数中的振幅大小
*/
private void flagWave() {
for (int j = 0; j <= HEIGHT; j++) {
for (int i = 0; i <= WIDTH; i++) {
//在获取纵坐标的偏移量时,利用正弦函数的周期性给函数增加一个周期K * Math.PI,就是为了让图像能够动起来
float offsetY = (float) Math.sin(2 * Math.PI * i / WIDTH + K * Math.PI);
verts[(j * (WIDTH + 1) + i) * 2 + 1] = orig[(j * (WIDTH + 1) + i) * 2 + 1] + offsetY * A;
}
}
}
这样,每次在重绘时,通过改变相位来改变偏移量,从而造成一个动态的效果,就好象旗帜在风中飘扬一样,效果图如下。
使用drawBitmapMesh()方法可以创建很多复杂的图像效果,但是对它的使用也相对复杂,需要我们对图像处理有很深厚的功底。同时,对算法的要求也比较高,需要计算各种特效下不同的坐标点变化规律,从而设计出不同的特效。
代码如下:
public class WaveView extends AppCompatImageView {
private static final int HEIGHT=200;//想要划分的高
private static final int WIDTH=200;//想要划分的宽
private int COUNT = (WIDTH + 1) * (HEIGHT + 1);
private float[] verts = new float[COUNT * 2];
private float[] orig = new float[COUNT * 2];
private float A = 50;//表示正弦函数中的振幅大小
private float K = 1;
private Bitmap mBitmap;
private int mWaveSrc;
public WaveView(Context context) {
this(context,null);
}
public WaveView(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
public WaveView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(R.styleable.WaveView);
mWaveSrc=typedArray.getResourceId(R.styleable.WaveView_waveSrc,R.drawable.iu1);
mBitmap= BitmapFactory.decodeResource(getResources(),mWaveSrc);
float bitmapWidth = mBitmap.getWidth();
float bitmapHeight = mBitmap.getHeight();
int index = 0;
for (int y = 0; y <= HEIGHT ; y++) {
float fy = bitmapHeight * y / HEIGHT;
for (int x = 0; x <= WIDTH; x++) {
float fx = bitmapWidth * x / WIDTH;
orig[index * 2] = verts[ index * 2] = fx;
//这里人为将坐标+100是为了让图像下移,避免扭曲后被屏幕遮挡
orig[index * 2 + 1] = verts[ index * 2 + 1] = fy ;
index++;
}
}
typedArray.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
flagWave();
K += 0.1f;//将K的值增加
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);
setImageBitmap(mBitmap);
invalidate();
}
/**
* 按当前点所在的横坐标的位置来确定纵坐标的偏移量,其中A代表正弦函数中的振幅大小
*/
private void flagWave() {
for (int j = 0; j <= HEIGHT; j++) {
for (int i = 0; i <= WIDTH; i++) {
//在获取纵坐标的偏移量时,利用正弦函数的周期性给函数增加一个周期K * Math.PI,就是为了让图像能够动起来
float offsetY = (float) Math.sin(2 * Math.PI * i / WIDTH + K * Math.PI);
verts[(j * (WIDTH + 1) + i) * 2 + 1] = orig[(j * (WIDTH + 1) + i) * 2 + 1] + offsetY * A;
}
}
}
}
android 特效绘图,Android绘图机制与处理技巧——Android图像处理之图形特效处理...相关推荐
- Android图像处理之图形特效处理
前面我们了解了关于图像色彩处理的相关技巧,下面继续来探讨图形图像方面的处理技巧. 1.Android变形矩阵------Matrix 对于图像的色彩处理,Android系统提供了ColorMatrix ...
- 第六章Android绘图机制与处理技巧(Android群英传)
本章将介绍关于Andorid绘图机制的一些高级技巧与分析 6.1.屏幕的尺寸信息 一般我们以720x1280为标准原稿,那么1dp = 2px 6.2.2D绘图基础 Canvas提供了很多api 如: ...
- Android绘图机制与处理技巧-更新中
概述 这里我们主要来探讨下 Android屏幕的相关只是 Android绘图技巧 Android图像处理技巧 SurfaceView的使用 绘图技巧中,医生讲的比较粗略,更多的细节参考了 Keegan ...
- Android 中View的绘制机制源代码分析 三
到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...
- Android内存泄露和GC机制
Android内存泄露和GC机制 本文先对Android内存垃圾回收机制进行介绍,之后对分析.定位内存泄露常用的测试方法进行总结,分享给大家. 一.Android内存垃圾回收(GC机制) 1.综述 A ...
- Android群英传》读书笔记 (3) 第六章 Android绘图机制与处理技巧 + 第七章 Android动画机制与使用技巧...
第六章 Android绘图机制与处理技巧 1.屏幕尺寸信息 屏幕大小:屏幕对角线长度,单位"寸": 分辨率:手机屏幕像素点个数,例如720x1280分辨率: PPI(Pixels ...
- Android使用学习之绘图(Canvas,Paint)与手势感应及其应用(乒乓球小游戏)
作为一个没有学习Android的菜鸟,最近一直在工作之外努力地学习的Android的使用.这周看了下Android的绘图,主要是Canvas,Paint等,感觉需要实践下,下午正好有空,就想整一个乒乓 ...
- 《OpenGL ES 3.x游戏开发(上卷)》一1.5 Android应用程序运行的机制
本节书摘来异步社区<OpenGL ES 3.x游戏开发(上卷)>一书中的第1章,第1.5节,作者: 吴亚峰 责编: 张涛,更多章节内容可以访问云栖社区"异步社区"公众号 ...
- Android View框架的measure机制
Android中View框架的工作机制中,主要有三个过程: 1.View树的测量(measure)Android View框架的measure机制 http://www.cnblogs.com/xyh ...
最新文章
- 一条推文卖出 290 万美元天价?网友:我竟是隐藏的富翁
- 泰坦尼克号数据_看完泰坦尼克号生还数据之后,你还会认为是妇女和儿童先走吗?...
- C# 位域[flags]
- ubuntu-14.04.2-desktop-i386.iso:ubuntu-14.04.2-desktop-i386:安装Oracle11gR2
- bzoj1190:[HNOI2007]梦幻岛宝珠
- python3 centos7 Python.h无法找到
- 利用CURL修改页面内容
- 逻辑结构的四种基本关系
- 迅为4412开发平台Zigbee模块在物联网智能家居中的应用
- 模板,宏,atuo关键字的简述
- dbm和db的关系与区别
- asp.net网站负载测试
- Preface Numbering序言页码
- openssl-1.0.0 的新增功能与欠缺(一)
- Windows双屏配置(笔记本+外显示器)
- idea右侧没有maven,main方法无启动图标解决方法
- 【转】 HtmlUnit简介
- VC++实现打开文件和打开所在文件夹的功能(附源码)
- 电工操作常识口诀QY-DZ03
- 幼儿园教师怎么教计算机知识,幼儿园教师的专业知识到底包含哪些,家长必知!...
热门文章
- 感谢Adobe,用上了Silverlight RC0版本
- C# 值类型的局限性
- 欢迎来到元宇宙的虚拟世界
- 手把手教你学Dapr - 1. .Net开发者的大时代
- 优化 ASP.NET Core Docker 镜像的大小
- 微软腾讯京东都在高薪招.NET Core,你准备好了吗!
- 服务器重新部署踩坑记
- asp.net core 从 3.1 到 5.0
- 解决 WPF 绑定集合后数据变动界面却不更新的问题(使用 ObservableCollection)
- ​你可能不知道的7个HTML小技巧