【最小構成】pythonのpillowモジュールを使ってタピオカミルクティーの画像に「100円」の文字を合成する

#目的
きっかけはLINE Botのリッチメニュー。
これは自分で作ったとあるBotなんですが、画面下部にあるリッチメニューをタップすると、
それに見合った応答が返ってくるようになっています。

![MicrosoftTeams-image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495652/0ffcb3da-7a63-4cbc-3746-06036e4ddb53.png)

さて、今回のポイントはリッチメニューの部分です。
これ、実はボタンでもなんでもなくタダの画像です（現状LINEの仕組み上は画像で表現するしかないっぽい・・・）。

で、これが作りにくいんです。
そんなイイ感じのアプリは残念ながら持っていなかったので、パワポとペイントを使って切り貼りしています。まあそれなりにイイ出来だとは思うんですけど、時間がかかってしまいました。
（追記：最近のアップデートで、LINE Developers画面で簡単に作成できるようになっていました！）

そこで「それ、Pythonでできるんじゃね？」と思って調べていたところ
pillowなるモジュールでできそうなことが判明！
早速ですがやり方を見ていきましょう。

#インストール
Python使いならもう慣れっこですが、とりあえずモジュールのインストールを行います。

```shell
$ pip install pillow
```
ちなみにpillowはPILというモジュールのフォークプロジェクトらしいです。
公式ドキュメントは<a href="https://pillow.readthedocs.io/en/stable/index.html">ココ</a>。
今回は主にこのドキュメントを参考に、画像処理をやっていきたいと思います。


#既存の画像に文字を描く
今回はこのタピオカの画像に「100円」と入れてみたいと思います。
いきなりコードから行きます。

```python:main.py
from PIL import Image, ImageDraw, ImageFont

#元画像を読み込んでくる
image = Image.open("tapioca.png")

#文字を書きこむ為のオブジェクトが用意されているので取得する
draw = ImageDraw.Draw(image)

#フォントを指定する（フォントファイルはWindows10ならC:\\Windows\\Fontsにあります）
#sizeは文字サイズです（とりあえず適当に50）
font = ImageFont.truetype("YuGothL.ttc", size=50)

#文字を描く
#最初の(0,0)は文字の描画を開始する座標位置です　もちろん、(10,10)などでもOK
#fillはRGBで文字の色を決めています
draw.text((0, 0), "100円", fill=(255,0,0), font=font)

#画像を保存する
image.save("new_tapioca.png")
```

こんな感じになりました。
![new_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495652/0a638e1c-1c5a-d625-301b-f2abf6785c13.png)

#応用（センタリング等）
文字の配置を弄ってみようと思います。
ドキュメントを見ている限りでは、そういうオプションがあったのですがどういうわけかうまく行かなかったので、絶対にうまくいく原始的な方法を考えました。

##画像サイズを取得してみる
```python:main.py

from PIL import Image, ImageDraw, ImageFont

#元画像を読み込んでくる
image = Image.open("tapioca.png")

#画像のサイズを取得する
print(image.size[0]) # => 今回の例では771
print(image.size[1]) # => 今回の例では856
```

これで最大のサイズがわかりました。

##画像全体の中心から描画を開始してみる
上記の771と856を描画開始位置にしてしまうと、画像の右下から始まってしまい、せっかくの文字がほとんど見えません。
では、771と856を2で割り、その位置を開始位置にしてみます。
これで画像の中央部分から描画が始まるはずです。

![new_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495652/c7115c16-9f30-438a-7ef0-03461af551df.png)

まあわかっていましたがこうなりますね。
開始地点はたしかに画像のど真ん中ですが、こうじゃない・・・。

##さらに「100円」の描画領域のサイズを取得し、その半分の値分だけ場所を引き戻す
ちょっとタイトルがわかりにくいですが、「100円」の中心を画像全体の中心と合わせたいわけです。
つまり、「100円」が描画されるときのサイズを取得し、その上で「100円」のサイズの半分だけ開始地点を戻してやれば実現できます。う～ん、言葉にするのが難しい。

とりあえずやってみます。
まず、「100円」自体のサイズは以下で取得できました。

```python
from PIL import Image, ImageDraw, ImageFont

#元画像を読み込んでくる場合
image = Image.open("tapioca.png")

#文字を書きこむ為のオブジェクトが用意されているので取得する
draw = ImageDraw.Draw(image)

#フォントを決める
font = ImageFont.truetype("YuGothL.ttc", size=50)

#「100円」のサイズを取得
draw_text_width, draw_text_height = draw.textsize("100円", font=font)
```

まず「100円」の描画開始地点を元画像の中心部分とした上で
このdraw_text_width、draw_text_heightの半分のサイズだけ、上と左に引き戻します。
上のコードの続きと考えてください。

```python
draw.text(((image.size[0] / 2 - draw_text_width / 2), (image.size[1] / 2 - draw_text_height / 2)), "100円", fill=(255, 0, 0), font=font)
```

長くて分かりづらいですね。
こうしたほうがわかりやすいかも知れません。

```python
start_X_point = image.size[0] / 2 - draw_text_width / 2
start_Y_point = image.size[1] / 2 - draw_text_height / 2

draw.text((start_X_point, start_Y_point), "100円", fill=(255, 0, 0), font=font)
```
##完成コード
```python:main.py
from PIL import Image, ImageDraw, ImageFont

#元画像を読み込んでくる場合
image = Image.open("tapioca.png")

#文字を書きこむ為のオブジェクトが用意されているので取得する
draw = ImageDraw.Draw(image)

#フォントを決める
font = ImageFont.truetype("YuGothL.ttc", size=50)

#描きたい文字のサイズを取得する
draw_text_width, draw_text_height = draw.textsize("100円", font=font)

#描きたい文字のサイズと元画像のサイズを元に、描画開始ポイントの座標を決める
start_X_point = image.size[0] / 2 - draw_text_width / 2
start_Y_point = image.size[1] / 2 - draw_text_height / 2

#描画する
draw.text((start_X_point, start_Y_point), "100円", fill=(255, 0, 0), font=font)

#出来上がった画像を保存する
image.save("new_tapioca.png")
```

これでめでたく真ん中に持ってくることができました。

![new_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495652/39e0773c-8d4e-5985-1881-7d2fd13e3808.png)


#まとめ
・画像と文字の合成はPillowでカンタンにできる！
・合成するだけならカンタンだけど、センタリングとかは若干面倒かも
　（ドキュメント読む限りではセンタリングするオプションがあるんですけどね、なぜかうまく行かない...）