【Pythonでサムネ制作②】PILで画像の上に透過画像を重ねる

はじめに
こんなことやります。 - 画像の上に画像を重ねる - 画像の位置を調整 - 画像の大きさをリサイズして重ねる - 透過画像と文字を画像の上に重ねる - YAMLで管理してWatchdogで自動生成
この記事は、ブログサイトのアイキャッチ画像(サムネ)をPythonで自動生成する企画の第二弾になります。 前回は をやりました。今回は、画像の上に透過画像を重ねるにはどうすればよいかを解説していきますね!
このページのアイキャッチ画像は、今回ご紹介する方法でPythonによって自動生成したものです。文言の修正や色使いなどをYAMLで管理して、YAMLが更新されたら Watchdogでイベントフック してコンパイルさせてます。
画像の上に画像を重ねる
それではさっそく、画像の上に画像を重ねていきます。まずは「ベースとなる画像」とその上に「重ねたい画像」を用意します。
↑この画像に↓こちらのロゴ画像を重ねていきます。ロゴ画像はPNGファイルで作られており、実際の背景は透過であります。

プログラムはこんな感じです。めちゃ簡単ですね。
from PIL import Image
= 'panda.jpg' # ベース画像
base_path = 'python-logo.png' # 重ねる透過画像
logo_path = 'out.jpg' # 出力ファイル
out_path
= Image.open(base_path)
base = Image.open(logo_path)
logo
0, 0), logo)
base.paste(logo, ( base.save(out_path)
プログラムを実行すると↓こんな感じで画像の上に画像を重ねることができました。

プログラミングのポイント
プログラム中の paste()
は、画像に画像を重ねるためのメソッドです。重ねたい画像を第一引数に指定し、位置をタプルで第二引数にしていします。
また、paste()
メソッドの第三引数はマスク画像を指定するためのものです。透過画像自身をマスク画像とすることで、透明部分が表現できているワケですね!
仮に、paste()
メソッドの第三引数を省略して出力してみますと、↓下の写真のように透過部分が黒く塗りつぶされてしまいます。

画像の位置を調整
次に重なる画像の位置を調整してみます。左下にロゴ画像を配置したいので、↓こんな感じでプログラミングしてみました。
from PIL import Image
= 'panda.jpg' # ベース画像
base_path = 'python-logo.png' # 重ねる透過画像
logo_path = 'out.jpg' # 出力ファイル
out_path
= Image.open(base_path)
base = Image.open(logo_path)
logo
= base.size
base_w, base_h = logo.size
logo_w, logo_h
0, base_h - logo_h), logo)
base.paste(logo, ( base.save(out_path)
画像の大きさをリサイズして重ねる
今度は重なる画像の大きさをリサイズして重ねてみましょう。画像を少し大きくして重ねるにはこんな感じのプログラミングになります。
from PIL import Image
= 'panda.jpg' # ベース画像
base_path = 'python-logo.png' # 重ねる透過画像
logo_path = 'out.jpg' # 出力ファイル
out_path
= Image.open(base_path)
base = Image.open(logo_path)
logo
= base.size
base_w, base_h = logo.size
logo_w, logo_h
= 1.5
scale = logo.resize((int(logo_w * scale), int(logo_h * scale))) # リサイズ
logo_resized = logo_resized.size
logo_resized_w, logo_resized_h
0, base_h - logo_resized_h), logo_resized)
base.paste(logo_resized, ( base.save(out_path)
はい。↓ロゴが少し大きくなって重ねることができました。
プログラミングのポイント
resize()
メソッドにタプルでリサイズしたいwidth、heightの値を渡します。ただしint型でないとエラーになりますのでご注意ください。
ちなみに、画像の一部分をくり抜きたい場合は crop()
を使います。
透過画像と文字を画像の上に重ねる
応用編としまして、前回の でやったテキストも組み合わせて、透過画像と文字を画像の上に重ねてみましょう。
位置決めなどはハードコーディングで調整する必要がありますが、できるだけ関数にまとめました。
from PIL import Image,ImageFont,ImageDraw
def overlay_text(base_img, text, font_path, font_size, font_color, stroke_color, stroke_width):
= ImageFont.truetype(font_path, font_size)
font = ImageDraw.Draw(base_img)
draw
= font.getsize(text, stroke_width=stroke_width)
font_w, font_h = base_img.size
base_img_w, base_img_h = 40
margin_left = 50
margin_bottom
= (margin_left, base_img_h - font_h - margin_bottom)
pos
draw.text(
pos, text,=font,
font=stroke_color,
fill=stroke_width * 2,
stroke_width=font_color)
stroke_fill
draw.text(
pos, text,=font,
font=font_color,
fill=stroke_width,
stroke_width=stroke_color)
stroke_fill
return base_img
def overlay_logo(base_img, path, scale):
= Image.open(path)
logo = base_img.size
base_w, base_h = logo.size
logo_w, logo_h
= logo.resize((int(logo_w * scale), int(logo_h * scale))) # リサイズ
logo_resized = logo_resized.size
logo_resized_w, logo_resized_h = 170
margin_bottom 0, base_h - logo_resized_h - margin_bottom), logo_resized)
base_img.paste(logo_resized, (return base_img
= 'panda.jpg' # ベース画像
base_path = 'python-logo.png' # 重ねる透過画像
logo_path = 'out.jpg' # 出力ファイル
out_path
= 'Pandasでデータ解析・使い方'
text = '/System/Library/Fonts/ヒラギノ角ゴシック W7.ttc'
font_path = 100
font_size = (255,248,196) # 文字の色
font_color = (112, 96, 85) # 枠線の色
stroke_color = 10
stroke_width
= Image.open(base_path)
base = overlay_logo(base, logo_path, 1.0)
base = overlay_text(base, text, font_path, font_size, font_color, stroke_color, stroke_width)
base base.save(out_path)
はい。↓サムネの完成でございます。

これでサムネを半自動化で作れるようになりましたね!
プログラミングのポイント
テキストの枠線を二重に重ねるには、 draw.text()
で stroke_width
の太さを変えて二回呼び出しているのがポイントです。
YAMLで管理してWatchdogで自動生成
さいごに「おまけ」としまして、このサイトを生成している自作ジェネレータのサムネ自動化プログラムをご紹介します。ここまで解説した技術と の技術を組み合わせてサムネを自動生成させてます。
import yaml
import os
import conf
from PIL import Image,ImageFont,ImageDraw
import time
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
class WatchdogHandler(PatternMatchingEventHandler):
def __init__(self, callback, patterns):
super(WatchdogHandler, self).__init__(patterns=patterns)
self.callback = callback
def __callback_handler(self, func,*args):
return func(*args)
def on_modified(self, event):
print(event)
self.__callback_handler(self.callback)
def watch(path, command, extensions):
= WatchdogHandler(command, extensions)
event_handler = Observer()
observer =True)
observer.schedule(event_handler, path, recursive
observer.start()try:
while True:
1)
time.sleep(except KeyboardInterrupt:
observer.stop()
observer.join()
def __get_fit_font_size(text, font_path, stroke_width, font_size = 200, limit_width = 1920, margin = 200):
= ImageFont.truetype(font_path, font_size)
font = font.getsize(text, stroke_width=stroke_width)
w, h if w < (limit_width - margin):
return font_size
print('{}, {}, {}, {}'.format(text, font_path, stroke_width, font_size - 5))
return __get_fit_font_size(text, font_path, stroke_width, font_size - 5)
def __overlay_text(base_img, text, font_path, font_size, font_color, stroke_color, stroke_width):
= ImageFont.truetype(font_path, font_size)
font = ImageDraw.Draw(base_img)
draw
= font.getsize(text, stroke_width=stroke_width)
font_w, font_h = base_img.size
base_img_w, base_img_h = 40
margin_left = 50
margin_bottom
= ((base_img_w - font_w)/2, (base_img_h - font_h)/2)
pos
draw.text(
pos, text,=font,
font=font_color,
fill=stroke_width,
stroke_width=stroke_color)
stroke_fill
return base_img
def __overlay_logo(base_img, path, scale):
= Image.open(path)
logo = base_img.size
base_w, base_h = logo.size
logo_w, logo_h = logo.resize((int(logo_w * scale), int(logo_h * scale))) # リサイズ
logo_resized = logo_resized.size
logo_resized_w, logo_resized_h = 20
margin_right = 50
margin_bottom - logo_resized_w - margin_right, base_h - logo_resized_h - margin_bottom), logo_resized)
base_img.paste(logo_resized, (base_w return base_img
def make(target_dir):
= target_dir + '/' + 'eyecatch.yml'
yml_path with open(yml_path) as file:
= yaml.safe_load(file)
cf
= target_dir + '/' + cf['base_path']
base_path = conf.LOGO_DIR + '/' + cf['logo_path']
logo_path = target_dir + '/' + cf['out_path']
out_path
= cf['text']
text = cf['font_path']
font_path
= tuple(cf['font_color'])
font_color = tuple(cf['stroke_color'])
stroke_color = cf['stroke_width']
stroke_width
= __get_fit_font_size(text, font_path, stroke_width)
font_size = Image.open(base_path)
base = __overlay_logo(base, logo_path, 0.9)
base = __overlay_text(base, text, font_path, font_size, font_color, stroke_color, stroke_width)
base
base.save(out_path)
class WatchdogHandler(PatternMatchingEventHandler):
def __init__(self, patterns):
super(WatchdogHandler, self).__init__(patterns=patterns)
def on_modified(self, event):
= os.path.dirname(event.src_path)
target_dir
make(target_dir)
if __name__ == "__main__":
= conf.SOURCES_DIR + '/blog'
dir_to_watch = ["*.yml"]
extensions
= WatchdogHandler(extensions)
event_handler = Observer()
observer =True)
observer.schedule(event_handler, dir_to_watch, recursive
observer.start()try:
while True:
1)
time.sleep(except KeyboardInterrupt:
observer.stop()
observer.join()
↓ YAMLファイルはこんな感じです。
common_yaml: develop.yml
text: "画像を重ねる"
subtext: "Pythonでサムネ制作への道"
icon_path: file-format.png
stroke_width: 10
プログラムの詳細は解説しませんが、一つ一つの技術は難しいものではありません。
関連記事
- ImageMagickで画像加工|シェル
- 【Pythonでサムネ制作①】PILで画像の上に文字を重ねて中央表示
- Composableでテキストフィールドをつくる【Android アプリ開発】
- fdupes で内容が重複しているファイルを見つける|シェル
- ESP-IDFでESP32の開発をはじめよう!