Android下的匀速贝塞尔

画世界pro里的画笔功能很炫酷

其画笔配置可以调节流量,密度,色相,饱和度,亮度等。

他的大部分画笔应该是通过一个笔头图片在触摸轨迹上匀速绘制的原理。

这里提供一个匀速贝塞尔的kotlin实现:

class EvenBezier {
    private var lastControlPoint: ControllerPoint? = null
    private var filterWeight = 0.5
    private var filterWeightInverse = 1 - filterWeight
    // 偏移量
    var stepOffset = 0.0
    // 间距
    var stepInterval = 5.0

    private val cValue = 0.33
    private val cValue2 = 0.1666
    private val cValue3 = 0.66


    private val curRawStroke = arrayListOf<ControllerPoint>()
    private val curRawSampledStroke = arrayListOf<ControllerPoint>()
    private val curFilteredStroke = arrayListOf<ControllerPoint>()

    fun begin(point: ControllerPoint): List<ControllerPoint> {
        curRawStroke.clear()
        curRawSampledStroke.clear()
        curFilteredStroke.clear()
        lastControlPoint = point
        stepOffset = stepInterval
        return extStroke(point)
    }

    fun extStroke(point: ControllerPoint): List<ControllerPoint> {
        val stepPoints = arrayListOf<ControllerPoint>()
        curRawStroke.add(point)
        curRawSampledStroke.add(point)
        val size = curRawStroke.size
        if (size >= 3) {
            val fPoint = calFilteredPoint(
                curRawSampledStroke[size-3],
                curRawSampledStroke[size-2],
                curRawSampledStroke[size-1],
            )
            curFilteredStroke.add(fPoint)
        }
        val filteredSize = curFilteredStroke.size
        if (filteredSize >= 3) {
            val list = createBezier(
                curFilteredStroke[filteredSize - 3],
                curFilteredStroke[filteredSize - 2],
                curFilteredStroke[filteredSize - 1],
            )
            stepPoints.addAll(list)
        }
        return stepPoints
    }

    private fun calFilteredPoint(p1: ControllerPoint, p2: ControllerPoint, p3: ControllerPoint): ControllerPoint {
        val m = p1.getMidPoint(p3)
        return ControllerPoint(
            (filterWeight * p2.x + filterWeightInverse * m.x).toFloat(),
            (filterWeight * p2.y + filterWeightInverse * m.y).toFloat(),
            (filterWeight * p2.p + filterWeightInverse * m.p).toFloat(),
        )
    }

    fun endStroke(point: ControllerPoint): List<ControllerPoint> {
        LogUtils.d("endStroke--->")
        val stepPoints = arrayListOf<ControllerPoint>()
        curRawStroke.add(point)
        curRawSampledStroke.add(point)
        val size = curRawSampledStroke.size
        if (size >= 3) {
            val fPoint = calFilteredPoint(
                curRawSampledStroke[size-3],
                curRawSampledStroke[size-2],
                curRawSampledStroke[size-1],
            )
            curFilteredStroke.add(fPoint)
        } else {
            LogUtils.d("sample size: $size")
        }

        val filteredSize = curFilteredStroke.size
        if (filteredSize >=3) {
            val list = createBezier(
                curFilteredStroke[filteredSize - 3],
                curFilteredStroke[filteredSize - 2],
                curFilteredStroke[filteredSize - 1],
            )
            stepPoints.addAll(list)
        } else {
            LogUtils.d("sample filteredSize: $filteredSize")
        }

        curRawStroke.add(point)
        curRawSampledStroke.add(point)
        val size1 = curRawSampledStroke.size
        if (size1 >= 3) {
            val fPoint = calFilteredPoint(
                curRawSampledStroke[size1-3],
                curRawSampledStroke[size1-2],
                curRawSampledStroke[size1-1],
            )
            curFilteredStroke.add(fPoint)
        } else {
            LogUtils.d("sample size1: $size1")
        }

        val filteredSize1 = curFilteredStroke.size
        if (filteredSize1 >=3) {
            val list = createBezier(
                curFilteredStroke[filteredSize1 - 3],
                curFilteredStroke[filteredSize1 - 2],
                curFilteredStroke[filteredSize1 - 1],
            )
            stepPoints.addAll(list)
        } else {
            LogUtils.d("sample filteredSize1: $filteredSize1")
        }
        return stepPoints
    }

    private fun createBezier(pt0: ControllerPoint, pt1: ControllerPoint,
                     pt2: ControllerPoint? = null): List<ControllerPoint> {
        val p0 = pt0
        val p3 = pt1
        val p0_x = p0.x
        val p0_y = p0.y
        val p0_p = p0.p
        val p3_x = p3.x
        val p3_y = p3.y
        val p3_p = p3.p
        val p1: ControllerPoint
        if (lastControlPoint == null) {
            p1 = ControllerPoint(
                (p0_x + (p3_x - p0_x)*cValue).toFloat(),
                (p0_y + (p3_y - p0_y)*cValue).toFloat(),
                (p0_p + (p3_p - p0_p)*cValue).toFloat(),
            )
        } else {
            p1 = lastControlPoint!!.getMirroredPoint(p0)
        }
        var p2: ControllerPoint
        if (pt2 != null) {
            p2 = ControllerPoint(
                (p3_x - (((p3_x - p0_x) + (pt2.x - p3_x)) * cValue2)).toFloat(),
                (p3_y - (((p3_y - p0_y) + (pt2.y - p3_y)) * cValue2)).toFloat(),
                (p3_p - (((p3_p - p0_p) + (pt2.p - p3_p)) * cValue2)).toFloat()
            )
        } else {
            p2 = ControllerPoint(
                (p0_x + (p3_x - p0_x) * cValue3).toFloat(),
                (p0_y + (p3_y - p0_y) * cValue3).toFloat(),
                (p0_p + (p3_p - p0_p) * cValue3).toFloat()
            )
        }
        lastControlPoint = p2
        return calStepPoints(p0, p1, p2, p3)
    }

    private fun calStepPoints(p0: ControllerPoint, p1: ControllerPoint, p2: ControllerPoint,
                      p3: ControllerPoint): List<ControllerPoint> {
        val stepPoints = arrayListOf<ControllerPoint>()
        var i = stepInterval

        // Value access
        var p0_x = p0.x
        var p0_y = p0.y
        var p0_p = p0.p

        // Algebraic conveniences, not geometric
        var A_x = p3.x - 3 * p2.x + 3 * p1.x - p0_x
        var A_y = p3.y - 3 * p2.y + 3 * p1.y - p0_y
        var A_p = p3.p - 3 * p2.p + 3 * p1.p - p0_p
        var B_x = 3 * p2.x - 6 * p1.x + 3 * p0_x
        var B_y = 3 * p2.y - 6 * p1.y + 3 * p0_y
        var B_p = 3 * p2.p - 6 * p1.p + 3 * p0_p
        var C_x = 3 * p1.x - 3 * p0_x
        var C_y = 3 * p1.y - 3 * p0_y
        var C_p = 3 * p1.p - 3 * p0_p

        var t = (i - stepOffset) / sqrt((C_x * C_x + C_y * C_y).toDouble())
        while (t <= 1.0) {
            // Point
            var step_x = t * (t * (t * A_x + B_x) + C_x) + p0_x
            var step_y = t * (t * (t * A_y + B_y) + C_y) + p0_y
            var step_p = t * (t * (t * A_p + B_p) + C_p) + p0_p
            stepPoints.add(ControllerPoint(
                step_x.toFloat(),
                step_y.toFloat(),
                step_p.toFloat()
            ));
            // Step distance until next one
            var s_x = t * (t * 3 * A_x + 2 * B_x) + C_x // dx/dt
            var s_y = t * (t * 3 * A_y + 2 * B_y) + C_y // dy/dt
            var s = sqrt(s_x * s_x + s_y * s_y) // s = derivative in 2D space
            var dt = i / s // i = interval / derivative in 2D
            t += dt
        }
        if (stepPoints.size == 0) // We didn't step at all along this Bezier
            stepOffset += p0.getDistance(p3)
        else
            stepOffset = stepPoints.last().getDistance(p3)
        return stepPoints
    }
}

在画笔的onTouch里进行相应的调用即可。

    private fun touchDown(e: MotionEvent) {
        mBezier.stepInterval = getStepSpace()
        mPointList.clear()
        val list = mBezier.begin(ControllerPoint(e.x, e.y, getPressValue(e)))
        mHWPointList.addAll(list)
    }

    private fun touchMove(e: MotionEvent) {
        val list = mBezier.extStroke(ControllerPoint(e.x, e.y, getPressValue(e)))
        mPointList.addAll(list)
    }

    private fun touchUp(e: MotionEvent) {
        val list = mBezier.endStroke(ControllerPoint(e.x, e.y, getPressValue(e)))
        mPointList.addAll(list)
    }

mPointList就是个匀速贝塞尔的点集合。getPressValue是获取当前点的按压力度,以更好实现笔的特效。

最终的匀速效果:

有瑕疵,点数太少时并不是匀速的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SD卡RAW故障解析与数据恢复全攻略

一、SD卡RAW现象解析 SD卡作为现代电子设备中常见的存储介质&#xff0c;其稳定性和可靠性直接关系到我们日常工作和生活的数据安全。然而&#xff0c;有时我们会遇到SD卡突然变成RAW格式的情况&#xff0c;这通常意味着SD卡的文件系统出现了严重的问题&#xff0c;导致无法正…

Python基础介绍 —— 使用pytest进行测试!

​编辑自动化测试 1319 篇文章62 订阅 订阅专栏 Pytest 是 Python 的一种单元测试框架&#xff0c;与 Python 自带的 unittest 测试框架类似&#xff0c;但是比 unittest 框架使用起来更简洁&#xff0c;效率更高。 Pytest 是一个成熟的全功能的 Python 测试工具&#xff0c;…

在VSCode中怎么配置Python开发环境?真的超简单!

前言&#xff1a;VS Code 里是不包括 Python 的&#xff0c;所以你首先得安装一个 Python。 1、终端运行 Python 安装完 python 之后&#xff0c;我们可以用任何一个文本编辑工具开始写 python 代码&#xff0c;然后在 cmd 中运行代码。 在 VS Code 中&#xff0c;在不安装任…

idea maven 项目融合

背景 &#xff1a;项目A 和项目B 是两个独立的多模块项目&#xff0c;项目A 和项目B &#xff0c;均为独立的数据源 。其中项目B 有两个数据原。 需要将项目B 以多模块的方式融合进项目A。 解决版本。建立项目C&#xff0c;只含有pom的&#xff0c;空项目&#xff0c;项目A和项…

Springboot 整合Mybatis 实现增删改查(二)

续上篇&#xff1a;Springboot整合Mybatis的详细案例图解分析-CSDN博客 mapper层&#xff08;StudentMapper&#xff09; //通过id查询student方法Student searchStudentById(int id);//通过id删除student方法int deleteStudentById(int id);//通过id增加student方法int inser…

文件批量管理利器,一键复制备份安全删除原文件,让文件管理更高效!

在数字化时代&#xff0c;我们每天都在与各种文件打交道&#xff0c;从文档、图片到视频、音频&#xff0c;文件的管理和存储变得越来越重要。然而&#xff0c;手动逐个处理文件不仅繁琐&#xff0c;还容易出错。那么&#xff0c;有没有一种方法可以让我们轻松实现文件的批量管…

如何提高Verilog代码编写水平?

在IC设计端的诸多岗位中&#xff0c;只要提到基础知识和必备技能&#xff0c;就一定少不了Verilog。 按照20年芯片设计老兵的说法“1. 知道module的基本框架。2. 知道怎么写assign&#xff0c;和always块。3. 其他没有了。” 也就是说用VerilogHDL做设计不要追求花架子&#…

鸿蒙开发系列教程(二十五)--样式处理(一)

1、样式属性 参考网址&#xff1a;https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-universal-attributes-text-style-0000001427902436-V3 属性方法以 . 链式调用的方式配置系统组件的样式和其他属性 Entry Component struct Index {build() …

由浅到深认识Java语言(1):前提概要

该文章Github地址&#xff1a;https://github.com/AntonyCheng/java-notes 在此介绍一下作者开源的SpringBoot项目初始化模板&#xff08;Github仓库地址&#xff1a;https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址&#xff1a;https://blog.c…

linux常用命令指南

什么是Linux命令&#xff1f; Linux命令是在Linux操作系统中用于执行特定任务的命令行工具。它们被用于管理文件和目录、执行程序、配置系统设置等。Linux命令通常由一个命令名称和一些选项或参数组成&#xff0c;并且可以通过命令行界面&#xff08;CLI&#xff09;或脚本文件…

C语言例3-24:赋值表达式的例子

代码如下&#xff1a; #include<stdio.h> int main(void) {int i1,j;float f2.0f;// printf("fi-1 :%0.1f\n",fi-1); //ff(i-1)2.0// printf("ii>f :%d\n",ii>f); //i>f -->0 -->i0// printf("j!(i1) :%d\n",j…

智能ai文生视频,文生动漫小程序,系统搭建开发

目录 前言&#xff1a; 一、文生动漫系统搭建常规步骤 二、文生漫画是怎么操作的 总结&#xff1a; 前言&#xff1a; 小说推文是继短视频之后的又一个黄金赛道&#xff0c;它最大的特点就是&#xff0c;有一个人观看了你推荐的小说就有一份收益。那么使用系统小说转漫功能…

AI换脸软件rope最新更新的蓝宝石中文版下载

rope换脸软件蓝宝石版下载地址&#xff1a;点击下载 最近AI软件非常的火爆&#xff0c;今天就给大家带来一个可以AI替换人脸的工具rope&#xff0c;得益于机器学习技术的不断发展&#xff0c;rope经过深度神经网络的无数次迭代优化&#xff0c;最终得出的模型可以自动学习和识…

八大排序算法

排序算法 排序的概述排序的分类分为5大类&#xff1a;优点及缺点如何选择排序算法 八种排序之间的关系:一、插入排序直接插入排序动图详解代码实现 希尔排序动图详解代码实现 二、交换排序冒泡排序:动图详解代码实现 快速排序:动图详解代码实现 三、选择排序直接选择排序动图详…

Echo服务器学习__01(基础)

ASIO是一个跨平台&#xff0c;主要用于实现异步网络和其他一些底层I/O操作的C库 可以基于ASIO实现Echo服务端&#xff0c;在这之前&#xff0c;学习一些基础的知识和概念 ​ 1&#xff1a;IO多路复用 简单的来说&#xff0c;一个线程同时监听多个I/O事件就是I/O多路复用。任…

边缘计算+WEB端应用融合:AI行为识别智能监控系统搭建指南 -- 边缘设备图像识别及部署(二)

专栏目录 边缘计算WEB端应用融合&#xff1a;AI行为识别智能监控系统搭建指南 – 整体介绍&#xff08;一&#xff09; 边缘计算WEB端应用融合&#xff1a;AI行为识别智能监控系统搭建指南 -- 边缘图像识别及部署&#xff08;二&#xff09; 前言边缘图像识别与推流整体思路原始…

【人工智能】Gitee AI 天数智芯有奖体验开源AI模型,一定能有所收货,快来体验吧

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读小5的系列文章。 这是《人工智能》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 目录 前言两大赛道天数智芯1.模型地址2.天数智芯专区3.选择模型4.模型详情页5.部署模型6.成功部署7.执行例子8.移除模型 千模盲…

Flutter-仿淘宝京东录音识别图标效果

效果 需求 弹起键盘&#xff0c;录制按钮紧挨着输入框收起键盘&#xff0c;录制按钮回到初始位置 实现 第一步&#xff1a;监听键盘弹起并获取键盘高度第二步&#xff1a;根据键盘高度&#xff0c;录制按钮高度计算偏移高度&#xff0c;并动画移动第三步&#xff1a;键盘收起…

Unbuntu20.04 git push和pull相关问题

文章目录 Unbuntu20.04 git push和pull使用&#xff11;&#xff0e;下载[Git工具包](https://git-scm.com/downloads)&#xff12;&#xff0e;建立本地仓库&#xff13;&#xff0e;将本地仓库与github远程仓库关联&#xff14;&#xff0e;将本地仓库文件上传到github远程仓…