Рисование закрепленного равномерного кубического B-сплайна с помощью Cairo - PullRequest
3 голосов
/ 29 марта 2010

У меня есть набор координат, которые являются контрольными точками фиксированного равномерного кубического B-сплайна на плоскости 2D. Я хотел бы нарисовать эту кривую, используя вызовы Cairo (в Python, используя привязки Cairo's Python), но, насколько я знаю, Cairo поддерживает только кривые Безье. Я также знаю, что отрезки B-сплайна между двумя контрольными точками можно нарисовать с помощью кривых Безье, но я нигде не могу найти точные формулы. Учитывая координаты контрольных точек, как я могу получить контрольные точки соответствующих кривых Безье? Есть ли эффективный алгоритм для этого?

Ответы [ 2 ]

7 голосов
/ 29 марта 2010

Хорошо, я много искал с помощью 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-сплайна определяют один сегмент Безье, поэтому контрольные точки с 0 по 3 определяют первый сегмент Безье, контрольные точки с 1 по 4 определяют второй сегмент и так далее. Контрольные точки сплайна Безье могут быть определены путем линейной интерполяции между контрольными точками 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.

Кривая Безье от E до H с контрольными точками F и G эквивалентна открытому B-сплайну между точками A, B, C и D. См. Разделы 1-5 этого превосходного документа . Между прочим, вышеупомянутый метод называется алгоритмом Бёма, и он намного сложнее, если сформулирован надлежащим математическим способом, который учитывает также неоднородные или не кубические B-сплайны.

Мы должны повторить вышеописанную процедуру для каждой группы из 4 последовательных точек B-сплайна, поэтому в итоге нам понадобятся точки деления 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)
3 голосов
/ 29 марта 2010
...