仿Windows画板喷漆笔刷效果
基于Java
代码实现,并附有相应的Kotlin
版本
原创文章,转载请联系作者
软草平莎过雨新,轻沙走马路无尘。
何时收拾耦耕身?
先上效果图:
笔刷项目地址在此,大家要是喜欢的话,不妨来点个赞吧
效果解析
因为最终要实现的是windwos
下的画板喷漆笔刷,所以首先要对它做一个较为详细的效果解析。考虑到笔一般情况下笔刷的使用点,故此会分析一下点和线的效果细节。
- 画点
从左至右依次是对同一坐标点击2次,点击8次,点击16次的效果展示;
当数量趋向更大时,点的密集程度并没有很明显的偏向,基本可以确定要在圆内均匀分布
- 画线
如图为匀速且缓慢滑过时,由点构成线
具体实现
项目的大致框架由View
、BasePen
,两个大的模块构成。其中View
属于UI层面,BasePen
属于业务逻辑层面。接下来,将一一介绍这两个模块的具体功用和细节。
View
此项目的承载View为PenView
,不承担业务逻辑,就是起到一个容器的作用。在PenView
中唯一的作用就是触发invalidate()
方法。
private BasePen mBasePen;@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);if (w != 0 && h != 0) {if (mBasePen == null) {mBasePen = new SprayPen(w, h);}}}@Overridepublic boolean onTouchEvent(MotionEvent event) {MotionEvent event1 = MotionEvent.obtain(event);mBasePen.onTouchEvent(event1);switch (event.getActionMasked()) {case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_MOVE:invalidate();break;case MotionEvent.ACTION_UP:break;}return true;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);mBasePen.onDraw(canvas);}
复制代码
具体的业务逻辑,绘制、数据计算、触摸点移动Move等,全都由BasePen
以及它的子类来实现了。
低耦合性,代表着更多的自由度,对现有项目代码(如果应用到项目中)的冲击更小。在性能方面,如果View
满足不了要求,可以用更小的代价将其移植到性能更好的SurfaceView
里去。
业务逻辑
业务方面,BasePen
作为基类,承担了一些基础的数据计算、绘制等功能,而具体的画笔效果则交由子类实现。
先看看BasePen
里做了什么:
- 绘制
private List<Point> mPoints;
public void onDraw(Canvas canvas) {if (mPoints != null && !mPoints.isEmpty()) {canvas.drawBitmap(mBitmap, 0, 0, null);drawDetail(canvas);}}
复制代码
先将笔刷绘制到一张Bitmap
之上,再将这张Bitmap
交给PenView
来绘制出来。Point
是一个只记录了x和y坐标的类。
drawDetail(Canvas canvas)
是一个抽象类,由子类实现具体的绘制。
- 滑动轨迹 在
BasePen
的onTouchEvent(MotionEvent event1)
方法里。以每次DOWN
事件为开始,记录MOVE
内的所有坐标信息。考虑到喷漆效果基本不用处理笔锋效果,暂不考虑记录UP
信息(后续如果实现其他笔刷效果会优化这里)。
public void onTouchEvent(MotionEvent event1) {switch (event1.getActionMasked()) {case MotionEvent.ACTION_DOWN:clearPoints();handlePoints(event1);break;case MotionEvent.ACTION_MOVE:handlePoints(event1);break;case MotionEvent.ACTION_UP:break;}}private void handlePoints(MotionEvent event1) {float x = event1.getX();float y = event1.getY();if (x > 0 && y > 0) {mPoints.add(new Point(x, y));}}private void clearPoints() {if (mPoints == null) {return;}mPoints.clear();}
复制代码
- 喷漆实现
protected void drawDetail(Canvas canvas) {if (getPoints().isEmpty()) {return;}mTotalNum = 由自定义粒子密度以及画笔宽度计算而来drawSpray(当前最新坐标点.x, 当前最新坐标点.y, mTotalNum);}private void drawSpray(float x, float y, int totalNum) {for (int i = 0; i < totalNum; i++) {//算法计算出圆内随机点float[] randomPoint = getRandomPoint(x, y, mPenW, true);mCanvas.drawCircle(randomPoint[0], randomPoint[1], mCricleR, mPaint);}}
复制代码
以上是一部分伪代码,
SprayPen
内部定义了一个喷漆粒子密度,会根据画笔的宽度来实时改变粒子数量。每个粒子的半径则由外部依赖的组件提供的width
计算而来。
在drawDetail(...)
方法内,每一次MOVE
和DOWN
事件都会在相应坐标处,绘制一定数目的圆内随机点。
当其串联起来时,就形成了喷漆效果。当然这只是初步完成,还有一些算法需要完善。伪代码表述不全,可参考SprayPen,在代码中有比较完善的注释。
接下来会说一些有关喷漆算法方面的问题。
喷漆算法的几个问题
在实现功能的过程中,有两个问题是值得记录的。
一是圆内均匀随机点的分布问题;二是滑动速度快时,笔画的连接处理问题。
如何均匀的在圆内生成随机点
为了解决这个问题,主要尝试了三种方法:
x在(-R,R)范围内随机取值,由圆解析式
求解得y。然后对y在(-y,y)内随机取值,得到的点即为圆内点。同理,也可由y计算出x。
java代码如下:
float x = mRandom.nextInt(r);
float y = (float) Math.sqrt(Math.pow(r, 2) - Math.pow(x, 2));
y = mRandom.nextInt((int) y);
x = 对值随机取正负(x);
y = 对值随机取正负(y);
复制代码
最终呈现效果如下:
当样本数量达到2000时,形状如上所示
可以很明显的看到,在x轴方向,左右两端的密集程度明显高于圆心
随机值在大量数据下会具有规律性,可以理解为当数据很多时,x的取值在(-r,r)大致为均匀分布的,y的取值亦是。当处于左右两端时,y的取值范围变小,视觉效果就显得紧凑了些。
当然如果用概率论数理统计公式来验证会更有说服力,但可惜不会。。。(耸肩)
随机角度,在[0,360)内随机取得角度,然后在[0,r]范围内随机取值,然后使用sin
和cos
来求解x和y。
java代码如下:
float[] ints = new float[2];
int degree = mRandom.nextInt(360);
double curR = mRandom.nextInt(r)+1;
float x = (float) (curR * Math.cos(Math.toRadians(degree)));
float y = (float) (curR * Math.sin(Math.toRadians(degree)));
x = 对值随机取正负(x);
y = 对值随机取正负(y);
复制代码
最终呈现效果如下:
明显看到中心处的密集程度高于边缘地带,事实上当角度固定时,r在[0,R)范围内随机取值。当数量更大时,坐标点是均匀分布的。
当r越小时,所占用的面积越小,就会显得粒子很密集。
随机角度,在[0,360)内随机取得角度,取[0,1]内的随机平方根再和R相乘,然后使用sin
和cos
来求解x和y。
java代码如下:
int degree = mRandom.nextInt(360);
double curR = Math.sqrt(mRandom.nextDouble()) * r;
float x = (float) (curR * Math.cos(Math.toRadians(degree)));
float y = (float) (curR * Math.sin(Math.toRadians(degree)));
x = 对值随机取正负(x);
y = 对值随机取正负(y);
复制代码
最终呈现效果如下:
这次的视觉效果总算是达到了均匀的效果,这个算法是利用了一个根函数的特性,如下图:
红色是根函数,蓝色是线性函数。两者相比下来,根函数的取值会更大些,相应的,接近边缘的点就会更多一点,让粒子的分布效果更加均衡。
处理“奋笔疾书”情况
当以比较慢的速度滑动时,笔画尚显流畅无明显断层。当速度过快时,MOVE
留下的点更少,且间距大。会出现画笔断层现象,这时候就需要一些特殊的处理方法。
代码中设定了一个标准值D
,这个值是由BasePen
所持有的w和h两个值计算而来的,一般来说,这两个值期望为依附的View
的宽高。最初也考虑使用画笔的直径计算,但考虑到画笔直径是可以外部动态改变的。标准值最好保持一定的独立性,其所依赖的数据越稳定越好,要不然会影响平衡
。然后当MOVE
时,当前点距离上一个点的相对距离大于这个标准值D
时,就会判定此时处于快移速状态,间距越大移速越快,那么喷漆效果相应地就要减弱【直观而言就是粒子浓度要低】。
快移速状态时,代码会在当前点和上一个点之间,模拟出一些笔迹点。相应地,这些笔迹点的粒子密集度会低一些,其计算函数且是一个反驼峰的变化状态。即连续笔迹点的中间点粒子最稀疏,两边则最密集。
//手速过快时
float stepDis = mPenR * 1.6f;
//笔迹点的数量
int v = (int) (getLastDis() / stepDis);
float gapX = getPoints().get(getPoints().size() - 1).x - getPoints().get(getPoints().size() - 2).x;
float gapY = getPoints().get(getPoints().size() - 1).y - getPoints().get(getPoints().size() - 2).y;
//描绘笔迹点
for (int i = 1; i <= v; i++) {float x = (float) (getPoints().get(getPoints().size() - 2).x + (gapX * i * stepDis / getLastDis()));float y = (float) (getPoints().get(getPoints().size() - 2).y + (gapY * i * stepDis / getLastDis()));drawSpray(x, y, (int) (mTotalNum * calculate(i, 1, v)), mRandom.nextBoolean());}
/*** 使用(x-(min+max)/2)^2/(min-(min+max)/2)^2作为粒子密度比函数*/private static float calculate(int index, int min, int max) {float maxProbability = 0.6f;float minProbability = 0.15f;if (max - min + 1 <= 4) {return maxProbability;}int mid = (max + min) / 2;int maxValue = (int) Math.pow(mid - min, 2);float ratio = (float) (Math.pow(index - mid, 2) / maxValue);if (ratio >= maxProbability) {return maxProbability;} else if (ratio <= minProbability) {return minProbability;} else {return ratio;}}
复制代码
Kotlin
本项目在写的时候,顺便也写了一个Kotlin
版本的。注意,并不是用AS自带的代码转换的。所以Kotlin
版本会有很多不必要的测试体验代码,不要在意这些细节。
Kotlin版本这里这里,喜欢的不妨点个赞吧
总结
以上就是本次Demo
的思路、以及一些算法的解析。数学之美,令人沉醉*(数学学渣留下了悔恨的泪水。。。)*
数学才是本体啊
笔刷项目地址在此,代码中的注释会更加清晰些,大家要是喜欢的话,不妨来点个赞吧
有欢迎关注我的公众号,技术与生活
参考资料:
- 均匀的生成圆和三角形内的随机点
仿Windows画板喷漆笔刷效果相关推荐
- 基于.net之仿Windows画板设计
基于.net之仿Windows画板设计 队 长:周 洋 小组成员:周寅莹 袁晓旭 江春鹏 蒋彬含 朱振宇 屈生辉 万里骏 彭子航 指导老师:余敦辉 所在班级:湖北大学计算机科学与技术2016级 摘要: ...
- 图形学应用_着色器实例—笔刷效果
笔刷效果的实现 最终效果: 片元着色器代码: Shader "Hidden/Brush" {Properties{_MainTex("MainTex",2D)= ...
- 墨迹笔刷效果怎么制作?
在观看一些古装电视剧或者电影的时候,经常会在其中看到墨迹笔刷的效果,所以今天就为大家简单介绍一下怎么使用Vegas制作墨迹笔刷效果. 墨迹笔刷开场制作方法: 步骤1:打开视频制作软件,新建三个视频轨道 ...
- [OpenGL] 植被的动画和笔刷效果
资源来自Unreal商店 植被的渲染 植被的模型是通过建模导入得到的,本身是由多个面片组成的. 整个植被包含了albedo贴图+法线贴图+mask贴图. 我们使用一张mask贴图来完成透明测试,丢弃额 ...
- 简单的笔刷效果【OpenGL】
(字写得还不如小学生=_=b)
- HatchBrush笔刷效果
System.Drawing.Drawing2D. HatchBrush _BlackBrush = new HatchBrush(HatchStyle.Percent50, Color.Blue,C ...
- ItemTouchHelper实现拖拽笔刷效果-part1
因项目开发,需要实现一个数据列表的滑动删除,排序的功能,在网络上找了相关的资料,最后使用ItemTouchHelper实现了RecyclerView的拖动排序以及滑动删除. 运行效果图如下: 参考文章 ...
- 高端金毛金箔毛笔PS金属笔刷板绘笔刷合集,效果不错~
高端金毛金箔毛笔PS金属笔刷板绘笔刷 效果十分的高端,反正这金光闪闪的很好看! 是不是很棒的资源呢 分享给大家,喜欢的帮忙点个赞呀~ 下载地址:https://mdl.ink/H0qrEf
- 笔刷怎么做_原来是这样:用PS笔刷做出颜料肌理效果!
题图插画 | TX灼灼 " 当我意识到可以利用PS的某些工具, 设置不同的画笔之后, 一切都变了. 有些看起来很不合逻辑的组合, 最后产生的效果却是逼真的惊人, 再结合合适的形状就能创作出特 ...
- procreate 笔刷_Procreate新手漫画入门:笔刷,图层,上色
上个月新入手了一个新的ipad,又打开了一种关于漫画的新的可能性~同时验证了那句话:对生活保持好奇,你将收获更多. 于是就有一些喜欢画画的小伙伴有私信这样的漫画怎么画的? 这个秘密工具就是:ipad ...
最新文章
- android获取连接wifi名称,android 获取当前连接WIFI名称的有关问题
- Hello World With JBoss Modules
- 结合源码深入理解Android Crash处理流程
- Android开发之adb命令安装apk的问题
- SpringCloud:学习Docker安装zookeeper,注册服务
- zabbix常用key和自定义key的讲解
- MySQL基础篇(06):事务管理,锁机制案例详解
- 北京 || Java 技术、生活、工作交流社区
- iframe design=on 时,oncontextmeun不能触发之问题!
- 【备忘】一段用于在论坛上插入Flash内容的JavaScript代码
- 智能优化算法应用:基于麻雀搜索算法PID参数优化 - 附代码
- 数字化赋能全零售 国美按下战略加速键
- ASUS BIOS开启CPU虚拟化
- ASPECT RATIO
- 如何从PayPal提现
- 5G将给普通人,带来哪些黄金红利期?
- 全民开发者时代到来!华为云开发者日深圳站成功举办
- 排序方法基本介绍(1)
- Python+pandas分离Excel数据到同一个Excel文件中多个Worksheets
- office2016增强版注册
热门文章
- 数据分析--数据的分组和聚合
- 活动图中创建泳道(UML2活动框图创建泳道图例和操作流程)
- linux版本qq,QQLinux版下载-QQ for Linux下载v2.0.0 最新版-西西软件下载
- php中根据数字月份返回月份的英文缩写
- tumblr_使用CSS网格重新设计基于卡片的Tumblr布局
- 简历职称 计算机,个人简历专业技术职务怎么填 就是你所学的专业技术是你取得...
- 批处理命令--call和start
- 笔记本电脑接上hdmi后 笔记本无声音
- 有道云笔记·协作android版,【每天一品】有道云笔记协作
- linux中pingpong测试程序的解读