How do you draw transparent polygons with Python?

房东的猫 提交于 2019-11-27 21:10:46

问题


I'm using PIL (Python Imaging Library). I'd like to draw transparent polygons. It seems that specifying a fill color that includes alpha level does not work. Are their workarounds?

If it can't be done using PIL I'm willing to use something else.

If there is more than one solution, then performance should be factored in. The drawing needs to be as fast as possible.


回答1:


This is for Pillow, a more maintained fork of PIL. http://pillow.readthedocs.org/

If you want to draw polygons that are transparent, relative to each other, the base Image has to be of type RGB, not RGBA, and the ImageDraw has to be of type RGBA. Example:

from PIL import Image, ImageDraw

img = Image.new('RGB', (100, 100))
drw = ImageDraw.Draw(img, 'RGBA')
drw.polygon([(50, 0), (100, 100), (0, 100)], (255, 0, 0, 125))
drw.polygon([(50,100), (100, 0), (0, 0)], (0, 255, 0, 125))
del drw

img.save('out.png', 'PNG')

This will draw two triangles overlapping with their two colors blending. This a lot faster than having to composite multiple 'layers' for each polygon.




回答2:


What I've had to do when using PIL to draw transparent images is create a color layer, an opacity layer with the polygon drawn on it, and composited them with the base layer as so:

color_layer = Image.new('RGBA', base_layer.size, fill_rgb)
alpha_mask = Image.new('L', base_layer.size, 0)
alpha_mask_draw = ImageDraw.Draw(alpha_mask)
alpha_mask_draw.polygon(self.outline, fill=fill_alpha)
base_layer = Image.composite(color_layer, base_layer, alpha_mask)

When using Image.Blend I had issues with strange outlining behaviors on the drawn polygons.

The only issue with this approach is that the performance is abysmal when drawing a large number of reasonably sized polygons. A much faster solution would be something like "manually" drawing the polygon on a numpy array representation of the image.




回答3:


PIL's Image module provides a blend method.

Create a second image the same size as your first, with a black background. Draw your polygon on it (with full colour). Then call Image.blend passing the two images and an alpha level. It returns a third image, which should have a semi-transparent polygon on it.

I haven't measured the performance (hey, I haven't even tried it!) so I cannot comment on it's suitability. I suggest you work out your performance budget, and then measure it to see if it is fast enough for your purposes.




回答4:


I'm using cairo + pycairo for this, and it works well. And you can share image data between PIL and cairo, using python buffer interface, if there is operation in pil that can't be done in cairo.




回答5:


From what I have found it can't be done directly with PIL. Here is a solution with PyCairo. Cairo is also used by Mozilla, GTX+, Mono, Inkscape, and WebKit so I think it is a safe to use in terms of future support. It can also be done with aggdraw, an optional add-on for PIL. See my listed source for more details. Python version 2.7.3 is used.

Source: http://livingcode.org/2008/12/14/drawing-with-opacity.1.html

Helper file: random_polys_util.py

    MIN_ALPHA = 50
    MAX_ALPHA = 100

    WIDTH = 500
    HEIGHT = 250

    #
    #   Utilities
    #
    def hex2tuple(hex_color):
        return tuple([int(hex_color[i:i+2], 16) for i in range(1,9,2)])

    def tuple2hex(tuple_color):
        return "#%0.2X%0.2X%0.2X%0.2X" % tuple_color

    def ints2floats(tuple_color):
        return tuple([c / 255.0 for c in tuple_color])

    def inc_point(p, dp):
        return (p[0] + dp[0]) % WIDTH, (p[1] + dp[1]) % HEIGHT

    def inc_triangle(t, dt):
        return tuple([inc_point(t[i], dt[i]) for i in range(3)])

    def inc_color(c, dc):
        new_c = [(c[i] + dc[i]) % 256 for i in range(3)]
        new_a = (c[3] + dc[3]) % MAX_ALPHA
        if new_a < MIN_ALPHA: new_a += MIN_ALPHA
        new_c.append(new_a)
        return tuple(new_c)

    def draw_all(draw_fn):
        triangle = start_t
        color = start_c
        for i in range(50):
            triangle = inc_triangle(triangle, dt)
            color = inc_color(color, dc)
            draw_fn(triangle, color)

    #
    #   Starting and incrementing values
    #
    start_c = hex2tuple('E6A20644')
    start_t = (127, 132), (341, 171), (434, 125)
    dt = (107, 23), (47, 73), (13, 97)
    dc = 61, 113, 109, 41

Main file: random_polys.py

from random_polys_util import *

def cairo_poly(pts, clr):
    ctx.set_source_rgba(*ints2floats(clr))
    ctx.move_to(*pts[-1])
    for pt in pts:
        ctx.line_to(*pt)
    ctx.close_path()
    ctx.fill()

def cairo_main():
    # Setup Cairo
    import cairo
    global ctx
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
    ctx = cairo.Context(surface)
    # fill background white
    cairo_poly(((0,0),(WIDTH,0),(WIDTH,HEIGHT),(0,HEIGHT)),(255,255,255,255))
    draw_all(cairo_poly)
    surface.write_to_png('cairo_example.png')

def main():
    cairo_main()

if __name__ == "__main__":
    main()



回答6:


I had to draw an outside polygon with an outline, and subtract inner polygons (a common operation in GIS). Works like a charm using color (255,255,255,0).

image = Image.new("RGBA", (100,100))
drawing = ImageDraw.Draw(i)
for index, p in enumerate(polygons):
    if index == 0:
        options = { 'fill': "#AA5544",
                    'outline': "#993300"}
    else:
        options = {'fill': (255,255,255,0)}
    drawing.polygon( p, **options )

buf= StringIO.StringIO()
i.save(buf, format= 'PNG')
# do something with buf


来源:https://stackoverflow.com/questions/359706/how-do-you-draw-transparent-polygons-with-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!