Frage

Ich verwende Imaging Library Python und ich möchte einige Bezier-Kurven zeichnen. Ich glaube, ich Pixel für Pixel berechnen könnte, aber ich bin der Hoffnung, es ist etwas einfacher.

War es hilfreich?

Lösung

Eine Bezierkurve ist nicht so schwer, sich zu ziehen. Gegeben drei Punkte A, B, C Sie benötigen drei lineare Interpolation, um die Kurve zu zeichnen. Wir verwenden die skalare t als Parameter für die lineare Interpolation:

P0 = A * t + (1 - t) * B
P1 = B * t + (1 - t) * C

Diese interpoliert zwischen zwei Kanten die wir erstellt haben, Kante AB und Kante BC. Das einzige, was wir jetzt tun müssen, um den Punkt berechnen wir ziehen müssen, ist wie so zwischen P0 und P1 mit dem gleichen t interpolieren:

Pfinal = P0 * t + (1 - t) * P1

Es gibt ein paar Dinge, die getan werden müssen, bevor wir tatsächlich die Kurve ziehen. Zunächst einmal haben wir werden einige dt (delta t) gehen und wir müssen das 0 <= t <= 1 bewusst sein. Wie Sie sich vielleicht vorstellen können, wird uns das nicht eine glatte Kurve geben, sondern es ergibt sich nur eine diskrete Menge von Positionen, an denen plotten. Der einfachste Weg, dies zu lösen, ist einfach eine Linie zwischen dem aktuellen Punkt zu ziehen und dem vorherigen Punkt.

Andere Tipps

def make_bezier(xys):
    # xys should be a sequence of 2-tuples (Bezier control points)
    n = len(xys)
    combinations = pascal_row(n-1)
    def bezier(ts):
        # This uses the generalized formula for bezier curves
        # http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Generalization
        result = []
        for t in ts:
            tpowers = (t**i for i in range(n))
            upowers = reversed([(1-t)**i for i in range(n)])
            coefs = [c*a*b for c, a, b in zip(combinations, tpowers, upowers)]
            result.append(
                tuple(sum([coef*p for coef, p in zip(coefs, ps)]) for ps in zip(*xys)))
        return result
    return bezier

def pascal_row(n, memo={}):
    # This returns the nth row of Pascal's Triangle
    if n in memo:
        return memo[n]
    result = [1]
    x, numerator = 1, n
    for denominator in range(1, n//2+1):
        # print(numerator,denominator,x)
        x *= numerator
        x /= denominator
        result.append(x)
        numerator -= 1
    if n&1 == 0:
        # n is even
        result.extend(reversed(result[:-1]))
    else:
        result.extend(reversed(result))
    memo[n] = result
    return result

Dies ist zum Beispiel, zeichnet ein Herz:

from PIL import Image
from PIL import ImageDraw

if __name__ == '__main__':
    im = Image.new('RGBA', (100, 100), (0, 0, 0, 0)) 
    draw = ImageDraw.Draw(im)
    ts = [t/100.0 for t in range(101)]

    xys = [(50, 100), (80, 80), (100, 50)]
    bezier = make_bezier(xys)
    points = bezier(ts)

    xys = [(100, 50), (100, 0), (50, 0), (50, 35)]
    bezier = make_bezier(xys)
    points.extend(bezier(ts))

    xys = [(50, 35), (50, 0), (0, 0), (0, 50)]
    bezier = make_bezier(xys)
    points.extend(bezier(ts))

    xys = [(0, 50), (20, 80), (50, 100)]
    bezier = make_bezier(xys)
    points.extend(bezier(ts))

    draw.polygon(points, fill = 'red')
    im.save('out.png')

Sie können über den aggdraw auf der PIL, Bezier-Kurven sind unterstützt .

EDIT:

Ich habe nur ein Beispiel zu entdecken, es ist ein Fehler in der Path Klasse in Bezug auf curveto ist: (

Hier ist das Beispiel sowieso:

from PIL import Image
import aggdraw

img = Image.new("RGB", (200, 200), "white")
canvas = aggdraw.Draw(img)

pen = aggdraw.Pen("black")
path = aggdraw.Path()
path.moveto(0, 0)
path.curveto(0, 60, 40, 100, 100, 100)
canvas.path(path.coords(), path, pen)
canvas.flush()

img.save("curve.png", "PNG")
img.show()

Diese sollte den Fehler beheben, wenn Sie‘ wieder nach oben für das Modul neu zu kompilieren ...

Obwohl Bezier curveto Pfade nicht mit Aggdraw arbeiten, wie erwähnt von @ ToniRuža, gibt es eine andere Möglichkeit, dies in Aggdraw zu tun. Der Vorteil der Verwendung Aggdraw statt PIL oder eigenen Bezier-Funktionen ist, dass Aggdraw das Bild Antialiasing wird macht es glatter aussehen (Bild unten sehen).

Aggdraw Symbole

Statt die aggdraw.Path () der Klasse der Verwendung zu ziehen, können Sie die aggdraw.Symbol(pathstring)-Klasse verwenden, das ist im Grunde die gleiche, außer Sie den Pfad als String schreiben. Nach dem Aggdraw docs die Art und Weise Ihren Weg zu schreiben, wie ein String ist SVG Pfadsyntax zu verwenden (siehe:

Ich fand einen einfacheren Weg, um eine Bezier-Kurve zu schaffen (ohne aggraw und ohne komplexe Funktionen).

import math
from PIL import Image
from PIL import ImageDraw

image = Image.new('RGB',(1190,841),'white')
draw = ImageDraw.Draw(image)
curve_smoothness = 100

#First, select start and end of curve (pixels)
curve_start = [(167,688)] 
curve_end = [(678,128)]

#Second, split the path into segments
curve = [] 
for i in range(1,curve_smoothness,1):
    split = (curve_end[0][0] - curve_start[0][0])/curve_smoothness
    x = curve_start[0][0] + split * i 
    curve.append((x, -7 * math.pow(10,-7) * math.pow(x,3) - 0.0011 * math.pow(x,2) + 0.235 * x + 682.68))

#Third, edit any other corners of polygon
other =[(1026,721), (167,688)]

#Finally, combine all parts of polygon into one list
xys = curve_start + curve + curve_end + other #putting all parts of the polygon together
draw.polygon(xys, fill = None, outline = 256)

image.show()
scroll top