基于Java代码实现,并附有相应的Kotlin版本
原创文章,转载请联系作者

软草平莎过雨新,轻沙走马路无尘。
何时收拾耦耕身?

先上效果图:

笔刷项目地址在此,大家要是喜欢的话,不妨来点个赞吧

效果解析

因为最终要实现的是windwos下的画板喷漆笔刷,所以首先要对它做一个较为详细的效果解析。考虑到笔一般情况下笔刷的使用点,故此会分析一下线的效果细节。

  • 画点

从左至右依次是对同一坐标点击2次,点击8次,点击16次的效果展示;
当数量趋向更大时,点的密集程度并没有很明显的偏向,基本可以确定要在圆内均匀分布

  • 画线

如图为匀速且缓慢滑过时,由点构成线

具体实现

项目的大致框架由ViewBasePen,两个大的模块构成。其中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)是一个抽象类,由子类实现具体的绘制。

  • 滑动轨迹 在BasePenonTouchEvent(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(...)方法内,每一次MOVEDOWN事件都会在相应坐标处,绘制一定数目的圆内随机点。
当其串联起来时,就形成了喷漆效果。当然这只是初步完成,还有一些算法需要完善。伪代码表述不全,可参考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]范围内随机取值,然后使用sincos来求解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相乘,然后使用sincos来求解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所持有的wh两个值计算而来的,一般来说,这两个值期望为依附的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画板喷漆笔刷效果相关推荐

  1. 基于.net之仿Windows画板设计

    基于.net之仿Windows画板设计 队 长:周 洋 小组成员:周寅莹 袁晓旭 江春鹏 蒋彬含 朱振宇 屈生辉 万里骏 彭子航 指导老师:余敦辉 所在班级:湖北大学计算机科学与技术2016级 摘要: ...

  2. 图形学应用_着色器实例—笔刷效果

    笔刷效果的实现 最终效果: 片元着色器代码: Shader "Hidden/Brush" {Properties{_MainTex("MainTex",2D)= ...

  3. 墨迹笔刷效果怎么制作?

    在观看一些古装电视剧或者电影的时候,经常会在其中看到墨迹笔刷的效果,所以今天就为大家简单介绍一下怎么使用Vegas制作墨迹笔刷效果. 墨迹笔刷开场制作方法: 步骤1:打开视频制作软件,新建三个视频轨道 ...

  4. [OpenGL] 植被的动画和笔刷效果

    资源来自Unreal商店 植被的渲染 植被的模型是通过建模导入得到的,本身是由多个面片组成的. 整个植被包含了albedo贴图+法线贴图+mask贴图. 我们使用一张mask贴图来完成透明测试,丢弃额 ...

  5. 简单的笔刷效果【OpenGL】

    (字写得还不如小学生=_=b)

  6. HatchBrush笔刷效果

    System.Drawing.Drawing2D. HatchBrush _BlackBrush = new HatchBrush(HatchStyle.Percent50, Color.Blue,C ...

  7. ItemTouchHelper实现拖拽笔刷效果-part1

    因项目开发,需要实现一个数据列表的滑动删除,排序的功能,在网络上找了相关的资料,最后使用ItemTouchHelper实现了RecyclerView的拖动排序以及滑动删除. 运行效果图如下: 参考文章 ...

  8. 高端金毛金箔毛笔PS金属笔刷板绘笔刷合集,效果不错~

    高端金毛金箔毛笔PS金属笔刷板绘笔刷 效果十分的高端,反正这金光闪闪的很好看! 是不是很棒的资源呢 分享给大家,喜欢的帮忙点个赞呀~ 下载地址:https://mdl.ink/H0qrEf

  9. 笔刷怎么做_原来是这样:用PS笔刷做出颜料肌理效果!

    题图插画 | TX灼灼 " 当我意识到可以利用PS的某些工具, 设置不同的画笔之后, 一切都变了. 有些看起来很不合逻辑的组合, 最后产生的效果却是逼真的惊人, 再结合合适的形状就能创作出特 ...

  10. procreate 笔刷_Procreate新手漫画入门:笔刷,图层,上色

    上个月新入手了一个新的ipad,又打开了一种关于漫画的新的可能性~同时验证了那句话:对生活保持好奇,你将收获更多. 于是就有一些喜欢画画的小伙伴有私信这样的漫画怎么画的? 这个秘密工具就是:ipad ...

最新文章

  1. android获取连接wifi名称,android 获取当前连接WIFI名称的有关问题
  2. Hello World With JBoss Modules
  3. 结合源码深入理解Android Crash处理流程
  4. Android开发之adb命令安装apk的问题
  5. SpringCloud:学习Docker安装zookeeper,注册服务
  6. zabbix常用key和自定义key的讲解
  7. MySQL基础篇(06):事务管理,锁机制案例详解
  8. 北京 || Java 技术、生活、工作交流社区
  9. iframe design=on 时,oncontextmeun不能触发之问题!
  10. 【备忘】一段用于在论坛上插入Flash内容的JavaScript代码
  11. 智能优化算法应用:基于麻雀搜索算法PID参数优化 - 附代码
  12. 数字化赋能全零售 国美按下战略加速键
  13. ASUS BIOS开启CPU虚拟化
  14. ASPECT RATIO
  15. 如何从PayPal提现
  16. 5G将给普通人,带来哪些黄金红利期?
  17. 全民开发者时代到来!华为云开发者日深圳站成功举办
  18. 排序方法基本介绍(1)
  19. Python+pandas分离Excel数据到同一个Excel文件中多个Worksheets
  20. office2016增强版注册

热门文章

  1. 数据分析--数据的分组和聚合
  2. 活动图中创建泳道(UML2活动框图创建泳道图例和操作流程)
  3. linux版本qq,QQLinux版下载-QQ for Linux下载v2.0.0 最新版-西西软件下载
  4. php中根据数字月份返回月份的英文缩写
  5. tumblr_使用CSS网格重新设计基于卡片的Tumblr布局
  6. 简历职称 计算机,个人简历专业技术职务怎么填 就是你所学的专业技术是你取得...
  7. 批处理命令--call和start
  8. 笔记本电脑接上hdmi后 笔记本无声音
  9. 有道云笔记·协作android版,【每天一品】有道云笔记协作
  10. linux中pingpong测试程序的解读