android 特效绘图,Android绘图机制与处理技巧——Android图像处理之图形特效处理...

Android变形矩阵——Matrix

对于图像的图形变换,Android系统是通过矩阵来进行处理的,每个像素点都表达了其坐标的X、Y信息。Android的图形变换矩阵是一个3x3的矩阵,如下图所示:

84b4bbb66136

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的矩阵,如下图所示:

84b4bbb66136

图形变换初始矩阵

图像的变形处理通常包含以下四类基本变换:

Translate——平移变换

Rotate——旋转变换

Scale——缩放变换

Skew——错切变换

平移变换

平移变换的坐标值变换过程就是将每个像素点都进行平移变换,当从P(x0,y0)平移到P(x1,y1)时,所需的平移矩阵如下所示:

84b4bbb66136

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θ

矩阵形式如下图所示:

84b4bbb66136

旋转变换矩阵

前面是以坐标原点为旋转中心的旋转变换,如果以任意点O为旋转中心来进行旋转变换,通常需要以下三个步骤:

1.将坐标原点平移到O点

2.使用前面讲的以坐标原点为中心的旋转方法进行旋转变换

3.将坐标原点还原

缩放变换

一个像素点是不存在缩放的概念的,但是由于图像是由很多个像素点组成的,如果将每个点的坐标都进行相同比例的缩放,最终就会形成让整个图像缩放的效果,缩放效果的公式如下

x1=K1x0

y1=K2y0

矩阵形式如下图所示:

84b4bbb66136

缩放变换矩阵

错切变换

错切变换(skew)在数学上又称为Shear mapping(可译为“剪切变换“)或者Transvection(缩并),它是一种比较特殊的线性变换。错切变换的效果就是让所有点的X坐标(或者Y坐标)保持不变,而对应的Y坐标(或者X坐标)则按比例发生平移,且平移的大小和该点到Y轴(或者X轴)的距离成正比。错切变换通常包含两种——水平错切与垂直错切。

错切变换的计算公式如下:

水平错切

x1=x0+K1y0

y1=y0

垂直错切

x1=x0

y1=K2x0+y0

矩阵形式如下图

84b4bbb66136

错切变换矩阵

由上面的分析可以发现,这个图形变换3x3的矩阵与色彩变换矩阵一样,每个位置的元素所表示的功能是有规律的,总结如下:

84b4bbb66136

矩阵变换规律

可以发现,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;

}

}

}

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/282774.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPF 使用 DrawingContext 绘制刻度条

WPF 使用 DrawingContext 绘制刻度条控件名&#xff1a;Ruler作者&#xff1a;WPFDevelopersOrg原文链接&#xff1a; https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用大于等于.NET40&#xff1b;Visual Studio 2022;项目使用 MIT 开源许可协议&#xff1b;定义I…

个人中心的html,个人中心.html

&#xfeff;个人中心$axure.utils.getTransparentGifPath function() { return resources/images/transparent.gif; };$axure.utils.getOtherPath function() { return resources/Other.html; };$axure.utils.getReloadPath function() { return resources/reload.html; };…

使用CMD命令修改Windows本地账户密码

2019独角兽企业重金招聘Python工程师标准>>> 一、以管理员身份运行cmd命令 二、在命令提示符窗口中输入命令符&#xff1a;net user Administrator 123&#xff0c;然后按回车键“Enter”。(Administrator是你的win8用户名&#xff0c;123是重新设置的密码。) ​ 三…

java线程安全问题原因及解决办法

1.为什么会出现线程安全问题 计算机系统资源分配的单位为进程&#xff0c;同一个进程中允许多个线程并发执行&#xff0c;并且多个线程会共享进程范围内的资源&#xff1a;例如内存地址。当多个线程并发访问同一个内存地址并且内存地址保存的值是可变的时候可能会发生线程安全问…

html语言怎么添加图片,我想问你一下,你是怎么在html中插入本地图片?非常感谢...

满意答案小蜜蜂手工2013.10.03采纳率&#xff1a;43% 等级&#xff1a;12已帮助&#xff1a;7929人img{float:right}在下面的段落中&#xff0c;我们添加了一个样式为 float:right 的图像。结果是这个图像会浮动到段落的右侧。This is some text. This is some text. This i…

EntityFrameworkCore上下文如何实现继承?

【导读】如果我们存在基础设施服务和其他服务&#xff0c;我们会定义属于基础设施服务的上下文以及其他服务的上下文&#xff0c; 而且会独立部署&#xff0c;此时其他服务需要使用基础服务&#xff0c;我们都会暴露基础服务接口给到其他服务调用&#xff0c;这也是常规操作若在…

美观又实用,10 款强大的开源 Javascript 图表库

2019独角兽企业重金招聘Python工程师标准>>> 随着发展&#xff0c;现代 Web 设计在改善体验和功能的同时&#xff0c;对于美观的追求也越来越高&#xff0c;可视化、交互式、动态等元素和效果似乎已成为标配。 以下是为开发者推荐的 10 款开源 Javascript 图表库&am…

EF CORE 7 RC1 发布

原文链接&#xff1a;https://devblogs.microsoft.com/dotnet/announcing-ef7-rc1/[1]原文作者&#xff1a;Jeremy Likness翻译&#xff1a;沙漠尽头的狼(谷歌翻译加持)Entity Framework Core 7 (EF7) Release Candidate 1 已发布&#xff01;该团队专注于解决缺陷、小幅改进以…

微服务太分散?使用Fundebug集中式bug监控

摘要&#xff1a; 微服务日志分散&#xff0c;可以使用Fundebug的异常监控将它们集中起来。 当一个项目复杂到一定程度&#xff0c;功能越来越多&#xff0c;随之对应的模块也越来越多。 如果都放在一个大的项目下面&#xff0c;共同开发&#xff0c;整合发布&#xff0c;那么会…

html404页面怎么添加,网站要如何设置自定义404页面?

之前我们讲述过网站设置404页面对于优化或是用户体验的重要意义&#xff0c;大家可移步到《网站为什么要设置404页面》查看&#xff0c;今天我们讲解的是网站要如何设置自己的404页面。现在大多数空间商都有了404设置的功能&#xff0c;我们可将404页面上传至空间里面&#xff…

ASP.NET Core在.NET 7 RC1中的更新

原文链接&#xff1a;https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-7-rc-1/[1]原文作者&#xff1a;Daniel Roth翻译&#xff1a;沙漠尽头的狼(谷歌翻译加持).NET 7 Release Candidate 1 (RC1) 现已推出[2]&#xff0c;其中包括对 ASP.NET Core 的许…

html5 tab菜单切换页面,11个常用的jQuery TAB切换菜单源码及制作教程

11个常用的jQuery TAB切换菜单源码及制作教程SponsorTAB切换式菜单可以方便为我们减少很多网页布局空间&#xff0c;而且用jQuery的话可以加入一些动画效果&#xff0c;比如渐变&#xff0c;向左右滑动等&#xff0c;提升一定的用户体验&#xff0c;所以TAB菜单目前来说是很流行…

ConcurrentDictionary字典操作竟然不全是线程安全的?

好久不见&#xff0c;马甲哥封闭居家半个月&#xff0c;记录之前遇到的一件小事。ConcurrentDictionary<TKey,TValue>绝大部分api都是线程安全的[1]&#xff0c;唯二的例外是接收工厂函数的api&#xff1a;AddOrUpdate、GetOrAdd&#xff0c;这两个api不是线程安全的&…

HTML中弹窗中加入图片,javascript里怎么实现点击图片弹出对话框?

JavaScript中可以使用document.getElementsByTagName方法后去img标签&#xff0c;然后遍历所有img标签并为其添加点击事件实现点击弹出对话框。JavaScript实现点击图片弹出对话框&#xff1a;img {width: 500px;height: 300px;}//获取所有的img标签var imgObjs document.getEl…

Dcloud课程2 什么是Dcloud

Dcloud课程2 什么是Dcloud 一、总结 一句话总结&#xff1a;DCloud提供了一套快速开发应用的跨平台技术方案。 1、DCloud的产品架构&#xff1f; MUI(H5)HBuilder 2、什么是MUI&#xff1f; 最接近原生体验的移动App的UI框架。 3、什么是H5&#xff1f; html5功能增强标准 二、…

html5 轮询自动刷新数据,后台调用exe,前端定时轮询调用结果

前提使用asp.net core 2.1前端使用vueui使用element-ui前端发送请求用Axios新建asp.net core程序1.jpg修改Index.html{Layout null;}test{{ msg }}发送请求打开记事本// 创建 Vue 实例&#xff0c;得到 ViewModelvar vm new Vue({el: #app,data: {msg: 准备发送请求打开exe},…

洛谷 P2951 [USACO09OPEN]捉迷藏Hide and Seek

题目描述 Bessie is playing hide and seek (a game in which a number of players hide and a single player (the seeker) attempts to find them after which various penalties and rewards are assessed; much fun usually ensues). She is trying to figure out in which…

linux下tomcat开启远程调试

1.center下&#xff0c;在startup.sh文件首行中添加如下语句 declare -x CATALINA_OPTS"-server -Xdebug -Xnoagent -Djava.compilerNONE -Xrunjdwp:transportdt_socket,servery,suspendn,address8000"(不要换行&#xff0c;要在同一行)Ubuntu下&#xff0c;在catali…

.NET 7 RC1 发布

原文链接&#xff1a;https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-rc-1/[1]原文作者&#xff1a;Jeremy Likness&#xff0c;Angelos Petropoulos&#xff0c;Jon Douglas翻译&#xff1a;沙漠尽头的狼(谷歌翻译加持)今天我们宣布 .NET 7 候选版本 1。这是生产…

.NET MAUI实战 FilePicker

1.概要最近在迁移 GeneralUpdate.Tool的时候需要用到文件选择&#xff0c;在MAUI中可以使用FilePicker进行选择。ref1: https://gitee.com/Juster-zhu/GeneralUpdateref2:https://docs.microsoft.com/zh-cn/dotnet/maui/platform-integration/storage/file-picker?tabswindows…