[英]Drawing a clamped uniform cubic B-spline using Cairo
I have a bunch of coordinates which are the control points of a clamped uniform cubic B-spline on the 2D plane. 我有一堆坐标,它们是2D平面上夹紧的均匀立方B样条的控制点。 I would like to draw this curve using Cairo calls (in Python, using Cairo's Python bindings), but as far as I know, Cairo supports Bézier curves only.
我想使用Cairo调用绘制此曲线(在Python中,使用Cairo的Python绑定),但据我所知,Cairo仅支持Bézier曲线。 I also know that the segments of a B-spline between two control points can be drawn using Bézier curves, but I can't find the exact formulae anywhere.
我也知道可以使用Bézier曲线绘制两个控制点之间的B样条曲线,但我无法在任何地方找到精确的公式。 Given the coordinates of the control points, how can I derive the control points of the corresponding Bézier curves?
给定控制点的坐标,如何导出相应Bézier曲线的控制点? Is there any efficient algorithm for that?
那有什么有效的算法吗?
Okay, so I searched a lot using Google and I think I came up with a reasonable solution that is suitable for my purposes. 好的,所以我用Google搜索了很多,我想我想出了一个适合我目的的合理解决方案。 I'm posting it here - maybe it will be useful to someone else as well.
我在这里张贴它 - 也许它对其他人也有用。
First, let's start with a simple Point
class: 首先,让我们从一个简单的
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))
A cubic B-spline is nothing more than a collection of Point
objects: 立方B样条只不过是
Point
对象的集合:
class CubicBSpline(object):
__slots__ = ("points", )
def __init__(self, points):
self.points = [Point(*coords) for coords in points]
Now, assume that we have an open uniform cubic B-spline instead of a clamped one. 现在,假设我们有一个开放的均匀立方B样条而不是夹紧的。 Four consecutive control points of a cubic B-spline define a single Bézier segment, so control points 0 to 3 define the first Bézier segment, control points 1 to 4 define the second segment and so on.
三次B样条的四个连续控制点定义单个Bézier段,因此控制点0到3定义第一个Bézier段,控制点1到4定义第二个段,依此类推。 The control points of the Bézier spline can be determined by linearly interpolating between the control points of the B-spline in an appropriate way.
Bézier样条的控制点可以通过以适当的方式在B样条的控制点之间线性插值来确定。 Let A, B, C and D be the four control points of the B-spline.
设A,B,C和D为B样条的四个控制点。 Calculate the following auxiliary points:
计算以下辅助点:
A Bézier curve from E to H with control points F and G is equivalent to an open B-spline between points A, B, C and D. See sections 1-5 of this excellent document . 从E到H的Bézier曲线与控制点F和G相当于A,B,C和D点之间的开放B样条。参见本优秀文件的 1-5节。 By the way, the above method is called Böhm's algorithm, and it is much more complicated if formulated in a proper mathematic way that accounts for non-uniform or non-cubic B-splines as well.
顺便说一句,上述方法被称为Böhm算法,如果用适当的数学方法表示,它也会更加复杂,这也解释了非均匀或非立方B样条。
We have to repeat the above procedure for each group of 4 consecutive points of the B-spline, so in the end we will need the 1:2 and 2:1 division points between almost any consecutive control point pairs. 我们必须对B样条的每组4个连续点重复上述过程,因此最后我们将需要几乎任何连续控制点对之间的1:2和2:1分割点。 This is what the following
BSplineDrawer
class does before drawing the curves: 这是绘制曲线之前的以下
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()
Finally, if we want to draw clamped B-splines instead of open B-splines, we simply have to repeat both endpoints of the clamped B-spline three more times: 最后,如果我们想要绘制夹紧的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)
Finally, this is how the code should be used: 最后,这是代码应该如何使用:
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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.