代码之家  ›  专栏  ›  技术社区  ›  Tamás

利用cairo绘制夹紧均匀三次b样条

  •  3
  • Tamás  · 技术社区  · 14 年前

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

    2 回复  |  直到 6 年前
        1
  •  7
  •   Tamás    6 年前

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

    首先,让我们从一个简单的 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样条曲线只不过是 物体:

    class CubicBSpline(object):
        __slots__ = ("points", )
    
        def __init__(self, points):
            self.points = [Point(*coords) for coords in points]
    

    现在,假设我们有一个开放的均匀三次b样条而不是一个固定的b样条。三次b样条曲线的四个连续控制点定义了一个bzier段,因此控制点0到3定义了第一个bzier段,控制点1到4定义了第二个段,依此类推。bzier样条的控制点可以通过在b样条的控制点之间以适当的方式进行线性插值来确定。设a、b、c和d为b样条曲线的四个控制点。计算下列辅助点:

    1. 找出A-B线以2:1的比例分开的点,设为A’。
    2. 找出C-D线以1:2的比例分开的点,设为D’。
    3. 把b-c线分成三等分,让这两点为f和g。
    4. 找到a'和f中间的点,这就是e。
    5. 找到G和D中间的点,这就是H。

    具有控制点F和G的E到H的B_zier曲线相当于点A、B、C和D之间的开放B样条曲线。见第1-5节 this excellent document . 顺便说一下,上述方法被称为b_m算法,如果用适当的数学方法来描述非均匀或非三次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)
    
        2
  •  3
  •   tzot    14 年前