Skip to content
Advertisement

How to create overlapping semitransparent shapes on semitransparent background with PIL

I’m trying to create image with semitransparent shapes drawn on transparent background. For some reason instead of staying transparent, the shapes are completely covering those beneath them. My code:

from PIL import Image, ImageDraw
img = Image.new("RGBA", (256, 256), (255,0,0,127))
drawing = ImageDraw.Draw(img, "RGBA")
drawing.ellipse((127-79, 127 - 63, 127 + 47, 127 + 63), fill=(0, 255, 0, 63), outline=(0, 255, 0, 255))
drawing.ellipse((127-47, 127 - 63, 127 + 79, 127 + 63), fill=(0, 0, 255, 63), outline=(0, 0, 255, 255))
img.save("foo.png", "png")

I would expect the result to look something like (except for background not being transparent):
expected
but it looks like:
actual

When I try to save it as GIF with img.save("foo.gif", "gif"), result is even worse. Circles are solid, no difference between outline and fill.

Advertisement

Answer

As I mentioned in a comment, ImageDraw.Draw doesn’t do blending—whatever is drawn replaces whatever pixels that were there previously. To get the effect you want requires drawing things in a two-step process. The ellipse must first be drawn on a blank transparent background, and then that must be alpha-composited with current image (bg_img) to preserve transparency.

In the code below this has been implementing in re-usable function:

from PIL import Image, ImageDraw


def draw_transp_ellipse(img, xy, **kwargs):
    """ Draws an ellipse inside the given bounding box onto given image.
        Supports transparent colors
    """
    transp = Image.new('RGBA', img.size, (0,0,0,0))  # Temp drawing image.
    draw = ImageDraw.Draw(transp, "RGBA")
    draw.ellipse(xy, **kwargs)
    # Alpha composite two images together and replace first with result.
    img.paste(Image.alpha_composite(img, transp))


bg_img = Image.new("RGBA", (256, 256), (255, 0, 0, 127))  # Semitransparent background.

draw_transp_ellipse(bg_img, (127-79, 127-63, 127+47, 127+63),
                    fill=(0, 255, 0, 63), outline=(0, 255, 0, 255))
draw_transp_ellipse(bg_img, (127-47, 127-63, 127+79, 127+63),
                    fill=(0, 0, 255, 63), outline=(0, 0, 255, 255))

bg_img.save("foo.png")

This is the image it created viewed in my image file editor app which renders semi-transparent portions of images with a checker-board pattern. As you can see the opaque outlines are the only part that isn’t.

screenshot of image in image editor

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement