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 PillowImport 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.
| Argument | Typical value | Purpose |
|---|---|---|
| source image | overlay | The image data placed on the base. |
| position tuple | (51, 1480) | The destination coordinates for the overlay's upper-left corner. |
| optional mask | overlay 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 placement | x calculation | y calculation |
|---|---|---|
| top-left | margin | margin |
| top-right | base.width - overlay.width - margin | margin |
| bottom-left | margin | base.height - overlay.height - margin |
| bottom-right | base.width - overlay.width - margin | base.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:
- Load the photograph as the base image.
- Load the transparent logo as an RGBA overlay.
- Check dimensions and resize the logo if necessary.
- Choose a margin and calculate the desired coordinates.
- Paste the logo with its alpha mask.
- Preview the edited image during local testing.
- 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.
| Format | Transparency support | Typical use |
|---|---|---|
| PNG | Supports alpha transparency. | Transparent graphics, logos, overlays, and results that must retain alpha. |
| JPEG | Does 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;
xincreases rightward andyincreases 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.