如何使用Python的PIL绘制贝塞尔曲线?
我正在使用Python的图像库,想要画一些贝塞尔曲线。我想我可以逐个像素地计算,但我希望有更简单的方法。
5 个回答
8
你可以在PIL的基础上使用aggdraw,它支持贝塞尔曲线。
编辑:
我做了一个例子,结果发现Path
类在curveto
方面有个bug :(
不过这里还是有个例子:
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()
这个链接应该能修复这个bug,如果你愿意重新编译这个模块的话...
31
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')
比如说,这段代码可以画出一个心形:
13
贝塞尔曲线其实并不难画。给定三个点 A
、B
和 C
,我们需要进行三次线性插值来绘制这条曲线。我们用一个叫 t
的参数来进行线性插值:
P0 = A * t + (1 - t) * B
P1 = B * t + (1 - t) * C
这个插值是在我们创建的两条边之间进行的,边 AB 和边 BC。现在我们需要做的就是用同样的 t
在 P0 和 P1 之间进行插值,像这样:
Pfinal = P0 * t + (1 - t) * P1
在实际绘制曲线之前,还有几件事情需要处理。首先,我们会走一些 dt
(增量 t),并且需要注意 0 <= t <= 1
。你可能能想象,这样做不会得到一条平滑的曲线,而只是得到一组离散的位置来绘制。解决这个问题最简单的方法就是在当前点和上一个点之间画一条线。