Python の PIL を使用してベジェ曲線を描くにはどうすればよいですか?
-
05-07-2019 - |
質問
Python のイメージング ライブラリを使用しており、ベジェ曲線を描画したいと考えています。ピクセルごとに計算できると思いますが、もっと単純なものがあることを願っています。
解決
ベジェ曲線を描くのはそれほど難しくありません。 3つのポイントA
、B
、C
を指定すると、曲線を描くために3つの線形補間が必要になります。スカラーt
を線形補間のパラメーターとして使用します。
P0 = A * t + (1 - t) * B
P1 = B * t + (1 - t) * C
これは、作成した2つのエッジ、エッジABとエッジBCの間を補間します。描画するポイントを計算するために今やらなければならないことは、同じ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')
PILの上で aggdraw を使用できます。ベジェ曲線はサポート。
編集:
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()
これは、モジュールを再コンパイルするために再起動します...
@ToniRuža が述べたように、ベジェ曲線パスは Aggdraw では機能しませんが、Aggdraw でこれを行う別の方法があります。PIL や独自のベジェ関数の代わりに Aggdraw を使用する利点は、Aggdraw が画像をアンチエイリアスして、より滑らかに見えることです (下部の図を参照)。
Aggdraw シンボル
aggdraw.Path() クラスを使用して描画する代わりに、 aggdraw.Symbol(pathstring)
パスを文字列として記述することを除いて、基本的に同じクラスです。Aggdraw のドキュメントによると、パスを文字列として記述する方法は、SVG パス構文を使用することです (以下を参照)。 http://www.w3.org/TR/SVG/paths.html)。基本的に、パスへの各追加 (ノード) は通常、次で始まります。
- 描画アクションを表す文字 (絶対パスの場合は大文字、相対パスの場合は小文字) に続いて (間にスペースは入れません)
- X 座標 (負の数または方向の場合は、先頭にマイナス記号が付きます)
- カンマ
- y 座標 (負の数または方向の場合は、先頭にマイナス記号を付けます)
パス文字列では、複数のノードをスペースで区切るだけです。シンボルを作成したら、それを引数の 1 つとして渡して描画することを忘れないでください。 draw.symbol(args)
.
Aggdraw シンボルのベジェ曲線
特に 3 次ベジェ曲線の場合は、文字「C」または「c」の後に 6 つの数字を書きます (3 組の xy 座標 x1、y1、x2、y2、x3、y3 を数字の間にカンマを入れますが、最初の数字と数字の間にはカンマを入れません)。手紙)。ドキュメントによると、「S (滑らかな 3 次ベジェ)、Q (2 次ベジェ)、T (滑らかな 2 次ベジェ)」という文字を使用した他のベジェ バージョンもあります。完全なコード例は次のとおりです (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!"
出力は滑らかに見える曲線ベジェ図形です。
ベジェ曲線を作成する簡単な方法を見つけました(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()