Python 用Cairo绘制夹紧均匀三次B样条

Python 用Cairo绘制夹紧均匀三次B样条,python,cairo,bezier,Python,Cairo,Bezier,我有一组坐标,它们是二维平面上固定的均匀三次B样条的控制点。我想使用Cairo调用(在Python中,使用Cairo的Python绑定)绘制这条曲线,但据我所知,Cairo只支持Bézier曲线。我还知道,两个控制点之间的B样条曲线的线段可以使用Bézier曲线绘制,但我在任何地方都找不到精确的公式。给定控制点的坐标,如何导出相应Bézier曲线的控制点?有什么有效的算法吗?确实有用?好的,所以我用谷歌搜索了很多,我想我找到了一个合理的解决方案,适合我的目的。我把它贴在这里——也许它对其他人也有

我有一组坐标,它们是二维平面上固定的均匀三次B样条的控制点。我想使用Cairo调用(在Python中,使用Cairo的Python绑定)绘制这条曲线,但据我所知,Cairo只支持Bézier曲线。我还知道,两个控制点之间的B样条曲线的线段可以使用Bézier曲线绘制,但我在任何地方都找不到精确的公式。给定控制点的坐标,如何导出相应Bézier曲线的控制点?有什么有效的算法吗?

确实有用?

好的,所以我用谷歌搜索了很多,我想我找到了一个合理的解决方案,适合我的目的。我把它贴在这里——也许它对其他人也有用

首先,让我们从一个简单的
点开始
类:

from collections import namedtuple

class Point(namedtuple("Point", "x y")):
    __slots__ = ()

    def interpolate(self, other, ratio = 0.5):
        return Point(x = self.x * (1.0-ratio) + other.x * float(ratio), \
                     y = self.y * (1.0-ratio) + other.y * float(ratio))
三次B样条曲线不过是
对象的集合:

class CubicBSpline(object):
    __slots__ = ("points", )

    def __init__(self, points):
        self.points = [Point(*coords) for coords in points]
现在,假设我们有一个开放的均匀三次B样条,而不是一个固定的。三次B样条曲线的四个连续控制点定义单个Bézier线段,因此控制点0到3定义第一个Bézier线段,控制点1到4定义第二个线段,依此类推。Bézier样条的控制点可以通过在B样条的控制点之间以适当的方式进行线性插值来确定。设A、B、C和D为B样条曲线的四个控制点。计算以下辅助点:

  • 找到将A-B线以2:1的比例分开的点,让它成为A'
  • 找到将C-D线以1:2的比例分开的点,让它为D’
  • 将B-C线分成三等分,让两点分别为F和G
  • 找到A'和F中间的点,这就是E
  • 找到G和D'中间的点,这就是H
  • 具有控制点F和G的从E到H的Bézier曲线相当于点A、B、C和D之间的开放B样条曲线。请参见第1-5节。顺便说一句,上述方法称为Böhm算法,如果采用适当的数学方法来描述非均匀或非三次B样条曲线,则该方法要复杂得多

    我们必须对B样条曲线的每组4个连续点重复上述步骤,因此最终我们将需要几乎任何连续控制点对之间的1:2和2:1分割点。这是以下
    BSplineDrawer
    类在绘制曲线之前所做的操作:

    class BSplineDrawer(object):
        def __init__(self, context):
            self.ctx = context
    
        def draw(self, bspline):
            pairs = zip(bspline.points[:-1], bspline.points[1:])
            one_thirds = [p1.interpolate(p2, 1/3.) for p1, p2 in pairs]
            two_thirds = [p2.interpolate(p1, 1/3.) for p1, p2 in pairs]
    
            coords = [None] * 6
            for i in xrange(len(bspline.points) - 3):
                start = two_thirds[i].interpolate(one_thirds[i+1])
                coords[0:2] = one_thirds[i+1]
                coords[2:4] = two_thirds[i+1]
                coords[4:6] = two_thirds[i+1].interpolate(one_thirds[i+2])
    
                self.context.move_to(*start)
                self.context.curve_to(*coords)
                self.context.stroke()
    
    最后,如果要绘制固定B样条曲线而不是开放B样条曲线,只需将固定B样条曲线的两个端点再重复三次:

    class CubicBSpline(object):
        [...]
        def clamped(self):
            new_points = [self.points[0]] * 3 + self.points + [self.points[-1]] * 3
            return CubicBSpline(new_points)
    
    最后,代码应该如何使用:

    import cairo
    
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 600, 400)
    ctx = cairo.Context(surface)
    
    points = [(100,100), (200,100), (200,200), (100,200), (100,400), (300,400)]
    spline = CubicBSpline(points).clamped()
    
    ctx.set_source_rgb(0., 0., 1.)
    ctx.set_line_width(5)
    BSplineDrawer(ctx).draw(spline)
    

    @谢谢,这有助于我朝着正确的方向开始。关于完整的解决方案和我找到的算法的简化描述,请参见我上面的答案。您能让这段代码在Python 3.x中工作吗?我已经尝试过了,但它有一些奇怪的异常。从一个范围到另一个范围。另外,三分之一=[p1.interpolate(p2,1/3.)对于p1,p2成对使用右括号)会抛出一个错误。我现在没有Python 3.x,但我认为您需要做以下更改:1)将
    zip(…)
    替换为
    list(zip(…)
    因为我们将在列表上迭代两次,2)将
    xrange()
    替换为
    range()
    ,3)在当前看到右括号结尾的地方使用方括号,因为这是一个打字错误(我现在要编辑我的答案来修复它)谢谢,我确实找到了它并使其正常工作。有用的代码。