Question

J'utilise la bibliothèque d'images de Python et j'aimerais dessiner des courbes de Bézier. Je suppose que je pourrais calculer pixel par pixel mais j'espère qu'il y a quelque chose de plus simple.

Était-ce utile?

La solution

Une courbe de Bézier n’est pas si difficile à dessiner vous-même. Avec trois points A, B, C vous avez besoin de trois interpolations linéaires pour tracer la courbe. Nous utilisons le scalaire t comme paramètre pour l'interpolation linéaire:

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

Ceci interpole entre les deux bords que nous avons créés, bord AB et bord BC. La seule chose que nous devons maintenant faire pour calculer le point à dessiner est d’interpoler entre P0 et P1 en utilisant le même t comme suit:

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

Il y a plusieurs choses à faire avant de tracer la courbe. Tout d’abord, nous marcherons quelques dt (delta t) et nous devons être conscients que 0 <= t <= 1. Comme vous pouvez peut-être imaginer, cela ne nous donnera pas une courbe lisse, mais plutôt un ensemble discret de positions sur lesquelles tracer. La solution la plus simple consiste à tracer une ligne entre le point actuel et le point précédent.

Autres conseils

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

Ceci, par exemple, dessine un coeur:

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

Vous pouvez utiliser le aggdraw au-dessus de PIL, les courbes de Bézier sont pris en charge .

EDIT:

J'ai créé un exemple uniquement pour découvrir un bogue dans la classe Path concernant curveto: (

Voici quand même l'exemple:

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

Cela devrait corriger le bogue si vous ' re up pour recompiler le module ...

Bien que les chemins de Bézier Curveto ne fonctionnent pas avec Aggdraw, comme mentionné par @ToniRu & # 382; a, il existe un autre moyen de le faire dans Aggdraw. L’utilisation de Aggdraw au lieu de PIL ou de vos propres fonctions de Bézier présente l’avantage de permettre à Aggdraw d’antialiser l’image en la rendant plus lisse (voir la photo en bas).

Symboles Aggdraw

Au lieu d'utiliser la classe aggdraw.Path () pour dessiner, vous pouvez utiliser la classe aggdraw.Symbol(pathstring) qui est fondamentalement la même, sauf que vous écrivez le chemin sous forme de chaîne. Selon la documentation Aggdraw, la façon d'écrire votre chemin en tant que chaîne consiste à utiliser la syntaxe de chemin SVG (voir: http://www.w3.org/TR/SVG/paths.html ). Fondamentalement, chaque ajout (noeud) au chemin commence normalement par

  • une lettre représentant l'action de dessin (majuscule pour chemin absolu, minuscule pour chemin relatif), suivie de (aucun espace entre les deux)
  • la coordonnée x (précédée d'un signe moins s'il s'agit d'un nombre ou d'une direction négatif)
  • une virgule
  • la coordonnée y (précédée d'un signe moins s'il s'agit d'un nombre ou d'une direction négatif)

Dans votre chemin d'accès, séparez simplement vos multiples nœuds avec un espace. Une fois que vous avez créé votre symbole, rappelez-vous de le dessiner en le transmettant comme argument à draw.symbol(args).

Les courbes de Bézier dans les symboles Aggdraw

Spécifiquement pour les courbes de bezier cubiques, vous écrivez la lettre " C " ou " c " suivi de 6 chiffres (3 jeux de coordonnées xy x1, y1, x2, y2, x3, y3 avec des virgules entre les chiffres mais pas entre le premier chiffre et la lettre). Selon la documentation, il existe également d'autres versions de Bézier utilisant la lettre & "; S (Bezier cubique lisse), Q (Bézier quadratique), T (Bézier quadratique lisse) &" ;. Voici un exemple de code complet (nécessite PIL et 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!"

Et la sortie est une figure de Bézier incurvée et lisse: Résultat du script ci-dessus utilisant le symbole de courbe aggdraw bezier

J'ai trouvé un moyen plus simple de créer une courbe de Bézier (sans aggraw et sans fonctions complexes).

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()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top