我有一堆坐标,它们是2D平面上固定的均匀三次B样条曲线的控制点。 我想使用Cairo调用绘制此曲线(在Python中,使用Cairo的Python绑定),但是据我所知,Cairo仅支持Bézier曲线。 我也知道可以使用贝塞尔曲线来绘制两个控制点之间的B样条曲线的分段,但是我在任何地方都找不到确切的公式。 给定控制点的坐标,如何导出相应的贝塞尔曲线的控制点? 有什么有效的算法吗?
好的,所以我使用Google搜索了很多东西,我想出了一个适合我目的的合理解决方案。我在这里发布-也许对其他人也有用。
首先,让我们从一个简单的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样条曲线的四个连续控制点定义单个Bézier线段,因此控制点0到3定义第一个Bézier线段,控制点1-4定义第二个Bézier线段,依此类推。 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。
从E到H的Bézier曲线,控制点为F和G,等效于点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)
您是否可以在Ive尝试的Python 3.x中使此代码工作,但是它有一些奇怪的异常现象。 xrange到范围。 此外,右对右括号括起来的one_thirds = [p1,p2成对的p1.interpolate(p2,1/3。)。
我目前没有Python 3.x,但是我认为这些是您需要进行的更改:1)用list(zip(...))替换zip(...),因为我们要遍历列表两次,2)替换与range(),3)使用方括号,您当前会在其中看到一个右圆括号,因为无论如何这都是错字(我现在将编辑我的答案以进行修正)
谢谢,我确实弄清楚了,并使它正常工作。 有用的代码。
将B样条曲线转换为Bezier样条曲线有帮助吗?
@ΤζΩΤ?ΙΟΥ+1谢谢,这有助于我朝正确的方向入手。 有关完整的解决方案和我发现的算法的简化说明,请参见上面的答案。