Używam biblioteki obrazowania Pythona i chciałbym narysować kilka krzywych Beziera. Chyba mogę obliczyć piksel po pikselu, ale mam nadzieję, że jest coś prostszego.Jak mogę narysować krzywą Beziera przy użyciu PIL Pythona?
Odpowiedz
Krzywa Beziera nie jest trudna do narysowania. Biorąc pod uwagę trzy punkty: A
, B
, C
potrzebne są trzy liniowe interpolacje w celu narysowania krzywej. Używamy skalarne t
jako parametr dla interpolacji liniowej:
P0 = A * t + (1 - t) * B
P1 = B * t + (1 - t) * C
Ten interpoluje między dwiema krawędziami Utworzyliśmy, EDGE AB i krawędzi BC. Jedyną rzeczą, którą mamy teraz zrobić, aby obliczyć punkt musimy zwrócić się interpolacji pomiędzy P0 i P1 przy użyciu tego samego t tak:
Pfinal = P0 * t + (1 - t) * P1
Istnieje kilka rzeczy, które trzeba zrobić, zanim rzeczywiście narysuj krzywą. Po pierwsze, przejdziemy około dt
(delta t) i musimy mieć świadomość, że 0 <= t <= 1
. Jak moglibyście sobie wyobrazić, nie da to nam gładkiej krzywej, zamiast tego daje tylko dyskretny zestaw pozycji do wykreślenia. Najprostszym sposobem rozwiązania tego problemu jest po prostu narysowanie linii między bieżącym punktem a poprzednim punktem.
Możesz użyć aggdraw na górze PIL, krzywe beziera są supported.
EDIT:
zrobiłem przykład tylko do odkrycia, że jest to błąd w klasie Path
dotyczące curveto
:(
Oto przykład tak czy owak:
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()
This powinny napraw błąd, jeśli masz ochotę na ponowną kompilację modułu ...
+1 dla linku, jak naprawić błąd Aggdraw beziera, szkoda, że powiązania w Pythonie nie zostały zaktualizowane, aby to naprawić. –
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):
# This returns the nth row of Pascal's Triangle
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))
return result
to, na przykład, rysuje serca
from PILL 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')
Choć ścieżki Beziera curveTo nie działają z Aggdraw, jak wspomniano przez @ ToniRuža, istnieje inny sposób to zrobić w Aggdraw. Zaletą korzystania z Aggdraw zamiast z PIL lub z własnych funkcji beziera jest to, że Aggdraw antyaliasuje obraz, sprawiając, że wygląda on gładko (patrz rysunek u dołu).
Aggdraw Symbole
Zamiast aggdraw.Path() klasy rysować, można użyć klasy aggdraw.Symbol(pathstring)
która jest zasadniczo taka sama, z wyjątkiem napisać ścieżkę jako ciąg znaków. Zgodnie z dokumentami Aggdraw sposobem napisania ścieżki jako łańcucha jest użycie składni ścieżki SVG (patrz: http://www.w3.org/TR/SVG/paths.html).Zasadniczo, każdy dodatek (node) do ścieżki zwykle zaczyna
- literą wskazującą działanie zasysające (wielkie litery dla ścieżki bezwzględnej, małymi względnego toru), a następnie (bez przerw pomiędzy nimi)
- x współrzędnych (poprzedzać znakiem minus, jeśli jest to liczba ujemna lub kierunek)
- przecinek
- współrzędna y (poprzedzać znakiem minus, jeśli jest to liczba ujemna lub kierunek)
w twojej ścieżka ciąg po prostu oddzielaj wiele węzłów spacją. Po utworzeniu symbolu pamiętaj, aby go narysować, przekazując go jako jeden z argumentów do draw.symbol(args)
.
Beziera Krzywe symbolami Aggdraw
szczególności do sześciennych krzywych beziera można napisać literę "c" lub "c", a następnie przez 6 cyfr (3 zestawy xy współrzędnych x1, y1, X2, Y2, X3 , y3 z przecinkami pomiędzy liczbami, ale nie między pierwszą literą a literą). Zgodnie z dokumentami istnieją również inne wersje beziera za pomocą litery "S (gładki sześcienny bezier), Q (kwadratowy bezier), T (gładki kwadratowy bezier)". Powyżej znajduje się pełna przykładowy kod (wymaga PIL i aggdraw):
print "initializing script"
# imports
from PIL import Image
import aggdraw
# setup
img = Image.new("RGBA", (1000,1000)) # last part is image dimensions
draw = aggdraw.Draw(img)
outline = aggdraw.Pen("black", 5) # 5 is the outlinewidth in pixels
fill = aggdraw.Brush("yellow")
# the pathstring:
#m for starting point
#c for bezier curves
#z for closing up the path, optional
#(all lowercase letters for relative path)
pathstring = " m0,0 c300,300,700,600,300,900 z"
# create symbol
symbol = aggdraw.Symbol(pathstring)
# draw and save it
xy = (20,20) # xy position to place symbol
draw.symbol(xy, symbol, outline, fill)
draw.flush()
img.save("testbeziercurves.png") # this image gets saved to same folder as the script
print "finished drawing and saved!"
a wyjście jest gładka wyglądający zakrzywione bezier postać:
znalazłem prostszy sposób tworząc krzywą Beziera (bez aggraw i bez złożonych funkcji).
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()
dziękuję za odpowiedź, może skończyć to w końcu. To właśnie miałem na myśli, gdy powiedziałem: "Myślę, że mógłbym obliczyć piksel po pikselu" ... że mógłbym po prostu zrobić matematykę, ale zastanawiałem się, czy można użyć czegoś wbudowanego. – carrier