Android下的匀速贝塞尔

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

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

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

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

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里进行相应的调用即可。

Kotlin 复制代码
    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是获取当前点的按压力度,以更好实现笔的特效。

最终的匀速效果:

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

相关推荐
Estar.Lee2 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh3 小时前
uiautomator案例
android
工业甲酰苯胺4 小时前
MySQL 主从复制之多线程复制
android·mysql·adb
少说多做3434 小时前
Android 不同情况下使用 runOnUiThread
android·java
Estar.Lee5 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯6 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey7 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!9 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟10 小时前
Android音频采集
android·音视频
小白也想学C11 小时前
Android 功耗分析(底层篇)
android·功耗