このエントリー
をはてなブックマークに追加
published on in ImageMagick
tags: ImageMagick JPEG Resize

JPEG の size hinting について

何度もしつこいですが、以下の記事の -define jpeg:size への勝手な補足。

いつでもつければ良いというものではないので注意しましょう。
弊社では、このオプションはサービスの安定運用のためには無用と判断し、
現在このオプションは利用していません。

確かに注意が要りますが、この結論では「JPEG size hinting は危険なので使わない方が良い」と誤解する人が出そうなので勝手に補足します。

前提知識

JPEG のデータの持ち方

JPEG はマクロブロック(8x8)毎に画像の周波数成分のデータを保持していて、JPEG の Decode では波を合成する事でビットマップ画像に戻します。

(参考イメージ)

引用) https://www.cl.cam.ac.uk/teaching/1011/R08/jpeg/acs10-jpeg.pdf

左上の方が低周波、右下の方が高周波成分です。

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 の動作イメージ

詳しくはこちらを参照して下さい。

小さくリサイズする場合

  • 普通にリサイズ (-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 の該当コード

(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) {
      <略>

この 2/block_size scaling の条件にマッチしてそうです。(未確認)

使用上の注意

  • scaling decode は libjpeg8c の新機能なので、libjpeg6 とかだと使えません。
    • libjpeb-turbo は API は v8 互換だけど、どうだろう。(誰か教えて)
    • mozjpeg も知らない。(誰か教えて)
  • 拡大時に size hint を使うとメモリを沢山使って逆効果なので、縮小時だけ使った方が良い。
    • 拡大処理が殆ど使われない。その場合の最大サイズでもメモリが十分足りる。といったケースだと無視しても良い。
  • 縮小時だけ動かす場合、デコード前にファイルの縦横サイズを調べる処理が必要なので、少し面倒。
    • ImageMagick だと先に ping する。
  • scaling decode はリサイズのアルゴリズムが既存のバイリニアやバイキュービックと異なるので、size hint を使った場合と使わない場合とで微妙に結果画像が異なる。基本的に問題はないが用途によっては注意。

参考 URL