【Pythonでサムネ制作②】PILで画像の上に透過画像を重ねる
こんなことやります。
- 画像の上に画像を重ねる
- 画像の位置を調整
- 画像の大きさをリサイズして重ねる
- 透過画像と文字を画像の上に重ねる
- YAMLで管理してWatchdogで自動生成
この記事は、ブログサイトのアイキャッチ画像(サムネ)をPythonで自動生成する企画の第二弾になります。 前回は 【Pythonでサムネ制作①】PILで画像の上に文字を重ねて中央表示 をやりました。今回は、画像の上に透過画像を重ねるにはどうすればよいかを解説していきますね!
このページのアイキャッチ画像は、今回ご紹介する方法でPythonによって自動生成したものです。文言の修正や色使いなどをYAMLで管理して、YAMLが更新されたら Watchdogでイベントフック してコンパイルさせてます。
画像の上に画像を重ねる
それではさっそく、画像の上に画像を重ねていきます。まずは「ベースとなる画像」とその上に「重ねたい画像」を用意します。
↑この画像に↓こちらのロゴ画像を重ねていきます。ロゴ画像はPNGファイルで作られており、実際の背景は透過であります。
プログラムはこんな感じです。めちゃ簡単ですね。
from PIL import Image
base_path = 'panda.jpg' # ベース画像
logo_path = 'python-logo.png' # 重ねる透過画像
out_path = 'out.jpg' # 出力ファイル
base = Image.open(base_path)
logo = Image.open(logo_path)
# base.paste(logo, (0, 0))
base.paste(logo, (0, 0), logo)
base.save(out_path)
プログラムを実行すると↓こんな感じで画像の上に画像を重ねることができました。
プログラミングのポイント
プログラム中の paste() は、画像に画像を重ねるためのメソッドです。重ねたい画像を第一引数に指定し、位置をタプルで第二引数にしていします。
また、paste() メソッドの第三引数はマスク画像を指定するためのものです。透過画像自身をマスク画像とすることで、透明部分が表現できているワケですね!
仮に、paste() メソッドの第三引数を省略して出力してみますと、↓下の写真のように透過部分が黒く塗りつぶされてしまいます。
画像の位置を調整
次に重なる画像の位置を調整してみます。左下にロゴ画像を配置したいので、↓こんな感じでプログラミングしてみました。
from PIL import Image
base_path = 'panda.jpg' # ベース画像
logo_path = 'python-logo.png' # 重ねる透過画像
out_path = 'out.jpg' # 出力ファイル
base = Image.open(base_path)
logo = Image.open(logo_path)
base_w, base_h = base.size
logo_w, logo_h = logo.size
# print('base_w:{}, base_h:{}'.format(base_w, base_h))
# print('logo_w:{}, logo_h:{}'.format(logo_w, logo_h))
base.paste(logo, (0, base_h - logo_h), logo)
base.save(out_path)
プログラムを実行します。左下にロゴが表示されましたね。位置設定は 【Pythonでサムネ制作①】PILで画像の上に文字を重ねて中央表示 でもやりましたので簡単ですね。
画像の大きさをリサイズして重ねる
今度は重なる画像の大きさをリサイズして重ねてみましょう。画像を少し大きくして重ねるにはこんな感じのプログラミングになります。
from PIL import Image
base_path = 'panda.jpg' # ベース画像
logo_path = 'python-logo.png' # 重ねる透過画像
out_path = 'out.jpg' # 出力ファイル
base = Image.open(base_path)
logo = Image.open(logo_path)
base_w, base_h = base.size
logo_w, logo_h = logo.size
scale = 1.5
logo_resized = logo.resize((int(logo_w * scale), int(logo_h * scale))) # リサイズ
logo_resized_w, logo_resized_h = logo_resized.size
base.paste(logo_resized, (0, base_h - logo_resized_h), logo_resized)
base.save(out_path)
はい。↓ロゴが少し大きくなって重ねることができました。
プログラミングのポイント
resize() メソッドにタプルでリサイズしたいwidth、heightの値を渡します。ただしint型でないとエラーになりますのでご注意ください。 ちなみに、画像の一部分をくり抜きたい場合は crop() を使います。透過画像と文字を画像の上に重ねる
応用編としまして、前回の 【Pythonでサムネ制作①】PILで画像の上に文字を重ねて中央表示 でやったテキストも組み合わせて、透過画像と文字を画像の上に重ねてみましょう。
位置決めなどはハードコーディングで調整する必要がありますが、できるだけ関数にまとめました。
from PIL import Image,ImageFont,ImageDraw
def overlay_text(base_img, text, font_path, font_size, font_color, stroke_color, stroke_width):
font = ImageFont.truetype(font_path, font_size)
draw = ImageDraw.Draw(base_img)
font_w, font_h = font.getsize(text, stroke_width=stroke_width)
base_img_w, base_img_h = base_img.size
margin_left = 40
margin_bottom = 50
pos = (margin_left, base_img_h - font_h - margin_bottom)
draw.text(
pos, text,
font=font,
fill=stroke_color,
stroke_width=stroke_width * 2,
stroke_fill=font_color)
draw.text(
pos, text,
font=font,
fill=font_color,
stroke_width=stroke_width,
stroke_fill=stroke_color)
return base_img
def overlay_logo(base_img, path, scale):
logo = Image.open(path)
base_w, base_h = base_img.size
logo_w, logo_h = logo.size
logo_resized = logo.resize((int(logo_w * scale), int(logo_h * scale))) # リサイズ
logo_resized_w, logo_resized_h = logo_resized.size
margin_bottom = 170
base_img.paste(logo_resized, (0, base_h - logo_resized_h - margin_bottom), logo_resized)
return base_img
base_path = 'panda.jpg' # ベース画像
logo_path = 'python-logo.png' # 重ねる透過画像
out_path = 'out.jpg' # 出力ファイル
text = 'Pandasでデータ解析・使い方'
font_path = '/System/Library/Fonts/ヒラギノ角ゴシック W7.ttc'
font_size = 100
font_color = (255,248,196) # 文字の色
stroke_color = (112, 96, 85) # 枠線の色
stroke_width = 10
base = 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.save(out_path)
はい。↓サムネの完成でございます。
これでサムネを半自動化で作れるようになりましたね!
プログラミングのポイント
テキストの枠線を二重に重ねるには、 draw.text() で stroke_width の太さを変えて二回呼び出しているのがポイントです。
YAMLで管理してWatchdogで自動生成
さいごに「おまけ」としまして、このサイトを生成している自作ジェネレータのサムネ自動化プログラムをご紹介します。ここまで解説した技術と ファイル更新を監視してイベントフックする【Python x 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):
event_handler = WatchdogHandler(command, extensions)
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
def __get_fit_font_size(text, font_path, stroke_width, font_size = 200, limit_width = 1920, margin = 200):
font = ImageFont.truetype(font_path, font_size)
w, h = font.getsize(text, stroke_width=stroke_width)
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):
font = ImageFont.truetype(font_path, font_size)
draw = ImageDraw.Draw(base_img)
font_w, font_h = font.getsize(text, stroke_width=stroke_width)
base_img_w, base_img_h = base_img.size
margin_left = 40
margin_bottom = 50
pos = ((base_img_w - font_w)/2, (base_img_h - font_h)/2)
draw.text(
pos, text,
font=font,
fill=font_color,
stroke_width=stroke_width,
stroke_fill=stroke_color)
return base_img
def __overlay_logo(base_img, path, scale):
logo = Image.open(path)
base_w, base_h = base_img.size
logo_w, logo_h = logo.size
logo_resized = logo.resize((int(logo_w * scale), int(logo_h * scale))) # リサイズ
logo_resized_w, logo_resized_h = logo_resized.size
margin_right = 20
margin_bottom = 50
base_img.paste(logo_resized, (base_w - logo_resized_w - margin_right, base_h - logo_resized_h - margin_bottom), logo_resized)
return base_img
def make(target_dir):
yml_path = target_dir + '/' + 'eyecatch.yml'
with open(yml_path) as file:
cf = yaml.safe_load(file)
base_path = target_dir + '/' + cf['base_path']
logo_path = conf.LOGO_DIR + '/' + cf['logo_path']
out_path = target_dir + '/' + cf['out_path']
text = cf['text']
font_path = cf['font_path']
font_color = tuple(cf['font_color'])
stroke_color = tuple(cf['stroke_color'])
stroke_width = cf['stroke_width']
font_size = __get_fit_font_size(text, font_path, stroke_width)
base = 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.save(out_path)
class WatchdogHandler(PatternMatchingEventHandler):
def __init__(self, patterns):
super(WatchdogHandler, self).__init__(patterns=patterns)
def on_modified(self, event):
target_dir = os.path.dirname(event.src_path)
make(target_dir)
if __name__ == "__main__":
dir_to_watch = conf.SOURCES_DIR + '/blog'
extensions = ["*.yml"]
event_handler = WatchdogHandler(extensions)
observer = Observer()
observer.schedule(event_handler, dir_to_watch, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
↓ YAMLファイルはこんな感じです。
common_yaml: develop.yml
text: "画像を重ねる"
subtext: "Pythonでサムネ制作への道"
icon_path: file-format.png
stroke_width: 10
プログラムの詳細は解説しませんが、一つ一つの技術は難しいものではありません。