もうサムネイルで泣かないための ImageMagick ノウハウ集

こんにちわ、アプリケーション基盤チームの青木(@a_o_k_i_n_g)です。好きなみかんは紅マドンナです。

今回は、サイボウズのサムネイル事情について記事を書きたいと思います。サイボウズに限らず通常の Web アプリケーションでもサムネイル作成はよくあると思いますが、ハマりどころが多く涙しているサムネイリストも多いかと思います。これからの時代を生きるサムネイリストが快適なサムネイルライフを送れるよう、知見を共有したいと思います。

弊社では画像変換ツールに ImageMagick を用いており、従って本知見は ImageMagick 固有のものがほとんどです。

画像比較は人間の眼で行うべし

サムネイル周りに何か修正を入れたら修正前後の画像を比較しましょう。機械によるバイト列の比較では画像の良し悪しがわかりません。頼れるのは人間の眼だけです。肉眼で確認しましょう。

比較できるツールを作ると良いです。たとえばサイボウズでは下記のように、ブラウザで修正前後の画像一覧を見れるようにしています。二枚一組で、三列表示にしてます。 f:id:cybozuinsideout:20160104162401p:plain

透過画像がわかるよう、背景画像に何かしら色を入れたり、画像にボーダーを入れておくと確認しやすいです。

リソース大量消費に注意

高画素な画像や、画像サイズは小さくとも数千枚の画像を連結した GIF アニメの変換は大量のリソースを消費します。すると OutOfMemory で死んだり大量のディスク IO が走ったりします。これは言い換えると、サムネイル作成処理は DoS の穴になり得る、ということです。必ずリソース制限を行っておきましょう。

ImageMagick では -limit オプションでリソース制限ができます。-limit オプションは入力画像を指定するよりも前に指定しないと制限がうまく効かない場合があるので注意。ImageMagick 6.7.2 以前のバージョンは単位の MB を小文字で書いても大丈夫でしたが、6.7.3 以降は大文字でなければなりません。小文字で書くとミリバイト扱いされてしまいます。

convert -limit memory 256MB -limit disk 0 src.jpg dst.png

また、-limit オプションで画像変換 1 回あたりのリソース消費を抑えても、大量に同時実行されたら意味がありません。画像変換の並列度にも制限をいれましょう。

Orientation を考慮しよう

JPEG の場合は EXIF 情報に Orientation 情報が入っていることがあります。ImageMagick で変換する際は -auto-orient オプションをつけて向きを補正しましょう。Orientation 画像の確認は MS ペイントがオススメです。なぜなら、ペイントはシンプル故に Orientation を考慮した向きの補正を行わず、画像のバイト列の向きそのままの表示がされるからです。

EXIF に Orientation の項目が存在しながらも、値が入っていない場合があります。Orientation 値を抜き出して何らかの操作をする場合、null チェックが必要です。

Orientation 画像はこちらのサンプルが便利です。 http://www.galloway.me.uk/2012/01/uiimageorientation-exif-orientation-sample-images/

ただ、ImageMagick といえど Orientation 画像の変換でちょっと怪しい挙動があります。上記サンプル画像の right-mirrored.jpg を -auto-orient をつけて png に変換すると、offset 情報がおかしくなります。

$ convert right-mirrored.jpg -auto-orient out.png
$ identify out.png 
out.png PNG 480x640 640x480+160+4294967136 8-bit PseudoClass 256c 13.8KB 0.000u 0:00.000

このケースは -auto-orient をつけて一度 JPEG に変換し、改めて PNG に変換すると正しい情報の画像が得られます。

$ convert right-mirrored.jpg -auto-orient jpg:- | convert - out.png
$ identify out.png
out.png PNG 480x640 480x640+0+0 8-bit PseudoClass 256c 14.6KB 0.000u 0:00.000

透過画像を考慮しよう

PNG や GIF は透過色をサポートします。透過画像をそのまま JPEG に変換すると背景色が黒になるので、白にしたい場合は同じサイズにリサイズしましょう。

$ identify src.png
src.png PNG 800x600 800x600+0+0 8-bit DirectClass 218KB 0.000u 0:00.000
$ convert src.png -resize 800x600 -extent 800x600 dst.jpg

出力する形式も透過色をサポートしている場合は -background transparency を付与するとトラブルが少ないです。

CMYK 画像を考慮しよう

画像データの色空間が CMYK の場合があります。CMYK 画像をリサイズしたりすると色味が変わることがあるので、-colorspace RGB をつけましょう。

また、CMYK 画像は Java での通常操作では取り扱いできないというのもハマりどころです。

通常はこのような操作で読み書きできます。

File file = new File("/path/to/cmyk.jpg");
BufferedImage image = ImageIO.read(file);
....

ですが CMYK の場合例外が出ます。

Exception in thread "main" javax.imageio.IIOException: Unsupported Image Type
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1063)
    at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1034)
    at javax.imageio.ImageIO.read(ImageIO.java:1448)
    at javax.imageio.ImageIO.read(ImageIO.java:1308)
    at com.cybozu.common.Sample.main(Sample.java:15)

これを回避する方法もありますが、CMYK 専用のコードを書くよりは、ImageMagick に一任してしまったほうが効率的です。

グレイスケール画像を考慮しよう

白黒画像を PNG に変換すると、元画像より暗くなる場合があります。これは減色アルゴリズムによる挙動と思われます。JPEG はフルカラー画像を扱えますが、通常の PNG だと 256 色しか扱えないのです。

出力形式に PNG24 または PNG32 を明示的に指定して色空間を広げれば画像が暗くならずに済みます。ただし代償としてファイルサイズは大きくなります。

下記画像は、左から順に、元画像, $ convert gray.jpg dst.png の結果, $ convert gray.jpg png32:dst.png の結果です。ちなみにこの画像は弊社社員の近影です。

画像サイズの取得は ImageMagick で行おう

画像の変換は ImageMagick に任せるとして、画像サイズの取得はアプリケーションのコードで行いたくなるかもしれません。しかし前述したように Java の ImageIO は CMYK 画像の扱いに難があり、通常のコードではサイズの取得が行えません。また、同様に GIF についても JDK-7131823 : bug in GIFImageReader のバグで読めないことがあるので、サイズの取得といえど信頼と実績のある ImageMagick に任せるべきです。

ただ、画像サイズを取得するコマンド、ImageMagick の identify も -verbose オプションをつけるとメモリ大量消費することがあるので注意です。これは色の統計情報を取得しているためでしょう。-verbose をつける場合は、identify コマンドにも -limit オプションが効くので指定しましょう。

ImageMagick のオプションの順序に注意

オプションは前から順に評価されていくので、順序によって結果が異なります。-resize-extent 等の順序が異なると結果も異なるというのは直感に従いますが、たとえば背景色を指定するオプション -background なんかも順序によってオプションが効いたり効かなかったりすることがあります。繰り返しになりますが、サムネイルは肉眼で確認しましょう。

ImageMagick の -define jpeg:size に注意

下記ブログで -define jpeg:size の有用性が示されています。 http://blog.mirakui.com/entry/20110123/1295795409

確かに条件がマッチすれば大変素晴らしいオプションです。ただ、変換後のサイズが大きい場合、たいていは元画像サイズの 1/3 以上程度ですが、これを超えると -define jpeg:size オプションを指定していない時以上にメモリを消費します。いつでもつければ良いというものではないので注意しましょう。弊社では、このオプションはサービスの安定運用のためには無用と判断し、現在このオプションは利用していません。

ImageMagick その他

ImageMagick のオプションは膨大です。そのせいもあって、ドキュメントが間違えていることがあります。そんな時はそっとページ下部の Contact US をクリックしてレポートしましょう。すぐ反映されます。でも返事は来ないです。

まとめ

ImageMagick はオプションが複雑でわかりにくい部分もありますが、それでも様々な画像を扱えるという信頼は揺るぎません。Java やその他言語の各種ライブラリでも画像データを扱うことはできると思いますが、様々な画像に対応することを考えるとやはり ImageMagick に一任するのが良いように思います。

それでは、よいサムネイルライフを!