Domanda

Ho un mazzo di coordinate che sono i punti di controllo di un serrato uniforme cubica B-spline sul piano 2D. Vorrei richiamare questa curva utilizzando chiamate Cairo (in Python, utilizzando binding Python del Cairo), ma per quanto ne so, Il Cairo supporta solo le curve di Bézier. So anche che i segmenti di una B-spline tra due punti di controllo possono essere disegnati utilizzando le curve di Bézier, ma non riesco a trovare le formule esatte ovunque. Date le coordinate dei punti di controllo, come posso derivare i punti di controllo delle corrispondenti curve di Bezier? Esiste un algoritmo efficiente per questo?

È stato utile?

Soluzione

Va bene, così ho cercato un sacco utilizzando Google e penso che mi si avvicinò con una soluzione ragionevole che è adatto per i miei scopi. Sto postando qui -. Forse sarà utile a qualcun altro così

In primo luogo, cominciamo con una semplice classe 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 cubica B-spline non è altro che una collezione di oggetti Point:

class CubicBSpline(object):
    __slots__ = ("points", )

    def __init__(self, points):
        self.points = [Point(*coords) for coords in points]

Ora, si supponga che abbiamo un cubico B-spline aperta uniforme invece di una serrata. Quattro punti di controllo consecutivi di un cubica B-spline definiscono un singolo segmento Bézier, quindi punti di controllo da 0 a 3 definiscono il primo segmento Bézier, punti di controllo 1 a 4 definiscono il secondo segmento e così via. I punti di controllo della spline Bézier possono essere determinati interpolando linearmente tra i punti di controllo della B-spline in modo appropriato. Siano A, B, C e D sia i quattro punti di controllo della B-spline. Calcolare i seguenti punti ausiliari:

  1. Trova il punto che divide la linea A-B in un rapporto di 2: 1, lascia che sia A'
  2. .
  3. Trovare il punto che divide la linea C-D in un rapporto di 1: 2, che sia D'
  4. .
  5. Dividere la linea B-C in tre parti uguali, lasciare che i due punti siano F e G.
  6. Trova la metà punto tra A' e F, questo sarà E.
  7. Trova a metà strada tra il punto G e D', questo sarà H.

Una curva di Bezier da E a H con punti di controllo F e G è equivalente a una B-spline aperta tra i punti A, B, C e D. Vedere le sezioni 1-5 di questo documento eccellente . A proposito, il metodo di cui sopra viene chiamato algoritmo di Böhm, ed è molto più complicato, se formulata in modo corretto matematica che tiene conto non uniformi o non cubici B-spline pure.

Dobbiamo ripetere la procedura per ogni gruppo di 4 punti consecutivi del B-spline, così alla fine dovremo i 1: 1 punti di divisione tra quasi qualsiasi coppia di punti di controllo consecutive: 2 e 2. Questo è ciò che la seguente classe BSplineDrawer fa prima di disegnare le curve:

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()

Infine, se vogliamo disegnare bloccati B-spline invece di B-spline aperte, dobbiamo semplicemente ripetere entrambi gli endpoint del B-spline bloccato altre tre volte:

class CubicBSpline(object):
    [...]
    def clamped(self):
        new_points = [self.points[0]] * 3 + self.points + [self.points[-1]] * 3
        return CubicBSpline(new_points)

Infine, questo è come dovrebbe essere utilizzato il codice:

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)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top