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

JPEG の size hinting について

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

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

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

-define jpeg:size とは?

JPEG のデータの持ち方

JPEG は画像の周波数成分のデータを保持していて、JPEG の Decode では波を合成する事でビットマップ画像に戻します。尚、8x8 単位で画像をグリッド分割してこの処理をします。

(参考イメージ)

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

scaling 指定で Decode

元のサイズの画像データに変換する場合と比べて、手間を増やさず 12, 14, 18 サイズの画像データに変換できます。高周波成分を見なくて済む上に変換後のサイズが小さい事から、むしろより少ない手間でさえあります。

(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);
  • 縮小では 18 まで 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 倍使うという報告もありますし、2倍か3倍以上小さくリサイズする時だけ -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 の条件にマッチしたのかなと予想してます。

参考 URL