使用Cairo绘制夹紧的均匀立方B样条

时间:2010-03-28 22:13:25

标签: python cairo bezier

我有一堆坐标,它们是2D平面上夹紧的均匀立方B样条的控制点。我想使用Cairo调用绘制此曲线(在Python中,使用Cairo的Python绑定),但据我所知,Cairo仅支持Bézier曲线。我也知道可以使用Bézier曲线绘制两个控制点之间的B样条曲线,但我无法在任何地方找到精确的公式。给定控制点的坐标,如何导出相应Bézier曲线的控制点?那有什么有效的算法吗?

2 个答案:

答案 0 :(得分:7)

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

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

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样条只不过是Point个对象的集合:

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样条的四个控制点。计算以下辅助点:

  1. 找到以2:1的比例划分A-B线的点,让它成为A'。
  2. 找出以1:2的比例划分C-D线的点,让它为D'。
  3. 将B-C线分成三个相等的部分,让两个点分别为F和G.
  4. 找到A'和F之间的点,这将是E。
  5. 找到G和D'之间的点,这将是H。
  6. 从E到H的Bézier曲线,控制点F和G相当于A,B,C和D点之间的开放B样条。参见this excellent document的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)
    

答案 1 :(得分:3)