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

最终的匀速效果:

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

相关推荐
雨白6 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹8 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空10 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭10 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日11 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安11 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑11 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟15 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡17 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0017 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体