如何使用Python的PIL绘制贝塞尔曲线?
贝塞尔曲线并不是很难绘制自己。给定三点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
。正如你可以想象的那样,这不会给我们一条平滑的曲线,而只会产生一组离散的位置。解决这个问题的最简单方法是简单地在当前点和前一点之间划一条线。
您可以在PIL上面使用aggdraw,贝塞尔曲线为supported。
编辑:
我做出了榜样,才发现有在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()
This应修复错误,如果你想重新编译模块...
对于如何修复Aggdraw bezier错误的链接+1,太糟糕了,Python绑定尚未更新以修复它。 – 2014-01-28 19:18:52
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')
Fro完整性:您需要`从PIL导入Image`和`从PIL导入ImageDraw`才能工作。 – steffen 2012-03-01 20:51:34
虽然贝塞尔curveto路径不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)
的参数之一来传递就可以了。
Bezier曲线在Aggdraw符号
具体地为你写的字母 “C” 或 “c”,接着6位数字(3套的xy坐标X1,Y1,X2,Y2,X3三次Bezier曲线,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!"
和输出是一个平滑的外观曲线贝塞尔曲线图:
我发现了一个更简单的方法创建一个贝塞尔曲线(不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()
感谢您的回答,我最终可能会这样做。这就是我的意思,当我说“我想我可以逐像素地计算像素......”,我可以做数学,但想知道是否可以使用内置的东西。 – carrier 2008-10-29 12:54:15