何度もしつこいですが、以下の記事の -define jpeg:size への勝手な補足。
- もうサムネイルで泣かないための ImageMagick ノウハウ集
いつでもつければ良いというものではないので注意しましょう。
弊社では、このオプションはサービスの安定運用のためには無用と判断し、
現在このオプションは利用していません。
確かに注意が要りますが、この結論では「JPEG size hinting は危険なので使わない方が良い」と誤解する人が出そうなので勝手に補足します。
前提知識
JPEG のデータの持ち方
JPEG はマクロブロック(8x8)毎に画像の周波数成分のデータを保持していて、JPEG の Decode では波を合成する事でビットマップ画像に戻します。
(参考イメージ)
左上の方が低周波、右下の方が高周波成分です。
scaling 指定で decode
左上の低周波数成分だけ参照する事で、直接 1/2, 1/4, 1/8 サイズの画像にデコードするのが JPEG の scaling decode で、そのサイズを指示するのが size hinting です。高周波成分を無視出来る上に decode 直後の画像サイズが小さい事から、パフォーマンス向上が見込めます。
(ImageMagick から利用する libjpeg で 2,4,8 のように 2^n に限っているのは、波の合成(iDCT)の高速化で FFT を使う都合か。もしくは波のループの端が合わないか。)
やりたい事
リサイズ後の大きさに近くなるよう scale factor を指定して JPEG を decode し。そこからリサイズする事で、メモリやCPUを節約したい。
JPEG size hinting の動作イメージ
詳しくはこちらを参照して下さい。
- 本当は速いImageMagick: サムネイル画像生成を10倍速くする方法
- http://blog.mirakui.com/entry/20110123/1295795409
小さくリサイズする場合
- 普通にリサイズ (-define jpeg:size 無し)
- 小さいサイズでデコードしてリサイズ (-define jpeg:size 有り)
処理は減るしメモリも少ないし、パフォーマンス的には良い事づくめ。
大きくリサイズする場合 (予想)
問題にしているケースです。
- 普通にリサイズ (-define jpeg:size 無し)
- 大きなサイズでデコードしてリサイズ (-define jpeg:size 有り)
という動作が予想できます。1.5倍のメモリを使うという話も 2, 4, 8 倍で丁度良いサイズになる事はあまりないので、そこそこ話が合います。
実際の動き
jpeg_info の output_width, output_height を表示させて確認したところ、拡大する時には最大でも2倍指定で decode するようです。どんなに元画像とのサイズの差をつけても 4, 8 倍にはなりませんでした。
- fprintf で表示させる
fprintf(stderr, "AAA: jpeg_info:image_width,height:%d,%d output_width,height:%d,%d scale_num:%d scale_denom:%d\n", jpeg_info.image_width, jpeg_info.image_height, jpeg_info.output_width, jpeg_info.output_height, jpeg_info.scale_num, jpeg_info.scale_denom);
jpeg_calc_output_dimensions(&jpeg_info);
fprintf(stderr, "ZZZ: jpeg_info:image_width,height:%d,%d output_width,height:%d,%d scale_num:%d scale_denom:%d\n", jpeg_info.image_width, jpeg_info.image_height, jpeg_info.output_width, jpeg_info.output_height, jpeg_info.scale_num, jpeg_info.scale_denom);
- 縮小では 1/8 まで scaling decode が効く
$ convert -define jpeg:size=8x8 -resize 8x8 8000x8000.jpg 8x8.jpg
AAA: jpeg_info:image_width,height:8000,8000 output_width,height:8000,8000 scale_num:1 scale_denom:1000
ZZZ: jpeg_info:image_width,height:8000,8000 output_width,height:1000,1000 scale_num:1 scale_denom:1000
- 拡大では 2倍までしか効かない
$ convert -define jpeg:size=8000x8000 -resize 8000x8000 8x8.jpg 8000x8000.jpg
AAA: jpeg_info:image_width,height:8,8 output_width,height:8,8 scale_num:1 scale_denom:0
ZZZ: jpeg_info:image_width,height:8,8 output_width,height:16,16 scale_num:1 scale_denom:0
結論
拡大時もそんなに極端にメモリは食わずとも 1.5 倍使われるのは場合によってキツイので、念の為、縮小リサイズする時だけ -define jpeg:size をつけるよう条件分けすれば良いと思います。
scaling の該当コード
- http://gt.awm.jp/ImageMagick-6.9.3-0/S/1372.html#L1105
- ReadJPEGImage (coders/jpeg.c)
jpeg_info.scale_num=1U; jpeg_info.scale_denom=(unsigned int) scale_factor; jpeg_calc_output_dimensions(&jpeg_info);
- ReadJPEGImage (coders/jpeg.c)
(scale_num/scale_denom) 倍で変換するので、縮小しか対応していないように見えますが。libjpeg の中でよしなに処理してくれるようです。
- http://gt.awm.jp/jpeg-8/S/85.html#L52
- jpeg_core_output_dimensions
/* Compute actual output image dimensions and DCT scaling choices. */ if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom) { /* Provide 1/block_size scaling */ cinfo->output_width = (JDIMENSION) jdiv_round_up((long) cinfo->image_width, (long) cinfo->block_size); cinfo->output_height = (JDIMENSION) jdiv_round_up((long) cinfo->image_height, (long) cinfo->block_size); cinfo->min_DCT_h_scaled_size = 1; cinfo->min_DCT_v_scaled_size = 1; } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 2) { /* Provide 2/block_size scaling */ cinfo->output_width = (JDIMENSION) jdiv_round_up((long) cinfo->image_width * 2L, (long) cinfo->block_size); cinfo->output_height = (JDIMENSION) jdiv_round_up((long) cinfo->image_height * 2L, (long) cinfo->block_size); cinfo->min_DCT_h_scaled_size = 2; cinfo->min_DCT_v_scaled_size = 2; } else if (cinfo->scale_num * cinfo->block_size <= cinfo->scale_denom * 3) { <略>
- jpeg_core_output_dimensions
この 2/block_size scaling の条件にマッチしてそうです。(未確認)
使用上の注意
- scaling decode は libjpeg8c の新機能なので、libjpeg6 とかだと使えません。
- libjpeb-turbo は API は v8 互換だけど、どうだろう。(誰か教えて)
- mozjpeg も知らない。(誰か教えて)
- 拡大時に size hint を使うとメモリを沢山使って逆効果なので、縮小時だけ使った方が良い。
- 拡大処理が殆ど使われない。その場合の最大サイズでもメモリが十分足りる。といったケースだと無視しても良い。
- 縮小時だけ動かす場合、デコード前にファイルの縦横サイズを調べる処理が必要なので、少し面倒。
- ImageMagick だと先に ping する。
- scaling decode はリサイズのアルゴリズムが既存のバイリニアやバイキュービックと異なるので、size hint を使った場合と使わない場合とで微妙に結果画像が異なる。基本的に問題はないが用途によっては注意。
参考 URL
- https://en.wikipedia.org/wiki/Libjpeg
- JPEG tutorial
- 本当は速いImageMagick: サムネイル画像生成を10倍速くする方法
- JPEGヒント(scale denom)とは
- JPEG ライブラリを試す
- New djpeg -scale N/8 with all N=1…16 feature