我有一堆坐标,它们是二维平面上一个被夹紧的均匀三次B样条的控制点。我想要使用Cairo调用(在Python中使用Cairo的Python绑定)来绘制这条曲线,但据我所知,Cairo仅支持贝塞尔曲线。我也知道,在两个控制点之间的B样条段可以使用贝塞尔曲线来绘制,但我找不到确切的公式。给定控制点的坐标,如何推导出相应贝塞尔曲线的控制点?是否有任何高效算法可以实现?
好的,我使用谷歌进行了大量搜索,并且我认为我想出了一个适合我的目的的合理解决方案。我在这里发布它 - 也许对其他人也有用。
首先,让我们从一个简单的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))
Point
对象的集合:class CubicBSpline(object):
__slots__ = ("points", )
def __init__(self, points):
self.points = [Point(*coords) for coords in points]
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)
zip(...)
替换为list(zip(...))
,因为我们将两次迭代列表,2)用range()
替换xrange()
,3)在您当前看到右圆括号的地方使用方括号,因为那是一个错别字(我现在要编辑我的答案来修复它)。 - Tamás