[Python] 어떻게하면 파이썬의 PIL을 사용하여 베 지어 곡선을 그릴 수 있습니까?


Answers

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

예를 들면 다음과 같습니다.

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

파이썬의 이미징 라이브러리를 사용하고 있으며 일부 베 지어 곡선을 그려야합니다. 나는 픽셀 단위로 픽셀을 계산할 수 있다고 생각하지만, 더 단순한 것이 있기를 바라고 있습니다.




@ ToniRuža에서 언급 한 것처럼 Bezier curveto 경로는 Aggdraw와 작동하지 않지만 Aggdraw에서이 작업을 수행하는 또 다른 방법이 있습니다. PIL 대신 자신의 베 지어 함수 인 Aggdraw를 사용하면 Aggdraw가 이미지에 안티 알리아싱을 적용하여보다 매끄럽게 보일 수 있습니다 (하단 그림 참조).

Aggdraw 기호

그리기 위해 aggdraw.Path () 클래스를 사용하는 대신 aggdraw.Symbol(pathstring) 클래스를 사용할 수 있습니다.이 클래스는 경로를 문자열로 쓰는 것을 제외하고는 기본적으로 동일합니다. Aggdraw 문서에 따르면 경로를 문자열로 작성하는 방법은 SVG 경로 구문을 사용하는 것입니다 ( http://www.w3.org/TR/SVG/paths.html 참조 ). 기본적으로 경로에 대한 각 추가 (노드)는

  • 드로잉 동작을 나타내는 문자 (절대 경로는 대문자, 상대 경로는 소문자), 뒤에 (공백없이)
  • x 좌표 (음수 또는 방향 인 경우 빼기 부호 앞에옵니다)
  • 쉼표
  • y 좌표 (음수 또는 방향 인 경우 빼기 부호가 앞에 표시)

pathstring에서 여러 노드를 공백으로 구분하면됩니다. 심볼을 만든 후에는 draw.symbol(args) 의 인수 중 하나로 전달하여 심볼을 그려야합니다.

Aggdraw 기호의 베 지어 곡선

특히 입방 형 베 지어 곡선의 경우 문자 "C"또는 "c"다음에 6 개의 숫자 (xy 좌표 x1, y1, x2, y2, x3, y3의 3 개 세트는 숫자 사이에 쉼표가 있지만 첫 번째 숫자와 그 편지). 문서에 따르면 문자 "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!"

출력물은 부드럽고 곡선 된 베 지어 그림입니다.