VMware ESXi and vSphere Cluster Management

Combine and Overlay Images with Pillow

Learn how to combine images in Python with Pillow using Image.paste(), coordinates, transparent PNG masks, resizing, watermark placement, and safe output formats.

What image compositing means

Image compositing means placing a foreground image, called an overlay image, over a background or base image. The base image is the canvas that receives the overlay.

Common uses include adding logos, badges, stickers, labels, and watermarks. A watermark is a logo or other mark placed over an image for branding or attribution.

Overlaying is different from joining images side by side. It is also different from blending, where pixels from two images are mathematically combined to create an intermediate appearance. With paste(), you place overlay pixels at a chosen location on the base image.

Install Pillow and open the images

Pillow is a Python imaging library used to open, edit, compose, and save image files. Install it with:

python -m pip install Pillow

Import the Image module, then load the base and overlay files with Image.open():

from PIL import Image

base = Image.open("photo.jpg")
overlay = Image.open("logo.png")

The image whose paste() method you call is the image that changes. In this example, base is modified; overlay is only used as the source image.

Use Image.paste()

The basic form is:

base_image.paste(overlay_image, position)

position is a coordinate tuple such as (51, 1480). It specifies where the overlay's upper-left corner should be placed.

ArgumentTypical valuePurpose
source imageoverlayThe image data placed on the base.
position tuple(51, 1480)The destination coordinates for the overlay's upper-left corner.
optional maskoverlay or overlay.getchannel("A")Controls which overlay pixels are applied, especially for transparency.

paste() changes the base image in memory. It does not return a new composited image and it does not save a file automatically.

from PIL import Image

base = Image.open("photo.jpg")
overlay = Image.open("logo.png")

base.paste(overlay, (51, 1480), overlay)
base.save("combined.png")

Understand Pillow coordinates

Pillow uses a coordinate system whose origin, (0, 0), is at the upper-left corner of the image. The x value increases from left to right, and the y value increases from top to bottom.

For example, (51, 1480) places the overlay's upper-left corner 51 pixels from the left edge and 1,480 pixels from the top edge.

Calculate common positions

Use the dimensions of the base and overlay instead of guessing coordinates. The base image's size is available through base.width and base.height; the overlay has corresponding properties.

Desired placementx calculationy calculation
top-leftmarginmargin
top-rightbase.width - overlay.width - marginmargin
bottom-leftmarginbase.height - overlay.height - margin
bottom-rightbase.width - overlay.width - marginbase.height - overlay.height - margin
center(base.width - overlay.width) // 2(base.height - overlay.height) // 2

A margin, also called padding, keeps the overlay away from the canvas edges. For a bottom-right watermark:

margin = 20
x = base.width - overlay.width - margin
y = base.height - overlay.height - margin

base.paste(overlay, (x, y), overlay)

If the overlay is wider or taller than the base, these calculations can produce negative coordinates. That means part of the overlay extends outside the canvas. Pillow clips pixels outside the base image, so the result may be partly missing. Checking dimensions and resizing first is more predictable.

Transparency and masks

A mask is an image or channel that controls which source pixels are applied during a paste operation. A transparent logo PNG usually contains an alpha channel, which stores transparency information.

Without a mask, transparent background pixels may be treated as ordinary image pixels. The logo can then appear with an unwanted solid rectangle. Pass the RGBA overlay as the third argument when it contains alpha information:

from PIL import Image

base = Image.open("photo.jpg").convert("RGB")
overlay = Image.open("logo.png").convert("RGBA")

base.paste(overlay, (51, 1480), overlay)
base.save("watermarked.png")

Here, the third argument is the mask. Pillow uses the overlay's alpha channel to decide how strongly each pixel is applied. An opaque pixel replaces the corresponding base pixel, while a transparent pixel leaves the base unchanged.

You can extract the alpha channel explicitly with getchannel("A"):

overlay = Image.open("logo.png").convert("RGBA")
mask = overlay.getchannel("A")
base.paste(overlay, (x, y), mask)

Both approaches are useful. Passing overlay is concise; using getchannel("A") makes the mask's role explicit. A JPEG does not preserve alpha transparency, so it is not a suitable source for a transparent logo unless its background is acceptable.

Image modes and compatibility

An image mode describes the channels stored for every pixel.

  • RGB contains red, green, and blue channels. It is common for photographs and has no transparency.
  • RGBA contains red, green, blue, and an alpha channel for transparency.
  • L contains one grayscale or luminance channel. It is also commonly used for masks.

Images from different files may use different modes. Convert them deliberately when composing:

base = Image.open("photo.jpg").convert("RGB")
overlay = Image.open("logo.png").convert("RGBA")
mask = overlay.getchannel("A")
base.paste(overlay, (x, y), mask)

Mode problems can cause paste errors, unexpected colors, or missing transparency. An opaque base can normally be kept as RGB. An overlay that needs transparency should be RGBA, and its alpha channel can provide the mask.

Resize an oversized overlay

Check both images before placing the overlay:

print("Base:", base.size)
print("Overlay:", overlay.size)

A logo that covers too much of a photograph should be resized before its final position is calculated. Preserve its aspect ratio by supplying only a maximum width or height to thumbnail():

max_width = base.width // 4
max_height = base.height // 4

overlay.thumbnail((max_width, max_height))

thumbnail() changes the overlay in place and keeps its aspect ratio. If you need a new image with more control, calculate a proportional height and use resize(). Recalculate x and y after resizing because the overlay dimensions have changed.

margin = 20
x = base.width - overlay.width - margin
y = base.height - overlay.height - margin
base.paste(overlay, (x, y), overlay)

Complete watermark workflow

A reusable watermark workflow is:

  1. Load the photograph as the base image.
  2. Load the transparent logo as an RGBA overlay.
  3. Check dimensions and resize the logo if necessary.
  4. Choose a margin and calculate the desired coordinates.
  5. Paste the logo with its alpha mask.
  6. Preview the edited image during local testing.
  7. Save the result in a format suitable for the desired transparency behavior.
from PIL import Image

base = Image.open("photo.jpg").convert("RGB")
overlay = Image.open("logo.png").convert("RGBA")

overlay.thumbnail((base.width // 4, base.height // 4))
margin = 20
x = margin
y = base.height - overlay.height - margin

base.paste(overlay, (x, y), overlay)
base.show()
base.save("watermarked.png")

Changing only the coordinate calculations lets the same workflow place the watermark in any corner or in the center.

Preview and save the result

Use show() for a quick local preview:

base.show()

Save the modified base image with save(). Choose the output format according to whether transparency must remain available.

FormatTransparency supportTypical use
PNGSupports alpha transparency.Transparent graphics, logos, overlays, and results that must retain alpha.
JPEGDoes not support alpha transparency.Opaque photographic output with smaller file sizes.

If the final image is RGBA and the destination is JPEG, convert it to RGB first. Transparent pixels must be replaced by an opaque color or composited over an opaque base:

final_image = base.convert("RGB")
final_image.save("watermarked.jpg", quality=90)

Save as PNG instead when the output itself needs transparent pixels.

Troubleshooting

Unwanted rectangular logo background

The overlay may have been pasted without a mask, or the source file may not contain real transparency. Use an RGBA PNG and pass the overlay or overlay.getchannel("A") as the third argument.

Watermark is partly missing or outside the image

The overlay may extend beyond the base canvas because its coordinates were guessed or calculated before resizing. Compare both image sizes and use the base dimensions, overlay dimensions, and a margin to calculate the position.

Logo is too large

Resize it proportionally before positioning. A maximum size based on a fraction of the base width or height is often more reliable than a fixed pixel size.

JPEG saving fails or transparency disappears

JPEG cannot store an alpha channel. Save as PNG to retain transparency, or convert the final image to RGB before saving as JPEG.

Incompatible image modes

Convert the base to RGB or RGBA and the overlay to RGBA when alpha transparency is required. Use the overlay's alpha channel as the mask.

The edited image was not saved

paste() only changes the base image in memory. Call save() after compositing and provide an output filename.

Exam-relevant notes

  • The base image receives the overlay, so call base.paste(...).
  • The position tuple identifies the overlay's upper-left corner.
  • The coordinate origin is the upper-left; x increases rightward and y increases downward.
  • The third paste() argument is an optional mask, commonly an RGBA overlay or its alpha channel.
  • paste() mutates the base image and does not automatically save a file.
  • Use PNG for retained transparency and convert RGBA output to RGB before JPEG encoding.

For related composition concepts, see Combine Images.