문제

Python의 이미징 라이브러리를 사용하고 있으며 Bezier 곡선을 그리고 싶습니다. 픽셀로 픽셀을 계산할 수 있다고 생각하지만 더 간단한 것이 있기를 바라고 있습니다.

도움이 되었습니까?

해결책

베 지어 곡선은 자신을 그리기가 어렵지 않습니다. 세 가지 점이 주어졌습니다 A, B, C 곡선을 그리려면 세 가지 선형 보간이 필요합니다. 우리는 스칼라를 사용합니다 t 선형 보간의 매개 변수로 :

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

이것은 우리가 만든 두 개의 가장자리, Edge AB와 Edge BC 사이를 보간합니다. 우리가 그려야 할 포인트를 계산하기 위해 우리가해야 할 유일한 것은 같은 t와 같은 t를 사용하여 P0과 P1 사이를 보간하는 것입니다.

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

당신은 사용할 수 있습니다 aggdraw Pil 위에 Bezier 곡선이 있습니다 지원.

편집하다:

나는 버그가 있음을 발견하기 위해서만 모범을 보였습니다. 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()

이것 모듈을 다시 컴파일하려는 경우 버그를 수정해야합니다 ...

Bezier Curveto 경로는 AgGDRAD에서 작동하지 않지만 @Toniruža에서 언급했듯이 AggDraw에서는이 작업을 수행하는 또 다른 방법이 있습니다. PIL 또는 자신의 Bezier 함수 대신 AgGDRAD를 사용하면 AGGDRAD가 이미지를 더 매끄럽게 보이게한다는 것입니다 (하단의 그림 참조).

aggdraw 기호

aggdraw.path () 클래스를 사용하여 그리기 대신 aggdraw.Symbol(pathstring) 문자열로 경로를 쓰는 것을 제외하고는 기본적으로 동일합니다. AGGDRAD DOCS에 따르면 문자열로 경로를 작성하는 방법은 SVG Path Syntax를 사용하는 것입니다 (참조 : 참조 : 참조 : http://www.w3.org/tr/svg/paths.html). 기본적으로 경로에 각 추가 (노드)가 일반적으로 시작합니다.

  • 드로잉 동작을 나타내는 문자 (절대 경로의 대문자, 상대 경로의 소문자), (사이에 공간이 없음)
  • X 좌표 (마이너스 부호가 음수 또는 방향 인 경우)
  • 쉼표
  • y 좌표 (음수 또는 방향 인 경우 마이너스 부호에 앞서)

PathString에서 여러 노드를 공간으로 분리하십시오. 당신이 당신의 상징을 만든 후에는, 그 기호를 인수 중 하나로 전달하여 그것을 그려야합니다. draw.symbol(args).

aggdraw 기호의 베 지어 곡선

특히 입방 베 지어 곡선의 경우 문자 "C"또는 "C"와 6 개의 숫자 (3 세트의 XY 좌표 X1, Y1, X2, Y2, X3, Y3 숫자가 숫자 사이에 있지만 첫 번째 숫자와 첫 숫자 사이에 있지 않습니다. 그 편지). 문서에 따르면 "S (Smooth Cubic Bezier), Q (2 차 Bezier), T (Smooth Quadratic Bezier)"를 사용하여 다른 Bezier 버전도 있습니다. 다음은 전체 예제 코드입니다 (PLIL 및 AGGDRAD 필요).

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

나는 베 지어 곡선을 만드는 더 간단한 방법을 찾았다 (Aggraw가없고 복잡한 기능없이).

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