238

I have a transparent png image foo.png and I've opened another image with:

im = Image.open("foo2.png")

Now what I need is to merge foo.png with foo2.png.

(foo.png contains some text and I want to print that text on foo2.png)

pjpscriv
  • 866
  • 11
  • 20
Arackna
  • 2,665
  • 2
  • 16
  • 16

8 Answers8

483
from PIL import Image

background = Image.open("test1.png")
foreground = Image.open("test2.png")

background.paste(foreground, (0, 0), foreground)
background.show()

First parameter to .paste() is the image to paste. Second are coordinates, and the secret sauce is the third parameter. It indicates a mask that will be used to paste the image. If you pass a image with transparency, then the alpha channel is used as mask.

Check the docs.

cheng10
  • 714
  • 7
  • 10
nosklo
  • 217,122
  • 57
  • 293
  • 297
  • 15
    To make sure the foreground contains transparency in all cases, use `foreground.convert('RGBA')` for the mask parameter. – Mark Ransom Oct 04 '12 at 02:28
  • Wow! This solution did the trick instantly two weeks ago, but now I totally forgot it already, and now I see it again, it again does the trick! – physicalattraction Jun 30 '15 at 20:13
  • 23
    I'm getting ```ValueError: bad transparency mask``` – Deniz Ozger Mar 03 '16 at 15:48
  • 1
    Depending on your version, you may have to install the Python Image Library, and:' from PIL import Image` – William H. Hooper Jul 23 '16 at 17:57
  • 1
    Amazing, you can use image as mask. But can I use vectorial images like svg? – Beatriz Fonseca Sep 14 '16 at 01:57
  • 1
    `TypeError: paste() got an unexpected keyword argument 'alpha'` – Schütze Aug 08 '18 at 06:11
  • 12
    @DenizOzger To fix `ValueError: bad transparency mask` use `bg.paste(fg, (0, 0), fg.convert('RGBA'))` – Mingwei Samuel Jan 01 '19 at 03:01
  • 1
    Thank you for indicating the information about the mask and the link to the documentation appreciate it. – user3613932 May 25 '19 at 22:05
  • 1
    this does not work as expected, it is not a proper overlay for a transparent image. fading transparency gives ugly results. It transparency is ON/OFF you should get what you expect though – yota May 01 '20 at 09:57
  • 1
    Thank you for sharing. In my case "import Image" doesn't work. I had to use "from PIL import Image" instead. – Luis Jose May 24 '20 at 14:03
  • This doesn't work. paste() blits each channel (including alpha) using the mask as a weight, which means pasting a transparent image on top of an opaque one can cause opaque pixels to become transparent. It just happens to work in limited cases, but it's not alpha blending. At least 450 people have been confused by this so far, so it'd be nice to add a warning to the answer. – Glenn Maynard Mar 07 '23 at 23:17
98

Image.paste does not work as expected when the background image also contains transparency. You need to use real Alpha Compositing.

Pillow 2.0 contains an alpha_composite function that does this.

background = Image.open("test1.png")
foreground = Image.open("test2.png")

Image.alpha_composite(background, foreground).save("test3.png")

EDIT: Both images need to be of the type RGBA. So you need to call convert('RGBA') if they are paletted, etc.. If the background does not have an alpha channel, then you can use the regular paste method (which should be faster).

olt
  • 2,267
  • 1
  • 19
  • 13
  • I just used paste() to overlay one semi-transparent image on another, with PIL, and it worked as I expected. In what way doesn't it work as you expected? – Peter Hansen Jul 25 '13 at 17:27
  • 3
    @PeterHansen, paste() doesn't work as expected "when the background image also contains transparency". – homm Nov 05 '14 at 17:19
  • 1
    @PeterHansen There is example: https://github.com/python-pillow/Pillow/issues/924#issuecomment-61848826 – homm Nov 05 '14 at 17:53
  • @homm thanks. That was so long ago I have no memory of what I tried. It seems likely I did miss the part you quoted about the background image also having transparency. – Peter Hansen Nov 05 '14 at 19:58
  • 4
    I get `ValueError: image has wrong made` as well @DenizOzger – digitaldavenyc Oct 11 '16 at 20:55
  • This worked for me whereas paste was punching a "transparent hole" through my background. – Lian May 30 '17 at 12:01
  • 2
    `ValueError: images do not match` . Because this method works only when the image sizes are same – Trect Nov 28 '19 at 15:30
  • yup, you're right. both my bg and fg had transparency. the portion of my fg that contained color is supposed to be pasted on a transparent region of the bg. when `.paste` is used the fg becomes very grainy and off. alpha_composite worked perfectly – SpectraXCD Sep 12 '22 at 18:07
87

As olt already pointed out, Image.paste doesn't work properly, when source and destination both contain alpha.

Consider the following scenario:

Two test images, both contain alpha:

enter image description here enter image description here

layer1 = Image.open("layer1.png")
layer2 = Image.open("layer2.png")

Compositing image using Image.paste like so:

final1 = Image.new("RGBA", layer1.size)
final1.paste(layer1, (0,0), layer1)
final1.paste(layer2, (0,0), layer2)

produces the following image (the alpha part of the overlayed red pixels is completely taken from the 2nd layer. The pixels are not blended correctly):

enter image description here

Compositing image using Image.alpha_composite like so:

final2 = Image.new("RGBA", layer1.size)
final2 = Image.alpha_composite(final2, layer1)
final2 = Image.alpha_composite(final2, layer2)

produces the following (correct) image:

enter image description here

P.Melch
  • 8,066
  • 43
  • 40
18

One can also use blending:

im1 = Image.open("im1.png")
im2 = Image.open("im2.png")
blended = Image.blend(im1, im2, alpha=0.5)
blended.save("blended.png")
nvd
  • 2,995
  • 28
  • 16
3

Had a similar question and had difficulty finding an answer. The following function allows you to paste an image with a transparency parameter over another image at a specific offset.

import Image

def trans_paste(fg_img,bg_img,alpha=1.0,box=(0,0)):
    fg_img_trans = Image.new("RGBA",fg_img.size)
    fg_img_trans = Image.blend(fg_img_trans,fg_img,alpha)
    bg_img.paste(fg_img_trans,box,fg_img_trans)
    return bg_img

bg_img = Image.open("bg.png")
fg_img = Image.open("fg.png")
p = trans_paste(fg_img,bg_img,.7,(250,100))
p.show()
lee
  • 411
  • 5
  • 5
3

Here is my code to merge 2 images of different sizes, each with transparency and with offset:

from PIL import Image

background = Image.open('image1.png')
foreground = Image.open("image2.png")

x = background.size[0]//2
y = background.size[1]//2

background = Image.alpha_composite(
    Image.new("RGBA", background.size),
    background.convert('RGBA')
)

background.paste(
    foreground,
    (x, y),
    foreground
)

background.show()

This snippet is a mix of the previous answers, blending elements with offset while handling images with different sizes, each with transparency.

Krystof
  • 185
  • 1
  • 11
2
def trans_paste(bg_img,fg_img,box=(0,0)):
    fg_img_trans = Image.new("RGBA",bg_img.size)
    fg_img_trans.paste(fg_img,box,mask=fg_img)
    new_img = Image.alpha_composite(bg_img,fg_img_trans)
    return new_img
  • 3
    Hi, can you possibly add a little more context to your answer? Otherwise the requestor is not likely to learn the "why" behind it. – jimf Apr 28 '19 at 07:22
1

the key code is:

_, _, _, alpha = image_element_copy.split()
image_bg_copy.paste(image_element_copy, box=(x0, y0, x1, y1), mask=alpha)

the full function is:

def paste_image(image_bg, image_element, cx, cy, w, h, rotate=0, h_flip=False):
    image_bg_copy = image_bg.copy()
    image_element_copy = image_element.copy()

    image_element_copy = image_element_copy.resize(size=(w, h))
    if h_flip:
        image_element_copy = image_element_copy.transpose(Image.FLIP_LEFT_RIGHT)
    image_element_copy = image_element_copy.rotate(rotate, expand=True)
    _, _, _, alpha = image_element_copy.split()
    # image_element_copy's width and height will change after rotation
    w = image_element_copy.width
    h = image_element_copy.height
    x0 = cx - w // 2
    y0 = cy - h // 2
    x1 = x0 + w
    y1 = y0 + h
    image_bg_copy.paste(image_element_copy, box=(x0, y0, x1, y1), mask=alpha)
    return image_bg_copy

the above function supports:

  • position(cx, cy)
  • auto resize image_element to (w, h)
  • rotate image_element without cropping it
  • horizontal flip