いつもの convert に「-define jpeg:size=...」をつけるだけで10倍速くなる
convert -resize 180x120 src.jpg dst.jpgなどとするとおもうんですが、ここで、高速化のためのオプションを付けます。
convert -define jpeg:size=180x120 -resize 180x120 src.jpg dst.jpg
速くする方法というのは、実は、これだけです。*1
ベンチマーク
4288x2848のJPG画像(4.8MB)から180x120pxのサムネイル画像を以下の条件でそれぞれ10回ずつ生成し、変換にかかる時間を比較しました。
- ImageMagick での普通のリサイズ(縮小アルゴリズムはデフォルトのLanczos)
- ImageMagick で -define jpeg:size オプションを付けた場合のリサイズ(同様にLanczos)
- Imlib2 (imlib2-ruby) でリサイズ
ベンチマークのコードはこちら(gist: 488401)。
結果は以下のとおりです。*2
条件 | 1枚あたりの平均変換時間(sec) | 変換結果(quality=90) |
---|---|---|
1. ImageMagick(jpeg:size なし) | 2.735397 | |
2. ImageMagick(jpeg:size あり) | 0.275900 | |
3. Imlib2 | 0.832415 |
画質はほぼそのままで高速なら、このオプションを付けない理由はないですね。
なんで速くなるのか
なぜ jpeg:size で画像の大きさを指定しただけで、これほどまでに JPEG の縮小が速くなったのかを説明します。
そもそもこのオプションはなんなのかというと、公式ドキュメントに以下のような記述があります。
(以下、Common Formats -- IM v6 Examplesより意訳)
-define jpeg:size={width}x{height}
JPEGライブラリに、JPEGファイルの読み込みのヒントを与えます。このとき、作成される画像は、少なくともこの width x height より大きな画像になります。
もし入力画像が巨大なら、このヒントを与えることで、ImageMagickは小さな画像として開くため、画像読み込み時のメモリを大幅に節約でき、演算を劇的に高速化できるでしょう。
このオプションはあくまでヒントでしかなく、実際にその大きさの画像を得ることができるわけではないという点を忘れないでください。このヒントで指定したサイズに近い、かつそれ以上の大きさの画像を得ることになります。
convert -define jpeg:size=180x120 -resize 180x120 src.jpg dst.jpg
この時の挙動は次のようになります。
(一)ImageMagickは、-define jpeg:size で与えられた 180x120 を src.jpg のサイズとして記憶する
(二)ImageMagickは src.jpg を開き、実際のサイズが 4288x2848 であることを知る
(三)ImageMagickは、実際のサイズの 1/2(2144x1424)、1/4(1608x1068)、1/8(536x356)の中から、 180x120 に一番近く、かつ 180x120 より大きいサイズを選ぶ︵この場合は1/8︶
(四)ImageMagickは、 src.jpg を536x356の画像として開く
(五)ImageMagickは、 src.jpg を 536x356 から 180x120 にリサイズし、 dst.jpg として保存する
図にすると次のようになります。
ImageMagick は、 -define jpeg:size={width}x{height} というオプションが与えられると、実際の画像サイズの 1/2、1/4、1/8 のサイズの中から、オプションで指定されたサイズに近いものを選び、そのサイズとして画像を開きます。どうしてそんなことが可能なのかというと、その仕組はJPEGのデータ構造に由来しています。JPEG画像は、8x8︵場合によっては16x16︶pxの小さな正方形の画像の集合からなっています。詳しい説明は他の資料に任せますが、この構造を活かして、ImageMagickで利用しているJPEGライブラリのlibjpegでは、1/2、1/4、1/8のサイズへの縮小は高速に計算できるのです。
ImageMagickは画像をすべてメモリ上に展開するという特性があるので、元画像が巨大であるほどメモリ消費は激しくなり、そのためのメモリ確保に時間がかかります。それが ImageMagick が遅いと言われる原因のひとつです。リサイズ処理自体は、高速なアルゴリズム︵デフォルトではLanczos︶で行われるので、実は Imlib2 などの他のライブラリに比べても遅いわけではないのです。
この仕組みを簡単に理解するために、convert コマンドに、デバッグ出力をする﹁-debug﹂オプションを加えてみます。
まずは、 -define jpeg:size=180x120 をつけない場合です。
$ convert -debug Coder -log %e -resize 180x120 src.jpg dst.jpg Profile: exif, 45952 bytes Profile: xmp, 5079 bytes Profile: iptc, 76 bytes Interlace: nonprogressive Data precision: 8 Geometry: 4288x2848 :Geometry という項目が、 4288x2848 と出力されています。これは、 src.jpg が本来の 4288x2848 として開かれたことを示しています。 次に、 -define jpeg:size=180x120 を付けてみます。
$ convert -debug Coder -log %e -define jpeg:size=180x120 -resize 180x120 src.jpg dst.jpg Profile: exif, 45952 bytes Profile: xmp, 5079 bytes Profile: iptc, 76 bytes Scale factor: 23.733333333333334281 Interlace: nonprogressive Data precision: 8 Geometry: 536x356 :このとき、 Geometry は 536x356 となっており、この src.jpg は一旦、元画像の 1/8 である 536x356 として開かれたことがわかります。
まとめ
今回は、 ImageMagick で、JPEG画像の縮小を通常の10倍速くする方法を紹介しました。使えるシーンではどんどん -define jpeg:size をつけてみましょう。
おまけ‥ 古いバージョンのImageMagickでは?
この -define jpeg:size オプションは、 ImageMagick-6.5.x 以降から使うことができます。それより前のバージョンでは、-sizeオプションを使うことで同様の効果が得られます。おまけ: MagickWand では?
convert コマンドのオプションは -define jpeg:size=... ですが、同様の効果を MagickWand から得るためには、 MagickReadImage等 の前に
MagickSetOption(wand, "jpeg:size", "180x120");
を呼べばOKです。
おまけ: GraphicsMagick はどうなの?
*1:ImageMagick-6.4.9 かそれ以前のバージョンでは、 -define jpeg:size=... でなく -size=... とします
*2:前回の記事よりCPUが貧弱な MacBook Air 11inch で計測しているのでやや遅めです