2021/10/02(土) [n年前の日記]
#1 [cg_tools][python] 数字が書かれた画像をたくさん作りたい
数字が書かれた画像を ―― 「0000」から「00xx」まで連番が書かれた感じの画像を複数作りたい。ImageMagick を使えばできるんじゃないかと思えてきたので試してみた。
環境は、Windows10 x64 21H1 + Python 3.9.5 64bit + ImageMagick 7.1.0-5 Q16 x64。
環境は、Windows10 x64 21H1 + Python 3.9.5 64bit + ImageMagick 7.1.0-5 Q16 x64。
◎ 数字を描画。 :
以下のページで、ImageMagick を使って画像に文字を描画する方法が説明されてたので参考にして作業。ありがたや。
_ImageMagickで画像に文字を描画 - エラーの向こうへ
まず、指定できるフォントの種類を把握しないといけない。.ttf を指定することでもいいらしいけど…。とりあえず、以下を打てば利用できるフォントの一覧を表示できるらしい。大量にずらずらと出てきたのでテキストファイルに書き込んでエディタで参照。
実際に文字を描画してみる。以下の指定で、96x96 の画像サイズの真ん中に、「Sample」という文字を描画できる。
背景を透明にして描画したい場合は以下。
これで、一枚だけ作るやり方は分ったので、スクリプトを書いて大量に生成してみたい。
今回は、make_seq_img.py というPythonスクリプトを書いて、0000 - 0063 までの数字が書かれた64枚の画像を out/ ディレクトリに出力するようにしてみた。
_make_seq_img.py
Python では、subprocess.call(文字列) で外部プログラムを実行することができるらしい。
以下で実行。
_ImageMagickで画像に文字を描画 - エラーの向こうへ
まず、指定できるフォントの種類を把握しないといけない。.ttf を指定することでもいいらしいけど…。とりあえず、以下を打てば利用できるフォントの一覧を表示できるらしい。大量にずらずらと出てきたのでテキストファイルに書き込んでエディタで参照。
magick convert -list font magick convert -list font > fontlist.txt
実際に文字を描画してみる。以下の指定で、96x96 の画像サイズの真ん中に、「Sample」という文字を描画できる。
magick convert -size 96x96 -gravity center -font Arial-Black -fill white -background gray -pointsize 32 label:Sample output.png
- -size 96x96 : 画像サイズを指定。
- -gravity center : 画像の真ん中の位置を指定。
- -font Arial-Black : Arial-Blackフォントを指定。
- -fill white : 色は白で描画。
- -background gray : 背景色を灰色に。
- -pointsize 32 : フォントの文字サイズを指定。
- label:Sample : 「Sample」という文字列を描画させる。
背景を透明にして描画したい場合は以下。
magick convert -size 96x96 -gravity center -font Inconsolata-Bold -fill black -background rgba(0,0,0,0) -pointsize 32 label:0000 png32:output.png
- -background rgba(0,0,0,0) : 背景を透明化。もしかすると -background none でもいいのだろうか。
- png32:output.png : 出力ファイル名の先頭に png32: を追加してアルファチャンネルを含むpng画像として出力することを明示。
これで、一枚だけ作るやり方は分ったので、スクリプトを書いて大量に生成してみたい。
今回は、make_seq_img.py というPythonスクリプトを書いて、0000 - 0063 までの数字が書かれた64枚の画像を out/ ディレクトリに出力するようにしてみた。
_make_seq_img.py
import os
import subprocess
mgk = "magick convert"
size = "-size 96x96"
pos = "-gravity center"
font = "-font Inconsolata-Bold"
col = "-fill black"
bg = "-background rgba(0,0,0,0)"
pt = "-pointsize 40"
for i in range(8 * 8):
lbl = "%04d" % i
fn = "png32:out/%04d.png" % i
cmd = f"{mgk} {size} {pos} {font} {col} {bg} {pt} label:{lbl} {fn}"
subprocess.call(cmd)
print("Output: %s" % fn)
Python では、subprocess.call(文字列) で外部プログラムを実行することができるらしい。
以下で実行。
mkdir out python make_seq_img.py
◎ タイル状に並べて結合したいが上手く行かない。 :
これで大量の画像が生成できたので、タイル状に並べて結合してスプライトシートっぽくしてみたい。
そういった処理も ImageMagick で出来たはず、なのだけど…。
これが上手く行かない。各画像の下に、何故かファイル名っぽいものが描き込まれてしまう。何だコレ。
どうやら label なるものが描き込まれてしまっているらしい…。
-label "" (*NIX の場合は -label '' だろうか)をつけることで、label を描かない指定ができるらしいのだけど…。
たしかに label は無くなったけど、これでもまだ結果としてはよろしくない。96 x 96ドットの画像を 8 x 8個並べてるから、768 x 768ドットの画像になるはずだけど、実際は 768 x 912ドットになってしまっている。縦方向に余計な隙間が入ってるわけで。何だコレ。
以下のページを眺めてみたけど、解決方法は見つからず。
_Montage -- IM v6 Examples
以下のやりとりを眺めて、状況が分かってきた。数字画像を作成する際に label を指定したものだから、生成された画像のメタデータにも label 情報が残っていて、「この画像は label を持っている画像だな。では label も描き込まねば」と処理されてしまうようで。label を持った画像を扱うと、-label "" や -set label "" を指定しても label 用の描画領域が含まれてしまうのだとか。しかも、簡単な解決策は無いらしい。
_ImageMagick montage always includes labels - Stack Overflow
そういった処理も ImageMagick で出来たはず、なのだけど…。
magick montage -geometry +0+0 -background none out/*.png spritesheet.png
これが上手く行かない。各画像の下に、何故かファイル名っぽいものが描き込まれてしまう。何だコレ。
どうやら label なるものが描き込まれてしまっているらしい…。
-label "" (*NIX の場合は -label '' だろうか)をつけることで、label を描かない指定ができるらしいのだけど…。
magick montage -geometry +0+0 -background none -label "" out/*.png spritesheet.png
たしかに label は無くなったけど、これでもまだ結果としてはよろしくない。96 x 96ドットの画像を 8 x 8個並べてるから、768 x 768ドットの画像になるはずだけど、実際は 768 x 912ドットになってしまっている。縦方向に余計な隙間が入ってるわけで。何だコレ。
以下のページを眺めてみたけど、解決方法は見つからず。
_Montage -- IM v6 Examples
以下のやりとりを眺めて、状況が分かってきた。数字画像を作成する際に label を指定したものだから、生成された画像のメタデータにも label 情報が残っていて、「この画像は label を持っている画像だな。では label も描き込まねば」と処理されてしまうようで。label を持った画像を扱うと、-label "" や -set label "" を指定しても label 用の描画領域が含まれてしまうのだとか。しかも、簡単な解決策は無いらしい。
_ImageMagick montage always includes labels - Stack Overflow
◎ Python + Pillow でタイル状に並べて結合。 :
仕方ないので、ImageMagick で結合するのは諦めて、Python 3.9.5 64bit + Pillow 8.3.2 で画像結合するスクリプトを書いた。スクリプト内にマジックナンバーがその他が列挙されちゃってるけど…。まあ、処理ができればいいか…。
_joint_image.py
_output.png
一応これで、数字が書かれている画像をタイル状に並べることができた。
GIMP 2.10.22 x64 Portable samj版を使って、背景に少し模様っぽいものも追加してみた。96 x 96 ドットの画像を作ってから、フィルタ → カラーマッピング → 並べる、で、800% x 800% にして並べて、前述の画像と重ね合わせた。
以下が完成画像。CC0 / Public Domain ってことで。まあ、どこでこんなの使うんだよって感じだけど…。
_spritesheet_number.png
ということで、ImageMagick の挙動でハマったものの、なんとか目的は果たせた。
それにしても、結局 Python スクリプトを書いて対応したわけだから…。だったら最初から、数字を描き込む処理も含めて、全部 Python + Pillow で処理してしまえばよかったのではないか…な…。
_joint_image.py
from PIL import Image, ImageFilter
import glob
inputfiles = "out/*.png"
output = "output.png"
xloop = 8
l = sorted(glob.glob(inputfiles))
w, h = 0, 0
imgs = []
for s in l:
im = Image.open(s)
if im.size[0] > w:
w = im.size[0]
if im.size[1] > h:
h = im.size[1]
imgs.append(im)
yloop = len(l) // xloop
if len(l) % xloop != 0:
yloop += 1
tw = w * xloop
th = h * yloop
nim = Image.new("RGBA", (tw, th), (0, 0, 0, 0))
xi, yi = 0, 0
idx = 0
while yi < yloop:
x, y = xi * w, yi * h
nim.paste(imgs[idx].copy(), (x, y))
idx += 1
if idx >= len(l):
break
xi += 1
if xi >= xloop:
xi = 0
yi += 1
nim.save(output)
_output.png
一応これで、数字が書かれている画像をタイル状に並べることができた。
GIMP 2.10.22 x64 Portable samj版を使って、背景に少し模様っぽいものも追加してみた。96 x 96 ドットの画像を作ってから、フィルタ → カラーマッピング → 並べる、で、800% x 800% にして並べて、前述の画像と重ね合わせた。
以下が完成画像。CC0 / Public Domain ってことで。まあ、どこでこんなの使うんだよって感じだけど…。
_spritesheet_number.png
ということで、ImageMagick の挙動でハマったものの、なんとか目的は果たせた。
それにしても、結局 Python スクリプトを書いて対応したわけだから…。だったら最初から、数字を描き込む処理も含めて、全部 Python + Pillow で処理してしまえばよかったのではないか…な…。
[ ツッコむ ]
以上です。
