Pasting, masks and alpha compositing

Pillow · Practical · 12 min

Watermarks, overlays, stickers and image collages are all compositing problems with easy-to-miss alpha rules.

base.paste(other, box, mask=None) copies other into base in place. box can be a 2-tuple (x, y) for the top-left corner or a 4-tuple region. Without a mask, transparent pixels of other are copied as-is, overwriting the base, so a PNG logo pasted onto a photo gets an opaque rectangle around it.

Pass a mask: an L or 1 image (or the A band of an RGBA image) where 255 means copy, 0 means keep the base and values in between blend. Using the image itself as mask, base.paste(logo, (x, y), logo), makes Pillow use its alpha band.

Image.alpha_composite(dst, src) does true Porter-Duff "over" compositing: both images must be RGBA and the same size, and the result's alpha is computed correctly. That matters when the base itself has transparency, where paste-with-mask gets alpha wrong. dst.alpha_composite(src, dest=(x, y)) places a smaller src at an offset in place.

Other tools:

  • Image.blend(a, b, alpha): linear mix of two same-size, same-mode images (0.0 gives a, 1.0 gives b)
  • Image.composite(a, b, mask): pick per pixel from a or b via a mask
  • im.putalpha(band_or_value): add or replace the alpha band

A common watermark recipe: draw text on a transparent RGBA layer with a partly transparent fill, then alpha_composite it onto the photo converted to RGBA.

Example

from PIL import Image, ImageDraw

photo = Image.new("RGB", (200, 100), (0, 0, 255))
logo = Image.new("RGBA", (40, 40), (0, 0, 0, 0))               # transparent
ImageDraw.Draw(logo).ellipse([0, 0, 39, 39], fill=(255, 255, 0, 255))

a = photo.copy(); a.paste(logo, (10, 10))                     # no mask
b = photo.copy(); b.paste(logo, (10, 10), logo)               # alpha as mask
print(a.getpixel((11, 11)), b.getpixel((11, 11)))             # (0, 0, 0) (0, 0, 255)

layer = Image.new("RGBA", photo.size, (0, 0, 0, 0))
ImageDraw.Draw(layer).rectangle([100, 0, 199, 99], fill=(255, 255, 255, 128))
marked = Image.alpha_composite(photo.convert("RGBA"), layer)
print(marked.getpixel((150, 50)))                             # (128, 128, 255, 255)

fade = Image.blend(photo, Image.new("RGB", (200, 100), "red"), 0.25)
print(fade.getpixel((0, 0)))                                  # (63, 0, 191)

Remember

  • paste works in place; pass a mask (often the image's own alpha) to respect transparency
  • alpha_composite does correct 'over' blending of two RGBA images
  • blend cross-fades, composite picks per pixel via a mask
  • Draw watermarks on a transparent layer, then composite

Watch out for

  • Pasting a transparent PNG without a mask and getting a black or solid box around it.

Check yourself

  1. You paste a logo with transparent corners onto a photo and see black corners. What fixes it?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.alpha_composite

Builds on

Unlocks

Nothing builds on this yet.