画世界pro里的画笔功能很炫酷
其画笔配置可以调节流量,密度,色相,饱和度,亮度等。
他的大部分画笔应该是通过一个笔头图片在触摸轨迹上匀速绘制的原理。
这里提供一个匀速贝塞尔的kotlin实现:
class EvenBezier {private var lastControlPoint: ControllerPoint? = nullprivate var filterWeight = 0.5private var filterWeightInverse = 1 - filterWeight// 偏移量var stepOffset = 0.0// 间距var stepInterval = 5.0private val cValue = 0.33private val cValue2 = 0.1666private val cValue3 = 0.66private 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 = pointstepOffset = stepIntervalreturn extStroke(point)}fun extStroke(point: ControllerPoint): List<ControllerPoint> {val stepPoints = arrayListOf<ControllerPoint>()curRawStroke.add(point)curRawSampledStroke.add(point)val size = curRawStroke.sizeif (size >= 3) {val fPoint = calFilteredPoint(curRawSampledStroke[size-3],curRawSampledStroke[size-2],curRawSampledStroke[size-1],)curFilteredStroke.add(fPoint)}val filteredSize = curFilteredStroke.sizeif (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.sizeif (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.sizeif (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.sizeif (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.sizeif (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 = pt0val p3 = pt1val p0_x = p0.xval p0_y = p0.yval p0_p = p0.pval p3_x = p3.xval p3_y = p3.yval p3_p = p3.pval p1: ControllerPointif (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: ControllerPointif (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 = p2return 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 accessvar p0_x = p0.xvar p0_y = p0.yvar p0_p = p0.p// Algebraic conveniences, not geometricvar A_x = p3.x - 3 * p2.x + 3 * p1.x - p0_xvar A_y = p3.y - 3 * p2.y + 3 * p1.y - p0_yvar A_p = p3.p - 3 * p2.p + 3 * p1.p - p0_pvar B_x = 3 * p2.x - 6 * p1.x + 3 * p0_xvar B_y = 3 * p2.y - 6 * p1.y + 3 * p0_yvar B_p = 3 * p2.p - 6 * p1.p + 3 * p0_pvar C_x = 3 * p1.x - 3 * p0_xvar C_y = 3 * p1.y - 3 * p0_yvar C_p = 3 * p1.p - 3 * p0_pvar t = (i - stepOffset) / sqrt((C_x * C_x + C_y * C_y).toDouble())while (t <= 1.0) {// Pointvar step_x = t * (t * (t * A_x + B_x) + C_x) + p0_xvar step_y = t * (t * (t * A_y + B_y) + C_y) + p0_yvar step_p = t * (t * (t * A_p + B_p) + C_p) + p0_pstepPoints.add(ControllerPoint(step_x.toFloat(),step_y.toFloat(),step_p.toFloat()));// Step distance until next onevar s_x = t * (t * 3 * A_x + 2 * B_x) + C_x // dx/dtvar s_y = t * (t * 3 * A_y + 2 * B_y) + C_y // dy/dtvar s = sqrt(s_x * s_x + s_y * s_y) // s = derivative in 2D spacevar dt = i / s // i = interval / derivative in 2Dt += dt}if (stepPoints.size == 0) // We didn't step at all along this BezierstepOffset += p0.getDistance(p3)elsestepOffset = 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是获取当前点的按压力度,以更好实现笔的特效。
最终的匀速效果:
有瑕疵,点数太少时并不是匀速的。