Pythonで画像を編集したり、文字を入れたりする

## はじめに
nana musicでサーバサイドエンジニアをやっている@xKxAxKxです
この記事は[nana music Advent Calendar 2018](https://qiita.com/advent-calendar/2018/nana-music)の19日目の記事です

## nanaのTwitterカードの画像が変更されたぞ！
nanaのサウンドへのリンクをTwitterに投稿した時、以前は投稿ユーザのアイコンが表示されているだけだったんだけど、以下のように投稿ユーザのアイコン、再生ボタン画像、投稿パート毎の背景色画像、投稿楽曲タイトル、投稿楽曲アーティスト名、投稿ユーザ名の組み合わせで画像を生成し、それがTwitterカードの画像として使用されるようになった
<img width="499" alt="スクリーンショット 2018-12-13 16.35.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/80787/5344c805-649d-3e1e-b9e3-d605806bbe80.png">
画像ファイルを色々といじるの、割とカジュアルにやることができたので、どのような処理をやっているかについて書いていく

## 用意するもの
### アイコン画像
[いらすとや](https://www.irasutoya.com/)様から借りた
中二病（厨二病）の女の子が自分で考えたかっこいいポーズを取っているイラストです
![image.png](https://qiita-image-store.s3.amazonaws.com/0/80787/66659f18-b9fa-bfcc-3ec8-81976ce11fa3.png)

### 再生ボタン画像
はい
背景は透過になっている
![image.png](https://qiita-image-store.s3.amazonaws.com/0/80787/b3eec6c9-55a5-d278-1868-c3b96e5df774.png)

### パート毎の画像
ベースとなる画像
この画像に対してアイコン画像を貼っていったり、文字を入れたりする
今回はギターパートを投稿した場合の画像です
![935.png](https://qiita-image-store.s3.amazonaws.com/0/80787/b39f37fa-3ac0-6cda-f086-04d30611f16e.png)

### その他必要なもの
- Python3系(実際の環境は3.6.5)
- 画像を生成するぞ、という気持ち(重要)

## 手順
### 各種ライブラリのインストール
Pythonの画像処理ライブラリ Pillow をインストールする
公式ドキュメントは[こちら](https://pillow.readthedocs.io/en/5.3.x/)

```
$ pip install Pillow
```

### 再生ボタン画像とアイコンを合成する
まずは再生ボタン画像と、アイコンをそれぞれ `Image.open()` でpath/urlから画像データとして読み込みを行う

```python:画像データの読み込み
from PIL import Image

play_path = '再生ボタン画像のpath/url'
play = Image.open(play_path).copy()

icon_path = 'アイコンのpath/url'
icon = Image.open(icon_path).copy()
```

次にアイコン画像を適切なサイズに縮小する
nanaでは全てのアイコン画像は正方形で保存されているので以下の処理だけでいいけれどp、仮に正方形じゃない画像を扱う場合だと、画像を正方形にトリミングしないと、縦横比が変な画像になってしまう

```python:アイコンの縮小
icon = icon.resize(size=(252, 252), resample=Image.ANTIALIAS)
```
resize関数で `resample=Image.ANTIALIAS` というように指定しておかないと、大きい画像を縮小する時にジャギジャギしたような感じに処理されてしまう

最後に再生ボタンとアイコンの合成
アイコンに再生ボタンを貼り付けるという、イメージ

```python:合成
icon.paste(play, (51, 51), play)
```
第一引数が貼り付ける画像
第二引数が左端からのwidth/height
第三引数がマスクとなる画像を指定
今回の場合、再生ボタン画像は円形の背景透過であるため、透過部分がそのままマスクとなる

これにより以下のような画像が生成される
![image.png](https://qiita-image-store.s3.amazonaws.com/0/80787/98e05655-4c68-0c76-867c-47a4f068a1ed.png)

### アイコンを円形に切り出し、背景を透過にする
Twitterカードの画像を見てもらえばわかるとおり、アイコンは円形で貼り付けられているのでアイコンを円形に切り出して、背景をとうかにするひつようがある

まずは、円形に切り取るためのマスクを生成する

```python:マスクの生成
from PIL import　ImageDraw

mask = Image.new("L", icon.size, 0)
draw = ImageDraw.Draw(mask)
draw.ellipse((0, 0, icon.size[0], icon.size[1]), fill=255)
mask = mask.filter(ImageFilter.GaussianBlur(1))
```
これにより以下のようなアイコンの画像のサイズに合わせた円形で、背景が透過の画像が生成される
![mask.png](https://qiita-image-store.s3.amazonaws.com/0/80787/619f3524-c13a-2349-2377-df9f5fec7aa6.png)

このマスクを `putalpha()` を使ってアイコン+再生ボタンの画像にかぶせる

```python:アイコンを円形の透過画像にする
icon.putalpha(mask)
```
白背景なので分かりづらいけれど、以下のような画像となる
![image.png](https://qiita-image-store.s3.amazonaws.com/0/80787/b0ca3907-e7f5-55de-821c-21e80d7e52c2.png)

### 背景画像にアイコンを貼り付ける
ここは非常に簡単で、アイコンと再生ボタンの合成と同じ要領でやるだけで良い

```
base_image_path = '背景画像のpath/url'
base_img = Image.open(base_image_path).copy()
base_img.paste(icon, (80, 134), icon)
```

結果、以下のような画像が生成される
![new.png](https://qiita-image-store.s3.amazonaws.com/0/80787/7382f0c6-ff83-5e0b-065f-0607a56eac10.png)

### 文字の入力
最後に文字を入力していく
ここは、まずは文字を入力するための関数で説明していきたい

```python:画像に文字を入れる関数
from PIL import ImageFont

def add_text_to_image(img, text, font_path, font_size, font_color, height, width, max_length=740):
    position = (width, height)
    font = ImageFont.truetype(font_path, font_size)
    draw = ImageDraw.Draw(img)
    if draw.textsize(text, font=font)[0] > max_length:
        while draw.textsize(text + '…', font=font)[0] > max_length:
            text = text[:-1]
        text = text + '…'

    draw.text(position, text, font_color, font=font)

    return img
```

第1引数に画像データ
第2引数に入れる文字列
第3引数に使用するフォントのpath
第4引数に文字の大きさ
第5引数に文字の色
第6、第7引数にそれぞれ、文字を入れる位置
第8引数に文字列の最大の長さ
引数が多い関数だけれど、このような感じ

文字列の長さがmax_lengthを超えた場合は以下省略する、という感じになっている

最後にImageクラスの `text()` で文字入力を行なっていく

この関数を以下のようにそれぞれ呼び出していく

```python:文字の入力やっていき
song_title = "There Is A Light That Never Goes Out"
font_path = "フォントファイルのpath"
font_size = 57
font_color = (255, 255, 255)
height = 155
width = 380
img = add_text_to_image(base_img, song_title, font_path, font_size, font_color, height, width)

song_artist = "The Smiths"
font_path = "フォントファイルのpath"
font_size = 40
font_color = (255, 255, 255)
height = 244
width = 380
img = add_text_to_image(base_img, song_artist, font_path, font_size, font_color, height, width)

user_name = 'by xKxAxKx'
font_path = "フォントファイルのpath"
font_size = 40
font_color = (20, 23, 26)
height = 328
width = 380
img = add_text_to_image(base_img, user_name, font_path, font_size, font_color, height, width)

img.save('ファイルのパス')
```
すると、実際に使用しているtwitterカードの画像のようになる
![new.png](https://qiita-image-store.s3.amazonaws.com/0/80787/a40e81a3-e276-f029-097c-72dd187e4507.png)


## 最後に
実際は様々な部分を関数化したり、アイコン画像に対しても処理を追加したりしている
また、絵文字対応なども行なったりしているんだけれど、ざっとした流れとしては以上のような感じになっている

自分自身、Pythonで画像をどうのこうのする、という経験がほとんどなかったのだが、何かと需要はありそうではあるし、視覚的に成果が見えやすいのでやってて楽しかったっす

## 参考リンク
- [Pillow — Pillow (PIL Fork) 5.3.0 documentation](https://pillow.readthedocs.io/en/5.3.x/)

- [Pythonの画像処理ライブラリPillow(PIL)の使い方 | note.nkmk.m](https://note.nkmk.me/python-pillow-basic/)