简体   繁体   中英

How to merge a transparent png image with another image using PIL

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 )

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 .

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).

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:

在此处输入图片说明 在此处输入图片说明

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):

在此处输入图片说明

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:

在此处输入图片说明

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

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()
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

I ended up coding myself the suggestion of this comment made by the user @P.Melch and suggested by @Mithril on a project I'm working on.

I coded out of bounds safety as well, here's the code for it . (I linked a specific commit because things can change in the future of this repository)

Note: I expect numpy arrays from the images like so np.array(Image.open(...)) as the inputs A and B from copy_from and this linked function overlay arguments.

The dependencies are the function right before it, the copy_from method, and numpy arrays as the PIL Image content for slicing.

Though the file is very class oriented, if you want to use that function overlay_transparent , be sure to rename the self.frame to your background image numpy array.

Or you can just copy the whole file (probably remove some imports and the Utils class) and interact with this Frame class like so:

# Assuming you named the file frame.py in the same directory
from frame import Frame

background = Frame()
overlay = Frame()

background.load_from_path("your path here")
overlay.load_from_path("your path here")

background.overlay_transparent(overlay.frame, x=300, y=200)

Then you have your background.frame as the overlayed and alpha composited array, you can get a PIL image from it with overlayed = Image.fromarray(background.frame) or something like:

overlayed = Frame()
overlayed.load_from_array(background.frame)

Or just background.save("save path") as that takes directly from the alpha composited internal self.frame variable.

You can read the file and find some other nice functions with this implementation I coded like the methods get_rgb_frame_array , resize_by_ratio , resize_to_resolution , rotate , gaussian_blur , transparency , vignetting :)

You'd probably want to remove the resolve_pending method as that is specific for that project.

Glad if I helped you, be sure to check out the repo of the project I'm talking about, this question and thread helped me a lot on the development :)

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM