Posted at

python3でpillowを使って文字入り画像を生成する(ついでにbase64化された別の画像も重ねる)

More than 1 year has passed since last update.


環境


  • CentOS Linux release 7.0.1406 (Core)

  • Python 3.6.2

  • pip 10.0.1

  • 今日の日付 2018年7月29日

(一部、VPSサーバーではなくローカルで表示したエラーメッセージをコピペしたためにバージョンがズレている箇所がありますが、大勢に影響はありません。)


目標

まず目標を整理します。


  • VPSサーバー(CentOS7)が動いているサーバー上で

  • python3で

  • ある画像の上に別の画像(base64化されたもの)と文字を合成した画像を生成する

base64化された画像を使うのは、Web上でHTML5のcanvas要素からtoDataURLで生成した画像を使って何かする、という想定です。


ステップ


  • 何でも良いから画像を生成する

  • 何でも良いから文字の入った画像を生成する

  • 日本語の文字の入った画像を生成する

  • ある画像にbase64の画像と文字の入った画像を生成する

こんな感じのステップバイステップでいきましょう。

公式ドキュメントはこちら。なんだかんだ一番参考になります。

https://pillow.readthedocs.io/en/5.2.x/index.html


何でも良いから画像を生成する

今回はPillowというライブラリを使います。調べた感じ、これが今日(2018-07-22)現在のデファクトスタンダードっぽい。

pip install Pillow

場合によってはルート権限のあるアカウントで実行するかsudoをつけて下さい。

(pipでエラーが出た場合は、僕の前回の記事が参考になるかもしれません。)

pillowのインストールが済んだら、次のpythonプログラムを実行します。

from PIL import Image

im = Image.new("RGB",(300,300),"red")
im.save("./test.jpg")

Imageインスタンスを作成して、そのsaveメソッドを呼び出してるだけです。2行。

これで、test.jpgという赤一色の画像ファイルが生成されました。メッチャ簡単じゃない???すご。。。。。

各メソッドの引数の意味などは、各々公式リファレンスで確認お願いします。


文字の入った画像を生成する

from PIL import Image, ImageDraw

im = Image.new("RGB",(300,300),"blue")# Imageインスタンスを作る
draw = ImageDraw.Draw(im)# im上のImageDrawインスタンスを作る
draw.text((0,0),"hogehoge")
im.save("./test.jpg")

ここから段々ややこしくなりますね。

Imageインスタンスの上に、ImageDrawインスタンスを作り、そこで文字などの操作を行うようです。

とりあえずこれを実行すると、左上に"hogehoge"と文字の入った画像が生成されます。文字色とかフォントとかは全て初期値。

ところが。これを、文字列の部分だけ変えて

from PIL import Image, ImageDraw

im = Image.new("RGB",(300,300),"blue")# Imageインスタンスを作る
draw = ImageDraw.Draw(im)# im上のImageDrawインスタンスを作る
draw.text((0,0),"日本語の文字だよ")
im.save("./test.jpg")

とすると、以下のエラーがでます。

Traceback (most recent call last):

File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/PIL/ImageDraw.py", line 220, in text
mask, offset = font.getmask2(text, self.fontmode, *args, **kwargs)
AttributeError: 'ImageFont' object has no attribute 'getmask2'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "pillow_test.py", line 5, in <module>
draw.text((0,0),"日本語の文字だよ")
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/PIL/ImageDraw.py", line 224, in text
mask = font.getmask(text, self.fontmode, *args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/PIL/ImageFont.py", line 115, in getmask
return self.font.getmask(text, mode)
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-7: ordinal not in range(256)

多分、重要なのは最後の一行ですね。

UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-7: ordinal not in range(256)

要は「フォントがない」と言ってるわけです。この人は日本語の文字を知らないわけですね。だから教えてあげる必要があります。日本語のフォントファイルを指定しましょう。


日本語の文字の入った画像を生成する

日本語フォントは何でも良いですが、今回はこちらのサイトで「こころ明朝体」というフォントをダウンロードして使うことにしました。

日本語フォント「こころ明朝体」 - フォント無料ダウンロード|Typing Art

from PIL import Image, ImageDraw, ImageFont

im = Image.new("RGB",(300,300),"blue")# Imageインスタンスを作る
draw = ImageDraw.Draw(im)# im上のImageDrawインスタンスを作る
fnt = ImageFont.truetype('./Kokoro.otf',30) #ImageFontインスタンスを作る
draw.text((0,0),"日本語の\n文字だよ",font=fnt) #fontを指定
im.save("./test.png")

これで、日本語の文字が入った画像が作れました。

しれっと\nという暗号が入ってますが、これは「改行」です。こう書くと、画像の上ではちゃんと改行してくれます。


ある画像に、base64画像と文字を重ねて表示する

基本的にはこちらを参考にしましたが、

どうも情報が古く、python2系向けの情報になっているようでコードがそのまま使えず、四苦八苦しました。

最終的に動いたコードは以下になります。


from PIL import Image, ImageDraw, ImageFont
import io
import re
import base64

# base64化された画像データを用意
data = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
# 頭のいらない部分を取り除いた上で、バイト列にエンコード
image_data_bytes = re.sub('^data:image/.+;base64,','',data).encode('utf-8')
# バイト列をbase64としてデコード
image_data = base64.b64decode(image_data_bytes)#返り値もバイト列
# ファイルとして開き、pillowのImageインスタンスにする
im2 = Image.open(io.BytesIO(image_data))

im = Image.new("RGB",(300,300),"blue")
im.paste(im2,(30,30))#画像に画像を重ねます。二つ目の引数は位置の指定。
draw = ImageDraw.Draw(im)
fnt = ImageFont.truetype('./Kokoro.otf',30)
draw.text((0,0),"日本語の\n文字だよ",font=fnt)
im.save("./test.png")

base64化された画像として用意したこちらは、小さな赤い点が一つあるだけのpngです。この記事のサンプルコードを短くするために、小さな画像にしました。貼り付けられたことがわかれば何でも良いんです。

やってることは、


  1. 文字列型で、base64化された画像データを用意

  2. 頭のいらない部分(data:image/png;base64,という部分)を除去

  3. バイト列化(これをやらないと次のステップでデコード関数に渡せない)

  4. base64としてデコード(これでやっと画像ファイルのバイト列になる)

  5. ファイルとして開く

という感じ。このためにimportが3つも増えちゃいましたね。全部使ってます。

以上!!!

プログラミングで何か作る時って、まずこうやって一つずつ調べながら技術習得する時間があって、その後でやっと実装に入ることになるから、時間がかかっちゃうね〜。短くしたい。