如何根据给定的点集计算平滑路径的控制点?

14

我正在使用UIBezierPath,但是这个问题涉及路径的控制点,而不是绘图。给定一组点,我可以渲染一条路径。然而,我一直未能弄清楚如何计算控制点以获得像照片曲线编辑器中那样的平滑线条(如何在UIKit中实现Photoshop曲线编辑器)。

我看到的最接近的答案在这里:如何跟踪手指在触摸时绘制平滑曲线?

然而,我仍然无法理解正确的计算方法。简单的说,就是:

for (int i = 0; i< points; i++) 
{
     ...

     [path addQuadCurveToPoint:nextPoint controlPoint:WTF];
}

5
沉默如此之大,以至于它令人痛苦。 - akaru
您想让曲线恰好通过您给定的每个点吗?还是您希望更平滑的曲线仅接近于某些给定点? - rob mayoff
@robmayoff 如果它是一个简单得多的操作,那么关闭就足够了。 - akaru
如果有帮助的话,大多数色调曲线往往使用样条曲线插值来操作值。 - akaru
2个回答

13
你提供的图片示例并不使用二次贝塞尔曲线,因此我将参考该图片而非代码。
在 iOS(和 OS X)中,贝塞尔路径基本上是绘图命令和点的列表。例如:
[path moveTo:CGMakePoint(1,1)];
[path curveToPoint:(10,10) controPoint1:(3,7) controlPoint2:(4,1)];
[path curveToPoint:(10,10) controPoint1:(15,17) controlPoint2:(21,11)];
[path closePath];

结果为:

moveto (1,1)      
curveto (10,10) (3,7) (4,1) 
curveto (20,0) (15,17) (21,11)    
closepath 

贝塞尔曲线上的控制点控制了从某一点出发的曲线方向和速率。第一个控制点(cp)控制了从前一个点出发的曲线方向和速率,第二个控制点控制了到达的点的曲线方向和速率。对于二次曲线(使用addQuadCurveToPoint:controlPoint:得到的结果),这两个点是相同的,您可以在此方法的文档here中看到。

沿一组点得到平滑曲线的关键是使cp1和cp2共线,并且该线与该段两端的点平行。

Annotated curve

这将看起来像:

[path moveTo:2];
[path curveTo:3 controlPoint1:cp1 controlPoint2:cp2];

通过选择一些恒定的线段长度并进行一些几何运算(我现在忘记了所有线性方程,但它们很容易在谷歌搜索中找到),可以计算出cp1和cp2。

在代码中进入2->3线段时,应该已经为2->3(cp2)计算了控制点。接下来要解决的问题是使曲线从2->3线段平滑地过渡到3->4线段。根据之前的恒定线段长度(这将控制您得到多么平滑的曲线),可以通过计算一个与图中点2->3(cp2)和点3共线的点来为3->4计算一个cp1。然后计算一个3->4(cp2),它与3->4(cp1)共线并且与点3和点4形成的直线平行。重复此过程直到遍历完整个点数组。


1
谢谢您的回复。不幸的是,阅读了这些链接后,我仍然无法确定正确的方法。我怪我的大脑。 - akaru
没有办法编写计算控制点的代码。 - Abdul Yasin

4

我不确定这会有多大帮助,但我曾经实现过类似的功能,为笔记在这个应用程序(www.app.net/hereboy)中创建一条曲线路径。基本上,它是一个具有三个曲线的路径。

为了实现这个功能,我为每个曲线创建了4个点:起始点、结束点和两个控制点,分别位于25%和75%的位置。

以下是我编写的代码:

//create points along the keypath for curve.
CGMutablePathRef curvedPath = CGPathCreateMutable();
const int TOTAL_POINTS = 3;
int horizontalWiggle = 15;

int stepChangeX = (endPoint.x - viewOrigin.x) / TOTAL_POINTS;
int stepChangeY = (endPoint.y - viewOrigin.y) / TOTAL_POINTS;

for(int i = 0; i < TOTAL_POINTS; i++) {
    int startX = (int)(viewOrigin.x + i * stepChangeX);
    int startY = (int)(viewOrigin.y + i * stepChangeY);

    int endX = (int)(viewOrigin.x + (i+1) * stepChangeX);
    int endY = (int)(viewOrigin.y + (i+1) * stepChangeY);

    int cpX1 = (int)(viewOrigin.x + (i+0.25) * stepChangeX);
    if((i+1)%2) {
        cpX1 -= horizontalWiggle;
    } else {
        cpX1 += horizontalWiggle;
    }
    int cpY1 = (int)(viewOrigin.y + (i+0.25) * stepChangeY);

    int cpX2 = (int)(viewOrigin.x + (i+0.75) * stepChangeX);
    if((i+1)%2) {
        cpX2 -= horizontalWiggle;
    } else {
        cpX2 += horizontalWiggle;
    }
    int cpY2 = (int)(viewOrigin.y + (i+0.75) * stepChangeY);

    CGPathMoveToPoint(curvedPath, NULL, startX, startY);
    CGPathAddCurveToPoint(curvedPath, NULL, cpX1, cpY1, cpX2, cpY2, endX, endY);
}

祝你好运!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接