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

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


person carrier    schedule 29.10.2008    source источник


Ответы (5)


Кривую Безье не так уж и сложно нарисовать самому. Для трех точек 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. Как вы, возможно, догадались, это не даст нам плавной кривой, вместо этого будет получен только дискретный набор позиций для построения графика. Самый простой способ решить эту проблему - просто провести линию между текущей точкой и предыдущей точкой.

person Jasper Bekkers    schedule 29.10.2008
comment
спасибо за ваш ответ, возможно, в конце концов я так и сделаю. это то, что я имел в виду, когда сказал, что, по-моему, могу посчитать пиксель за пикселем ... что я могу просто делать математику, но мне было интересно, можно ли использовать что-то встроенное. - person carrier; 29.10.2008

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')
person unutbu    schedule 18.02.2010
comment
Для полноты: вам нужны from PIL import Image и from PIL import ImageDraw, чтобы это работало. - person steffen; 02.03.2012
comment
Является ли использование треугольника Паскаля для генерации коэффициентов вашим собственным изобретением? - person martineau; 30.01.2019
comment
Реализация принадлежит мне, но связь между биномиальными коэффициентами и треугольником Паскаля, вероятно, очень старая. - person unutbu; 30.01.2019

Вы можете использовать aggdraw поверх 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()

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

person Toni Ruža    schedule 29.10.2008
comment
+1 за ссылку о том, как исправить ошибку Aggdraw bezier, жаль, что привязки Python не были обновлены, чтобы исправить это. - person Karim Bahgat; 28.01.2014

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

Символы Aggdraw

Вместо использования класса aggdraw.Path () для рисования вы можете использовать класс aggdraw.Symbol(pathstring), который в основном тот же, за исключением того, что вы записываете путь в виде строки. Согласно документам 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!"

На выходе получается гладкая изогнутая фигура Безье: Результат из приведенного выше сценария с использованием символа кривой Безье Agdraw

person Karim Bahgat    schedule 28.01.2014

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

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()
person Lucas VL    schedule 23.08.2017