こんにちは。株式会社リンクネット ソリューション事業部の吉川です。
弊社ではCreateJSを使って入力文字列を画像化するシステムを開発していましたが、諸事情によりサーバ側で画像化を行う必要が出てきました。
そこで候補に挙がったのがPythonでした。
Pythonでの画像化にあたってフォント周りで悩んだことがあったので書き記しておきます。
$ python --version
Python 3.7.10
同じパラメータで画像化したときに、なるべくCreateJSと相違ない画像になることを目指します。
絵文字も描画する必要があるため、NotoColorEmoji
を使用することにしました。
フォントの情報を読み取るためにfonttools
を使用し、画像化にはPillow
を使用しました。
試しにハンバーガーの絵文字🍔をフォントサイズ75で描画しようとしたところ、OSError: invalid pixel size
というエラーが発生しました。
from PIL import ImageFont
font = ImageFont.truetype('NotoColorEmoji.ttf', 75) # OSError: invalid pixel size
エラー内容で調べてみると、以下のissueが見つかりました。
https://github.com/python-pillow/Pillow/issues/3346#issuecomment-612139601
ビットマップフォントは拡大・縮小ができず、有効なフォントサイズを指定する必要があるとのことです。
NotoColorEmoji
の場合は109を指定すべきということなので、フォントサイズ109で描画した画像をリサイズすることできれいに表示できました。
from PIL import Image, ImageDraw, ImageFont
font = ImageFont.truetype('NotoColorEmoji.ttf', 109)
width, height = font.getsize('🍔') # 絵文字を描画したときの幅と高さ
img = Image.new('RGBA', (width, height), '#FFFFFF00') # リサイズするためにいったん描画する透明な背景
ImageDraw.Draw(img).text((0, 0), '🍔', font=font, embedded_color=True) # 背景に絵文字を描画
img.resize((round(width * 75 / 109), round(height * 75 / 109))) # フォントサイズ75相当の大きさにリサイズ
PillowにはImageDraw.multiline_text
というメソッドがあり、複数行のテキストを簡単に出力することができます。
今回は絵文字も描画しなくてはならないため、1文字ずつ絵文字かどうかを判定しながら描画しなくてはならず、ImageDraw.multiline_text
は使えませんでした。
各行を書き始める位置を計算するためにImageFont.getbbox
やImageFont.getmetrics
で取得した値を使っていましたが、
CreateJSの画像とずれがあり、最終的には以下のサイトを参考にフォントのテーブルに格納されている値を参照して位置を調整しました。
https://aznote.jakou.com/prog/opentype/index.html
画像化したときに「A」の左下部分のような尖った部分が見切れてしまうフォントがありました。
他のフォントと見比べてみると、描画したときにバウンディングボックスの左端がマイナスの値になっていました。
from PIL import ImageFont
font = ImageFont.truetype('/path/to/font_file', 75)
font.getbbox('ABC') # (-2, 19, 113, 78)
描画する文字によってもバウンディングボックスの左端の値が変わるため、全行描画してから最も小さい左端の値ぶんだけずらして配置することで対応しました。
ここまでの対応でひとまずテキストの画像化ができるようになりました。
実際には、追加で背景色の設定やテキストに影をつけるといった装飾をする機能を実装しました。
普段ブラウザでサイトを閲覧する時には気にも留めませんでしたが、きれいに文字を表示するのはかなり大変だなと実感しました。