过多点圆滑三阶贝塞尔曲线Java版-闭合和不闭合两种
说明:
path.cubicTo是安卓三阶贝塞尔曲线的路径调用函数,也可以自己画,文章末尾附有n阶贝塞尔曲线的算法实现
一、不闭合实现
效果
代码实现
mappedPoints点集,sharpenRatio:控制系数,可以控制圆滑程度
public static void calculate(List<PointF> mappedPoints, float sharpenRatio, Path path) {
if (mappedPoints.size() < 3) {
throw new IllegalArgumentException("The size of mappedPoints must not less than 3!");
}
// Logger.d(TAG, "sharpenRatio: " + sharpenRatio);
PointF pMidOfLm = new PointF();
PointF pMidOfMr = new PointF();
PointF cache = null;
for (int i = 0; i <= mappedPoints.size() - 3; i++) {
PointF pL = mappedPoints.get(i);
PointF pM = mappedPoints.get(i + 1);
PointF pR = mappedPoints.get(i + 2);
pMidOfLm.x = (pL.x + pM.x) / 2.0f;
pMidOfLm.y = (pL.y + pM.y) / 2.0f;
pMidOfMr.x = (pM.x + pR.x) / 2.0f;
pMidOfMr.y = (pM.y + pR.y) / 2.0f;
float lengthOfLm = (float) Math.hypot(pM.x - pL.x, pM.y - pL.y);
float lengthOfMr = (float) Math.hypot(pR.x - pM.x, pR.y - pM.y);
float ratio = (lengthOfLm / (lengthOfLm + lengthOfMr)) * sharpenRatio;
float oneMinusRatio = (1 - ratio) * sharpenRatio;
// Logger.d(TAG, "ratio:%f, oneMinusRatio:%f.", ratio, oneMinusRatio);
float dx = pMidOfLm.x - pMidOfMr.x;
float dy = pMidOfLm.y - pMidOfMr.y;
PointF cLeft = new PointF();
cLeft.x = pM.x + dx * ratio;
cLeft.y = pM.y + dy * ratio;
PointF cRight = new PointF();
cRight.x = pM.x + -dx * oneMinusRatio;
cRight.y = pM.y + -dy * oneMinusRatio;
if (i == 0) {
PointF pMidOfLCLeft = new PointF((pL.x + cLeft.x) / 2.0f, (pL.y + cLeft.y) / 2.0f);
PointF pMidOfCLeftM = new PointF((cLeft.x + pM.x) / 2.0f, (cLeft.y + pM.y) / 2.0f);
float length1 = (float) Math.hypot(cLeft.x - pL.x, cLeft.y - pL.y);
float length2 = (float) Math.hypot(pM.x - cLeft.x, pM.y - cLeft.y);
ratio = (length1 / (length1 + length2)) * sharpenRatio;
PointF first = new PointF();
first.x = cLeft.x + (pMidOfLCLeft.x - pMidOfCLeftM.x) * ratio;
first.y = cLeft.y + (pMidOfLCLeft.y - pMidOfCLeftM.y) * ratio;
path.cubicTo(first.x, first.y, cLeft.x, cLeft.y, pM.x, pM.y);
} else {
path.cubicTo(cache.x, cache.y, cLeft.x, cLeft.y, pM.x, pM.y);
}
cache = cRight;
if (i == mappedPoints.size() - 3) {
PointF pMidOfMCRight = new PointF((pM.x + cRight.x) / 2.0f, (pM.y + cRight.y) / 2.0f);
PointF pMidOfCRightR = new PointF((pR.x + cRight.x) / 2.0f, (pR.y + cRight.y) / 2.0f);
float length1 = (float) Math.hypot(cRight.x - pM.x, cRight.y - pM.y);
float length2 = (float) Math.hypot(pR.x - cRight.x, pR.y - cRight.y);
ratio = (length2 / (length1 + length2)) * sharpenRatio;
PointF last = new PointF();
last.x = cRight.x + (pMidOfCRightR.x - pMidOfMCRight.x) * ratio;
last.y = cRight.y + (pMidOfCRightR.y - pMidOfMCRight.y) * ratio;
path.cubicTo(cRight.x, cRight.y, last.x, last.y, pR.x, pR.y);
}
// Logger.d(TAG, "cLeft:%s, cRight:%s.", cLeft, cRight);
}
}
二、闭合实现
调试过程中的效果
原必经点基础加头三个点,形成闭合,为什么加三个。。。。嗯。。。。尝试加思考,才得出来的。。。。
注:
图中白色实心点是原始要经过得点
空心点是我把辅助点打出来看看
如果能去掉这两个红色圈????,就完美了
最终效果,随便画,都漂亮!!!!:
代码实现
这里我用kotlin了,和上面java一样,注意点有两个
划重点!!!划重点!!划重点!!
1、原点基础加三个点:
mappedPoints.add(mappedPoints[0])、 mappedPoints.add(mappedPoints[1])、mappedPoints.add(mappedPoints[2])
2、去掉一轮的起点和二轮的末点,也就是Java代码中i=0和i=mappedPoints.size() - 3去掉既可,实际是去掉两个终点处的尖角点-------------------此处是重点,查了好多资料,都实现不了,最后自己多次尝试思考用这个最简单的办法解决了,自己画画就知道了,刚好多余这两条线形成尖角
kotlin代码实现如下
注意:去掉后path得移到这两个位置得添加,不然画偏了:
path.moveTo(pM.x, pM.y);path.moveTo(pR.x, pR.y)
private fun calculatePassCurve(mappedPoints: List<PointF>, sharpenRatio: Float, path: Path, canvas: Canvas) {
require(mappedPoints.size >= 3) { "The size of mappedPoints must not less than 3!" }
val pMidOfLm = PointF()
val pMidOfMr = PointF()
var cache: PointF? = null
for (i in 0..mappedPoints.size - 3) {
val pL = mappedPoints[i]
val pM = mappedPoints[i + 1]
val pR = mappedPoints[i + 2]
pMidOfLm.x = (pL.x + pM.x) / 2.0f
pMidOfLm.y = (pL.y + pM.y) / 2.0f
pMidOfMr.x = (pM.x + pR.x) / 2.0f
pMidOfMr.y = (pM.y + pR.y) / 2.0f
val lengthOfLm = hypot((pM.x - pL.x).toDouble(), (pM.y - pL.y).toDouble()).toFloat()
val lengthOfMr = hypot((pR.x - pM.x).toDouble(), (pR.y - pM.y).toDouble()).toFloat()
var ratio = lengthOfLm / (lengthOfLm + lengthOfMr) * sharpenRatio
val oneMinusRatio = (1 - ratio) * sharpenRatio
val dx = pMidOfLm.x - pMidOfMr.x
val dy = pMidOfLm.y - pMidOfMr.y
val cLeft = PointF()
cLeft.x = pM.x + dx * ratio
cLeft.y = pM.y + dy * ratio
val cRight = PointF()
cRight.x = pM.x + -dx * oneMinusRatio
cRight.y = pM.y + -dy * oneMinusRatio
if (i == 0) {
path.moveTo(pM.x, pM.y)
} else {
path.cubicTo(cache!!.x, cache.y, cLeft.x, cLeft.y, pM.x, pM.y)
canvas.drawPath(mPath, mPaint)
}
cache = cRight
if (i == mappedPoints.size - 3) {
path.moveTo(pR.x, pR.y)
}
}
}
小插曲
我的点是随机的,调试中间出现了个萌萌熊,让苦笑实现不了闭合圆滑的我,莫名喜感
上图,给大家看看
三、彩蛋篇----n 阶贝塞尔曲线点生成算法
/**
* deCasteljau算法
* p(i,j) = (1-t) * p(i-1,j) + t * p(i-1,j+1);
*
* @param i 阶数 4
* @param j 控制点 3
* @param t 时间
* @return
*/
private fun deCasteljauX(i: Int, j: Int, t: Float): Float {
if (i == 1) {
return (1 - t) * mControlPoints[j].x + t * mControlPoints[j + 1].x;
}
return (1 - t) * deCasteljauX(i - 1, j, t) + t * deCasteljauX(i - 1, j + 1, t);
}
/**
* deCasteljau算法
*
* @param i 阶数
* @param j 第几个点
* @param t 时间
* @return
*/
private fun deCasteljauY(i: Int, j: Int, t: Float): Float {
if (i == 1) {
return (1 - t) * mControlPoints[j].y + t * mControlPoints[j + 1].y;
}
return (1 - t) * deCasteljauY(i - 1, j, t) + t * deCasteljauY(i - 1, j + 1, t);
}
//返回贝塞尔点,可以直接连接这些点画
fun buildBezierPoints(controlPoints: ArrayList<PointF>): ArrayList<PointF> {
mControlPoints = controlPoints
var points = ArrayList<PointF>()
var tempPoints = ArrayList<PointF>()
var order = mControlPoints.size - 1 //阶数
//画的密集度,帧
var delta = 1.0f / 1000
var t = 0f
while (t < 1) {
t += delta
// Bezier点集
var pointF = PointF(deCasteljauX(order, 0, t), deCasteljauY(order, 0, t))
points.add(pointF)
}
return points
}