【Python】Pillowで画像を合成する:pasteメソッドと透過PNGの重ね合わせ

画像の上に別の画像を重ねる処理(スーパーインポーズ)は、著作権表示(ウォーターマーク)の追加や、商品画像への「SALE」バッジの合成などで頻繁に使用されます。

Pillowの paste() メソッドを使用しますが、特に背景が透明な画像(PNGなど)をきれいに重ねるには、第3引数(マスク) の指定が不可欠です。ここでは、背景画像上の指定した位置に、透過情報を維持したままロゴ画像を合成する方法を解説します。

目次

実行可能なサンプルコード

以下のコードは、メインとなる写真の右下に、半透明のロゴマークを合成して保存するスクリプトです。 座標計算を行い、画像のサイズが変わっても常に右下に配置されるように実装しています。

from PIL import Image, ImageDraw
import os

def composite_images_demo():
    # ファイル名の定義
    base_image_path = "scenery_photo.jpg"
    overlay_image_path = "watermark_logo.png"
    output_path = "photo_with_watermark.jpg"

    # 動作確認用の画像を生成(お手持ちの画像がある場合は不要)
    if not os.path.exists(base_image_path):
        _create_base_photo(base_image_path)
    if not os.path.exists(overlay_image_path):
        _create_watermark_icon(overlay_image_path)

    try:
        # 1. 画像の読み込み
        # ベース画像(背景)
        base_img = Image.open(base_image_path)
        
        # 重ねる画像(前景)
        # 透過情報を扱うため、convert("RGBA") で読み込むのが安全です
        overlay_img = Image.open(overlay_image_path).convert("RGBA")

        # 2. 貼り付け位置の計算
        # 今回は「右下」に配置します(余白を20px空ける)
        margin = 20
        base_w, base_h = base_img.size
        overlay_w, overlay_h = overlay_img.size
        
        # 座標 = (背景幅 - ロゴ幅 - 余白, 背景高さ - ロゴ高さ - 余白)
        position = (base_w - overlay_w - margin, base_h - overlay_h - margin)

        # 3. 画像の貼り付け (paste)
        # base_img自体が変更される破壊的メソッドなので、
        # 必要であれば事前に .copy() してください。
        #
        # 【重要】第3引数(mask)に同じ画像を渡すことで、アルファチャンネル(透明度)が適用されます。
        # これを省略すると、透明部分が黒や白で塗りつぶされてしまいます。
        base_img.paste(overlay_img, position, mask=overlay_img)

        # 4. 保存
        # JPEG形式で保存する場合、RGBモードである必要があります
        # (ベースが元々JPEGならRGBのままですが、念のため確認)
        if base_img.mode != "RGB":
            base_img = base_img.convert("RGB")
            
        base_img.save(output_path, quality=95)
        print(f"合成画像を保存しました: {output_path}")

    except Exception as e:
        print(f"エラーが発生しました: {e}")

def _create_base_photo(filename):
    """ベースとなる風景写真(ダミー)を作成"""
    img = Image.new("RGB", (600, 400), color=(100, 150, 200)) # 水色
    draw = ImageDraw.Draw(img)
    draw.rectangle((0, 300, 600, 400), fill=(50, 100, 50)) # 緑の大地
    img.save(filename)

def _create_watermark_icon(filename):
    """重ねるための円形ロゴ(透過あり)を作成"""
    # 背景透明(0,0,0,0)のキャンバスを作成
    size = 100
    img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
    draw = ImageDraw.Draw(img)
    
    # 半透明の赤い円を描画
    draw.ellipse((0, 0, size, size), fill=(255, 100, 100, 200))
    # 文字
    draw.text((20, 40), "COPY", fill="white")
    
    img.save(filename)

if __name__ == "__main__":
    composite_images_demo()

解説:pasteメソッドとマスク指定

base_image.paste(im, box, mask) メソッドの引数は以下の通りです。

  1. im: 貼り付ける画像オブジェクト(ソース)。
  2. box: 貼り付ける位置。(x, y) のタプル。
  3. mask (任意): マスク用画像。

なぜ mask が必要なのか?

mask 引数を指定せずに透過PNGを貼り付けると、透明な背景部分が「不透明な色(通常は黒)」として上書きされてしまいます。 ソース画像(overlay_img)がアルファチャンネル(透明度情報)を持っている場合、その画像自身を mask 引数にも渡すことで、「透明な部分は貼り付けない(下の画像を見せる)」という正しい合成処理が行われます。

# 失敗例(透明部分が黒くなる)
base_img.paste(overlay_img, position)

# 成功例(きれいに透過合成される)
base_img.paste(overlay_img, position, mask=overlay_img)
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次