Pregunta

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

¿Fue útil?

Solución

Una curva bezier no es tan difícil de dibujar. Dados tres puntos A, B, C necesita tres interpolaciones lineales para dibujar la curva. Utilizamos el escalar t como 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 de esta manera:

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

Hay un par de cosas que deben hacerse antes de dibujar la curva. En primer lugar, caminaremos algunos dt (delta t) y debemos ser conscientes de que 0 <= t <= 1. Como puede imaginar, esto no nos dará una curva suave, sino que solo dará 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.

Otros consejos

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

Esto, por ejemplo, dibuja un corazón:

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

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

EDIT:

Hice un ejemplo solo para descubrir que hay un error en la clase Path con 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 corregir el error si usted ' listo para recompilar el módulo ...

Aunque las curvas bezier curveto no funcionan con Aggdraw, como lo menciona @ToniRu & # 382; a, hay otra forma de hacer esto en Aggdraw. El beneficio de usar Aggdraw en lugar de PIL o sus propias funciones de Bezier es que Aggdraw antialias la imagen para 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 clase aggdraw.Symbol(pathstring) que es básicamente la misma, excepto que escribe la ruta como una cadena. Según los documentos de Aggdraw, la forma de escribir su ruta como una cadena es utilizar la sintaxis de 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 (precedida por un signo menos si es un número o dirección negativo)

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

Curvas de Bezier en símbolos Aggdraw

Específicamente para curvas bezier cúbicas, escribe la letra " C " o " c " seguido 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 letra). Según los documentos, también hay otras versiones de Bezier utilizando la letra & Quot; S (bezier cúbico liso), Q (bezier cuadrático), T (bezier cuadrático liso) & Quot ;. 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 bezier curva de aspecto suave: Resultado del script anterior usando el símbolo de curva aggdraw bezier

Encontré una forma más simple de crear una curva bezier (sin agrandar 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()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top