如何使用 Python 的 PIL 绘制贝塞尔曲线?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/246525/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I draw a bezier curve using Python's PIL?
提问by carrier
I'm using Python's Imaging Library and I would like to draw some bezier curves. I guess I could calculate pixel by pixel but I'm hoping there is something simpler.
我正在使用 Python 的成像库,我想绘制一些贝塞尔曲线。我想我可以逐个像素地计算,但我希望有更简单的东西。
采纳答案by Jasper Bekkers
A bezier curve isn't that hard to draw yourself. Given three points A
, B
, C
you require three linear interpolations in order to draw the curve. We use the scalar t
as the parameter for the linear interpolation:
自己画一条贝塞尔曲线并不难。给定三个点A
,B
,C
您需要三个线性插值才能绘制曲线。我们使用标量t
作为线性插值的参数:
P0 = A * t + (1 - t) * B
P1 = B * t + (1 - t) * C
This interpolates between two edges we've created, edge AB and edge BC. The only thing we now have to do to calculate the point we have to draw is interpolate between P0 and P1 using the same t like so:
这会在我们创建的两条边之间进行插值,边 AB 和边 BC。我们现在唯一要做的就是计算我们必须绘制的点,使用相同的 t 在 P0 和 P1 之间进行插值,如下所示:
Pfinal = P0 * t + (1 - t) * P1
There are a couple of things that need to be done before we actually draw the curve. First off we have will walk some dt
(delta t) and we need to be aware that 0 <= t <= 1
. As you might be able to imagine, this will not give us a smooth curve, instead it yields only a discrete set of positions at which to plot. The easiest way to solve this is to simply draw a line between the current point and the previous point.
在我们实际绘制曲线之前,需要做几件事。首先,我们将走一些dt
(delta t),我们需要知道0 <= t <= 1
. 正如您可能想象的那样,这不会给我们一条平滑的曲线,而是只产生一组离散的要绘制的位置。解决这个问题的最简单方法是简单地在当前点和前一点之间画一条线。
回答by unutbu
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
This, for example, draws a heart:
例如,这画了一颗心:
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')
回答by Toni Ru?a
You can use the aggdrawon top of PIL, bezier curves are supported.
EDIT:
编辑:
I made an example only to discover there is a bug in the Path
class regarding curveto
:(
我做了一个例子只是为了发现Path
课堂上有一个关于curveto
:(
Here is the example anyway:
无论如何,这是示例:
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()
Thisshould fix the bug if you're up for recompiling the module...
如果您准备重新编译模块,这应该可以修复该错误...
回答by Karim Bahgat
Although bezier curveto paths don't work with Aggdraw, as mentioned by @ToniRu?a, there is another way to do this in Aggdraw. The benefit of using Aggdraw instead of PIL or your own bezier functions is that Aggdraw will antialias the image making it look smoother (see pic at bottom).
尽管如@ToniRu?a 所述,贝塞尔曲线路径不适用于 Aggdraw,但在 Aggdraw 中还有另一种方法可以做到这一点。使用 Aggdraw 而不是 PIL 或您自己的贝塞尔函数的好处是 Aggdraw 将抗锯齿图像使其看起来更平滑(见底部图片)。
Aggdraw Symbols
聚合符号
Instead of using the aggdraw.Path() class to draw, you can use the aggdraw.Symbol(pathstring)
class which is basically the same except you write the path as a string. According to the Aggdraw docs the way to write your path as a string is to use SVG path syntax (see: http://www.w3.org/TR/SVG/paths.html). Basically, each addition (node) to the path normally starts with
aggdraw.Symbol(pathstring)
除了将路径写为字符串之外,您还可以使用基本相同的类,而不是使用 aggdraw.Path() 类来绘制。根据 Aggdraw 文档,将路径写入字符串的方法是使用 SVG 路径语法(请参阅:http: //www.w3.org/TR/SVG/paths.html)。基本上,路径的每个添加(节点)通常以
- a letter representing the drawing action (uppercase for absolute path, lowercase for relative path), followed by (no spaces in between)
- the x coordinate (precede by a minus sign if it is a negative number or direction)
- a comma
- the y coordinate (precede by a minus sign if it is a negative number or direction)
- 代表绘图动作的字母(大写表示绝对路径,小写表示相对路径),后跟(中间没有空格)
- x 坐标(如果它是负数或方向,则前面加上一个减号)
- 逗号
- y 坐标(如果它是负数或方向,则前面有一个减号)
In your pathstring just separate your multiple nodes with a space. Once you have created your symbol, just remember to draw it by passing it as one of the arguments to draw.symbol(args)
.
在您的路径字符串中,只需用空格分隔多个节点。创建符号后,请记住通过将其作为参数之一传递给 来绘制它draw.symbol(args)
。
Bezier Curves in Aggdraw Symbols
Aggdraw 符号中的贝塞尔曲线
Specifically for cubic bezier curves you write the letter "C" or "c" followed by 6 numbers (3 sets of xy coordinates x1,y1,x2,y2,x3,y3 with commas in between the numbers but not between the first number and the letter). According the docs there are also other bezier versions by using the letter "S (smooth cubic bezier), Q (quadratic bezier), T (smooth quadratic bezier)". Here is a complete example code (requires PIL and aggdraw):
特别是对于三次贝塞尔曲线,您写下字母“C”或“c”,后跟 6 个数字(3 组 xy 坐标 x1,y1,x2,y2,x3,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!"
And the output is a smooth-looking curved bezier figure:
输出是一个看起来很光滑的弯曲贝塞尔曲线图:
回答by Lucas VL
I found a simpler way creating a bezier curve (without aggraw and without complex functions).
我找到了一种更简单的方法来创建贝塞尔曲线(没有 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()