【Python】PIL(Pillow)で画像に文字を描く

# はじめに
pythonのPILで画像に文字を入れたかったときに調べたことのメモです。


# 結論から
200×100の透明の画像に枠を描き中央に"Hello.World!"と描きます。
![img.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280129/d9d52f48-fa34-012e-8653-c71a1028aa9c.png)

```python:draw text
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

text = "Hello,World!"
fontFile = "Menlo-Regular.ttf"
fontSize = 24
mode = "RGBA"
whidth, hight = (200, 100)
baseColor = (0, 0, 0, 0)
lineColor = (0, 0, 255, 255)
textColor = (255, 0, 0, 255)

im = Image.new(mode, (whidth, hight), baseColor)
dr = ImageDraw.Draw(im)
dr.rectangle((0,0,whidth - 1, hight - 1), baseColor, lineColor)
dr.line((0, hight//2, whidth, hight//2), lineColor)
dr.line((whidth//2, 0, whidth//2, hight), lineColor)

font = ImageFont.truetype(fontFile, fontSize)
textWidth,textHight = dr.textsize(text, font)
text_x = (whidth - textWidth) // 2
text_y = (hight - textHight) // 2
dr.text((text_x, text_y), text, textColor, font=font)
```

# 説明
記事を書いてるときは覚えているのですが、すぐに忘れてしまうので細かく分解して説明しておきます。

## 透明の画像をつくる
新しく画像をつくるには、`Image.new(mode, size, color)`を使います。

<details><summary> mode 一覧 </summary>  

|mode|説明|
|---|---|
|1 | 1 ビット ピクセル、白黒、1 バイトあたり 1 ピクセルで格納|
|L | 8 ビット ピクセル、白黒|
|P |　8 ビット ピクセル、カラー パレットを使用して他のモードにマッピング|
|RGB | 3x8 ビット ピクセル、トゥルーカラー|
|RGBA | 4x8 ビット ピクセル、透過マスク付きトゥルーカラー|
|CMYK | 4x8 ビット ピクセル、4色分解|
|YCbCr | 3x8 ビット ピクセル、カラー ビデオ形式|
|Note | これは、ITU-R BT.2020 標準ではなく、JPEG を参照していること|
|LAB | 3x8 ビット ピクセル、L*a*b 色空間|
|HSV | 3x8 ビット ピクセル、色相、彩度、値の色空間|
|I | 32 ビットの符号付き整数ピクセル|
|F | 32 ビット浮動小数点ピクセル|
|**追加モード**|**次のようないくつかの追加モードを限定的にサポートします。**|
|LA | アルファ付きの L|
|PA | アルファ付きの P|
|RGBX | パディング付きのトゥルーカラー|
|RGBa | プレマルチプライド　アルファを使用したトゥルーカラー|
|La | 事前に乗算されたアルファを持つ L|
|I;16 | 16 ビット符号なし整数ピクセル|
|I;16L | 16 ビットのリトルエンディアン符号なし整数ピクセル|
|I;16B | 16 ビット ビッグ エンディアンの符号なし整数ピクセル|
|I;16N | 16 ビット ネイティブ エンディアン符号なし整数ピクセル|
|BGR;15 | 15 ビット反転 True Color|
|BGR;16 | 16 ビット反転 True Color|
|BGR;24 | 24 ビット反転 True Color|
|BGR;32 | 32 ビット反転 True Color|
</details>

```python:make image
from PIL import Image

mode = "RGBA"                           # モード
whidth, hight = (200, 100)              # 幅、高さ
size = (whidth, hight)                  # サイズ
baseColor = (0, 0, 0, 0)                # 色
im = Image.new(mode, size, baseColor)   # 画像を生成
im.save("img_0.png")                    # 画像を保存

```
よく使う`mode`は`"RGBA"`と`"L"`くらいです。


## 長方形を描く
長方形を描くには次のメソッドを使います。
`ImageDraw.rectangle(xy, fill=None, outline=None, width=1)`
<details><summary>パラメータ</summary>

|パラメータ|意味|
|:-:|---|
|**xy**| 境界ボックスを定義する 2 つのポイント。 [(x0, y0), (x1, y1)] または [x0, y0, x1, y1] のシーケンス。境界ボックスには両方の端点が含まれます。|
|**outline**|アウトラインに使用する色。|
|**fill**|塗りつぶしに使用する色|
|**width**|ライン幅 (ピクセル単位)|
</details>

```python:draw rectangle
from PIL import Image
from PIL import ImageDraw

im = Image.open("img_0.png")            # 画像を開く
dr = ImageDraw.Draw(im)                 # 描画オブジェクトを生成
whidth, hight = im.size                 # 画像の幅と高さ
box = (0, 0, whidth - 1, hight - 1)     # 境界ボックス
fillColor = (0, 0, 0, 0)                # 塗りつぶしの色
lineColor = (0, 0, 255, 255)            # 線の色
dr.rectangle(box, fillColor, lineColor) # 長方形を描画
im.save("img_1.png")                    # 画像を保存

```
左上の座標がが`(0,0)`で、境界ボックスには両方の端点が含まれるので、画像サイズが`256 × 256`の場合の最大の長方形なら、`(0, 0 ,255, 255)`と指定します。
なので、`box = (0, 0, width - 1, hight - 1)`になります。


## 線を描く
線を描くには次のメソッドを使います。
`ImageDraw.line(xy, fill=None, width=0, joint=None)`
<details><summary>パラメータ</summary>

|パラメータ|意味|
|:-:|:---|
|**xy**|[(x, y), (x, y), ...] のような 2 タプルまたは [x, y, x, y, ...] のような数値のシーケンス。|
|**fill**|線に使用する色。|
|**width**|ライン幅 (ピクセル単位)|
|**joint**|一連のライン間のジョイント タイプ。丸みを帯びたエッジの場合は「curve」にするか、None にすることができます。|
</details>

```python:draw line
from PIL import Image
from PIL import ImageDraw

im = Image.open("img_1.png")            # 画像を開く
dr = ImageDraw.Draw(im)                 # 描画オブジェクトを生成
whidth, hight = im.size                 # 画像の幅と高さ
lineColor = (0, 0, 255, 255)            # 線の色
centerX = whidth // 2                   # 左右の中央
centerY = hight // 2                    # 上下の中央
xy = ((centerX, 0), (centerX, hight))   # 縦線の始点と終点
dr.line(xy, lineColor)                  # 縦線を描画
xy = ((0,centerY), (whidth, centerY))   # 横線の始点と終点
dr.line(xy, lineColor)                  # 横線を描画
im.save("img_2.png")                    # 画像を保存
```


## 画面中央に文字を描く
文字を描画するだけなら`ImageDraw`モジュールだけでもできるのですが、文字のフォントを指定する場合は`ImageFont`を使います。
`ImageFont.truetype(font=None, size=10, index=0, encoding='', layout_engine=None)`
<details><summary>パラメータ</summary>

|パラメータ|意味|
|:-:|:---|
|**font**|TrueType フォントを含むファイル名またはファイルのようなオブジェクト。ファイルがこのファイル名で見つからない場合、ローダーは、Windows の fonts/ ディレクトリや、macOS の /Library/Fonts/、/System/Library/Fonts/、および ~/Library/Fonts/ などの他のディレクトリも検索する場合があります。|
|**size**|要求されたサイズ (ピクセル単位)。|
|**index**|ロードするフォント フェース (デフォルトは最初に利用可能なフェース)。|
|**encoding**|使用するフォント エンコーディング (デフォルトは Unicode)。可能なエンコーディングには次のものがあります (詳細については、FreeType のドキュメントを参照してください)。[”unic” (Unicode),”symb” (Microsoft Symbol),”ADOB” (Adobe Standard),”ADBE” (Adobe Expert),”ADBC” (Adobe Custom),”armn” (Apple Roman),”sjis” (Shift JIS),”gb “ (PRC),”big5”,”wans” (Extended Wansung),”joha” (Johab),”lat1” (Latin-1)]<br>使用する文字セットを指定します。後続の操作で提供されるテキストのエンコーディングは変更されません。|
|**layout_engine**]|利用可能な場合、使用するレイアウト エンジン: ImageFont.Layout.BASIC または ImageFont.Layout.RAQM。利用可能な場合、Raqm レイアウトがデフォルトで使用されます。それ以外の場合は、基本レイアウトが使用されます。<br>英語以外のすべてのテキストには、Raqm レイアウトをお勧めします。 Raqm レイアウトが不要な場合は、基本レイアウトの方がパフォーマンスが高くなります。<br>`feature="raqm"`と共に`PIL.features.check_feature()`を使用して、Raqm レイアウトのサポートを確認できます。|
</details>


```python:draw text
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

text = "Hello,World!"                   # 描画したい文字
fontFile = "Menlo-Regular.ttf"          # フォントファイル
fontSize = 24                           # フォントサイズ
im = Image.open("img_2.png")            # 画像を開く
dr = ImageDraw.Draw(im)                 # 描画オブジェクトを生成

# フォント オブジェクトを作成します。
fnt = ImageFont.truetype(fontFile, fontSize)

# 文字の幅と高さを取り出します。
textWidth, textHight = dr.textsize(text, font=fnt)
whidth, hight = im.size                 # 画像の幅と高さ
textColor = (255, 0, 0, 255)            # 文字の色
text_X = (whidth - textWidth) // 2      # 文字の左端座標
text_Y = (hight - textHight) // 2       # 文字の上端座標

# 画像の中央に文字を描画します。
dr.text((text_X, text_Y), text, textColor, font=fnt)

im.save("img_3.png")                    # 画像を保存
```
フォントを指定しないとデフォルトのビットマップフォントが使われます。
`dr.text((text_X, text_Y), text, textColor)`
その場合、文字のサイズを知ることができないので、文字の位置指定が雑になります。


# 参考にさせていいただきました。

[Pillow (PIL Fork) 9.3.0 documentation](https://pillow.readthedocs.io/en/stable/index.html)
- [Image Module - Pillow (PIL Fork) 9.3.0 documentation](https://pillow.readthedocs.io/en/stable/reference/Image.html#)
- [ImageDraw Module - Pillow (PIL Fork) 9.3.0 documentation](https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html)
- [ImageFont Module - Pillow (PIL Fork) 9.3.0 documentation](https://pillow.readthedocs.io/en/stable/reference/ImageFont.html)

[Python, Pillowで文字（テキスト）を描画、フォント設定 | note.nkmk.me](https://note.nkmk.me/python-pillow-imagedraw-text-imagefont/)