代码之家  ›  专栏  ›  技术社区  ›  carrier

如何使用python的pil绘制贝塞尔曲线?

  •  13
  • carrier  · 技术社区  · 16 年前

    我使用的是python的图像库,我想画一些贝塞尔曲线。 我想我可以一个像素一个像素的计算,但我希望有更简单的东西。

    5 回复  |  直到 6 年前
        1
  •  11
  •   Jasper Bekkers    13 年前

    贝塞尔曲线并不难画出来。给3分 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 (Delta T)我们需要知道 0 <= t <= 1 . 正如你所能想象的,这不会给我们一条平滑的曲线,相反它只会产生一组离散的位置来绘制。解决这个问题的最简单的方法就是在当前点和上一点之间画一条线。

        2
  •  18
  •   unutbu    6 年前
    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')
    
        3
  •  7
  •   Matt Razza    16 年前

    你可以使用 aggdraw 在pil上,贝塞尔曲线是 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 如果要重新编译模块,应该修复这个错误…

        4
  •  4
  •   Karim Bahgat    11 年前

    尽管bezier curveto paths不适用于aggdraw,正如@toniru_¾a所提到的,但在aggdraw中还有另一种方法可以做到这一点。使用aggdraw而不是pil或您自己的bezier函数的好处是aggdraw将消除图像的锯齿,使其看起来更平滑(请参见底部的图片)。

    AgGub符号

    您可以使用 aggdraw.Symbol(pathstring) 类,除了将路径作为字符串写入外,它基本上是相同的。根据aggdraw文档,将路径写入字符串的方法是使用svg路径语法(请参见: http://www.w3.org/TR/SVG/paths.html )基本上,路径的每个添加(节点)通常以

    • 表示绘图操作的字母(大写表示绝对路径,小写表示相对路径),后跟(中间没有空格)
    • X坐标(如果是负数或方向,则在负号之前)
    • 逗号
    • Y坐标(如果是负数或方向,则在负号之前)

    在路径字符串中,只需使用空格分隔多个节点。一旦你创建了你的符号,记住把它作为参数传递给 draw.symbol(args) .

    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!"
    

    输出是一个平滑的曲线贝塞尔图: Result from script above using aggdraw bezier curve symbol

        5
  •  1
  •   Lucas VL    8 年前

    我发现了一种创建贝塞尔曲线的简单方法(不使用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()