tiempo real punto para nivel matrices librerias graficos graficas graficar dibujar curvas coordenadas como python python-imaging-library bezier imaging

real - ¿Cómo puedo dibujar una curva bezier usando el PIL de Python?



librerias para graficar en python (5)

Estoy usando la Biblioteca de imágenes de Python y me gustaría dibujar algunas curvas de bezier. Creo que podría calcular píxel por píxel, pero espero que haya algo más simple.


Puede usar el aggdraw encima de PIL, las curvas de bezier son compatibles .

EDITAR:

Hice un ejemplo solo para descubrir que hay un error en la clase Path respecto a curveto :(

Aquí está el ejemplo de todos modos:

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

Esto debería arreglar el error si está listo para recompilar el módulo ...


Una curva bezier no es tan difícil de dibujar. Dado tres puntos A , B , C se requieren tres interpolaciones lineales para dibujar la curva. Utilizamos el t escalar como el parámetro para la interpolación lineal:

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

Esto interpola entre dos bordes que hemos creado, el borde AB y el borde BC. Lo único que tenemos que hacer ahora para calcular el punto que tenemos que dibujar es interpolar entre P0 y P1 usando la misma t así que:

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

Hay un par de cosas que deben hacerse antes de que realmente dibujemos la curva. Primero, caminaremos algunos dt (delta t) y debemos ser conscientes de que 0 <= t <= 1 . Como podrán imaginar, esto no nos proporcionará una curva suave, sino que produce solo un conjunto discreto de posiciones para trazar. La forma más fácil de resolver esto es simplemente dibujar una línea entre el punto actual y el punto anterior.


Aunque las curvas Betoier no funcionan con Aggdraw, como lo menciona @ToniRuža, hay otra forma de hacerlo en Aggdraw. El beneficio de usar Aggdraw en lugar de PIL o sus propias funciones de bezier es que Aggdraw antialias la imagen haciendo que se vea más suave (ver foto en la parte inferior).

Símbolos Aggdraw

En lugar de usar la clase aggdraw.Path () para dibujar, puede usar la aggdraw.Symbol(pathstring) que es básicamente la misma excepto que escribe la ruta como una cadena. De acuerdo con los documentos de Aggdraw, la forma de escribir su ruta como una cadena es usar la sintaxis de la ruta SVG (consulte: http://www.w3.org/TR/SVG/paths.html ). Básicamente, cada adición (nodo) a la ruta normalmente comienza con

  • una letra que representa la acción de dibujo (mayúscula para la ruta absoluta, minúscula para la ruta relativa), seguida de (sin espacios intermedios)
  • la coordenada x (precedida por un signo menos si es un número o dirección negativo)
  • una coma
  • la coordenada y (preceder por un signo menos si es un número o dirección negativo)

En su camino, simplemente separe sus múltiples nodos con un espacio. Una vez que haya creado su símbolo, simplemente recuerde dibujarlo pasándolo como uno de los argumentos para draw.symbol(args) .

Curvas de Bezier en símbolos de Aggdraw

Específicamente para las curvas de bezier cúbico escribe la letra "C" o "c" seguida de 6 números (3 conjuntos de coordenadas xy x1, y1, x2, y2, x3, y3 con comas entre los números pero no entre el primer número y la carta). Según los documentos, también hay otras versiones de bezier con la letra "S" (bezier cúbico liso), Q (bezier cuadrático), T (bezier cuadrático liso) ". Aquí hay un código de ejemplo completo (requiere PIL y 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!"

Y la salida es una figura curva bezier de aspecto liso:


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

Esto, por ejemplo, dibuja un corazón:

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


Encontré una manera más simple de crear una curva bezier (sin aggraw y sin funciones complejas).

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