Как я могу нарисовать кривую Безье, используя PIL Python?

StackOverflow https://stackoverflow.com/questions/246525

Вопрос

Я использую библиотеку изображений Python и хотел бы нарисовать несколько кривых Безье.Думаю, я мог бы посчитать попиксельно, но надеюсь, что есть что-то попроще.

Это было полезно?

Решение

Кривая Безье не так сложно нарисовать. Для трех точек A, B, C вам потребуется три линейные интерполяции, чтобы нарисовать кривую. Мы используем скаляр t в качестве параметра для линейной интерполяции:

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

Это интерполирует между двумя ребрами, которые мы создали, ребром AB и ребром BC. Единственное, что нам теперь нужно сделать, чтобы вычислить точку, которую мы должны нарисовать, это интерполировать между P0 и P1, используя один и тот же t, например:

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

Есть несколько вещей, которые необходимо сделать, прежде чем мы начнем рисовать кривую. Прежде всего, мы пройдемся немного dt (дельта t), и мы должны знать, что 0 <= t <= 1. Как вы можете себе представить, это не даст нам плавную кривую, а даст только дискретный набор позиций для построения графика. Самый простой способ решить эту проблему - просто нарисовать линию между текущей точкой и предыдущей точкой.

Другие советы

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

Это, например, рисует сердце:

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

Вы можете использовать раздражать поверх PIL кривые Безье поддерживается.

РЕДАКТИРОВАТЬ:

Я привел пример только для того, чтобы обнаружить ошибку в Path класс относительно curveto :(

Во всяком случае, вот пример:

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

Этот должен исправить ошибку, если вы готовы перекомпилировать модуль...

Хотя пути кривых Безье не работают с Aggdraw, как упоминал @ToniRuža, в Aggdraw есть другой способ сделать это.Преимущество использования Aggdraw вместо PIL или ваших собственных функций Безье заключается в том, что Aggdraw будет сглаживать изображение, делая его более гладким (см. рисунок внизу).

Символы Agdraw

Вместо использования класса aggdraw.Path() для рисования вы можете использовать aggdraw.Symbol(pathstring) class, который по сути тот же, за исключением того, что вы пишете путь в виде строки.Согласно документации Aggdraw, способ записи пути в виде строки заключается в использовании синтаксиса пути SVG (см.: http://www.w3.org/TR/SVG/paths.html).По сути, каждое дополнение (узел) к пути обычно начинается с

  • буква, обозначающая действие рисования (прописная для абсолютного пути, строчная для относительного пути), за которой следует (без пробелов между ними)
  • координата x (перед ней ставится знак минус, если это отрицательное число или направление)
  • запятая
  • координата y (перед ней ставится знак минус, если это отрицательное число или направление)

В строке пути просто разделите несколько узлов пробелом.После того, как вы создали свой символ, не забудьте нарисовать его, передав его в качестве одного из аргументов функции. draw.symbol(args).

Кривые Безье в символах Aggdraw

В частности, для кубических кривых Безье вы пишете букву «C» или «c», за которой следуют 6 цифр (3 набора координат xy x1, y1, x2, y2, x3, y3 с запятыми между числами, но не между первым числом и письмо).Согласно документации, существуют и другие версии Безье с использованием буквы «S (гладкая кубическая Безье), Q (квадратичная Безье), T (гладкая квадратичная Безье)».Вот полный пример кода (требуется PIL и 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!"

В результате получается гладкая изогнутая фигура Безье:Result from script above using aggdraw bezier curve symbol

Я нашел более простой способ создания кривой Безье (без сложных и сложных функций).

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()
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top