ImageMagick の JPEG オプション

JPEG を処理するオプション引数を紹介します。

# -quality &lt;Q&gt;

いわずとしれた画質オプションです。-quality 100 が最高画質で、この値を減らすと画質を犠牲にしてファイルサイズを節約できます。

こちらのポプユニティちゃん画像で実験させて頂きます。

- https://www.pixiv.net/member_illust.php?mode=medium&illust_id=60366682

```shell
for q in `seq 10 10 100` ;
  do convert popu-unity.png -quality $q popu-unity-${q}.jpg ;
done
```

|popu-unity | |
|---|---|
|-quality=10 ; 10395 bytes | ![popu-unity-10.jpg](https://qiita-image-store.s3.amazonaws.com/0/1798/a2e3ff7c-167a-1884-4fd9-96bd3821987e.jpeg)|
|-quality=20 ; 14867 bytes | ![popu-unity-20.jpg](https://qiita-image-store.s3.amazonaws.com/0/1798/539bc31d-0c32-553a-79e0-b7265bf5c319.jpeg)|
|-quality=50 ; 24191 bytes | ![popu-unity-50.jpg](https://qiita-image-store.s3.amazonaws.com/0/1798/1ebda011-60ad-590a-c207-e8eff39acce5.jpeg)|
|-quality=80 ; 38503 bytes | ![popu-unity-80.jpg](https://qiita-image-store.s3.amazonaws.com/0/1798/4df4b877-5869-a159-1657-b4e5e8f1ab8d.jpeg)|
|-quality=90 ; 61733 bytes | ![popu-unity-90.jpg](https://qiita-image-store.s3.amazonaws.com/0/1798/11821f79-2773-56cf-3c31-66da01eb478c.jpeg)|
|-quality=100 ; 156988 bytes | ![popu-unity-100.jpg](https://qiita-image-store.s3.amazonaws.com/0/1798/c724f92d-ce77-6a9e-21bb-6df7e3822adc.jpeg)|

quality 別ファイルサイズをグラフにするとこうなります。グラフは画像によって変わりますが、傾向としては大体の画像でこれと同様の形です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/1798/7c4b7f8c-3dc2-9517-169f-78554b5efb0c.png)

個人的なお勧めとしては、大事なイラストには 90以上、カメラで撮影したものなら 70〜75。どんな画像がくるか分からない場合は 85 位が良いと思います。

## -quality  &lt;Ql&gt;,&lt;Qc&gt;

-quality オプション自体は有名ですが、実は luma(輝度) と chroma(色味) とで値を別々に指定出来ます。

```shell
% convert popu-unity.png -quality 85,10 popu-unity-85,10.jpg
% convert popu-unity.png -quality 85,50 popu-unity-85,50.jpg
```
これで luma(輝度)を85、chroma(色味)は 10 と 50 指定です。

|popu-unity|
|---|
|-quality 85,10 ; 34780 bytes  ![popu-unity-85,10.jpg](https://qiita-image-store.s3.amazonaws.com/0/1798/fdfef3df-d257-f1bc-3f9f-177c94f0591f.jpeg)|
|-quality 85,50 ; 37397 bytes ![popu-unity-85,50.jpg](https://qiita-image-store.s3.amazonaws.com/0/1798/859e6266-418e-26e1-0cf3-5b3474861df1.jpeg)|

イラストだと色味が大事なので誤魔化しにくいですね。。あと色味を犠牲する場合は先にクロマサブサンプリングを使うでしょうから、Luma に比べてその時点でデータ量の少ない CbCr を更に頑張って減らしても、有用な状況は少なそうです。

# -sampling-factor

クロマサブサンプリングを指定出来ます。

```shell
% convert popu-unity.png -sampling-factor 1x1,1x1,1x1 popu-unity-444.jpg
% convert popu-unity.png -sampling-factor 2x1,1x1,1x1 popu-unity-422.jpg
% convert popu-unity.png -sampling-factor 2x2,1x1,1x1 popu-unity-420.jpg
% 
% identify -format "%f %[jpeg:sampling-factor]\n" popu-unity-4??.jpg
popu-unity-420.jpg 2x2,1x1,1x1
popu-unity-422.jpg 2x1,1x1,1x1
popu-unity-444.jpg 1x1,1x1,1x1
```

クロマサブサンプリングって何？という方は、こちらはご参考に。

- JPEG の YCbCr について
  - https://blog.awm.jp/2016/02/06/ycbcr/

輝度の解像度そのままに色味の解像度だけ減らす機能です。横だけ半分にしたり、縦横半分に出来ます。

|YUV422|
|---|
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/1798/a3a455af-b735-dc12-1e75-12c5fbed2d13.png)|

あと、もっと直接的な記法もあります。

```shell
% convert popu-unity.png -sampling-factor 4:4:4 popu-unity-444.jpg
% convert popu-unity.png -sampling-factor 4:2:2 popu-unity-422.jpg
% convert popu-unity.png -sampling-factor 4:2:0 popu-unity-420.jpg
% 
% identify -format "%f %[jpeg:sampling-factor]\n" popu-unity-4??.jpg
popu-unity-420.jpg 2x2,1x1,1x1
popu-unity-422.jpg 2x1,1x1,1x1
popu-unity-444.jpg 1x1,1x1,1x1
```

# -interlace jpeg

大抵の状況ではデフォルトで Baseline JPEG が作られます。
(たしか MozJPEG ライブラリだとデフォルトが Progressive だったかと)
interlace オプションで Progressive JPEG を作れます。

```shell
% convert popu-unity.png -interlace JPEG popu-unity-interlace.jpg
% convert popu-unity.png -interlace None popu-unity.jpg
% ls -l popu-unity.jpg popu-unity-interlace.jpg
-rw-r--r--  1 yoya  staff  64491 12 10 12:11 popu-unity-interlace.jpg
-rw-r--r--  1 yoya  staff  66347 12 10 12:11 popu-unity.jpg
```

Progressive JPEG が何かはこちらを参考にして下さい。

- ImageMagick で JPEG の形式を変換#プログレッシブJPEG
  - https://blog.awm.jp/2016/01/26/jpeg/#プログレッシブjpeg

JPEG は画像の粗い情報と細かい情報を分離する方式で、その粗い情報を先頭に、細かい情報を後ろにまとめるのが Progressive JPEG です。
ネットワークの重たい環境でもファイル読み込み途中でもはじめに粗い画像を表示して段々と細かい画像に変わっていく、大変有用な形式です。

|Baseline と Progressive の比較イメージ|
|---|
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/1798/a876257b-29c4-8ffd-29d8-9e0c41e014c8.png) |

Baseline よりもファイルサイズが減る傾向がありますので、積極的に試すと良いでしょう。
ただ、Progressive は Baseline よりエンコード/デコード共にだいぶ重たい傾向があるので、リアルタイム処理なり大量に処理したい場合等で注意が必要です。

# -comment

任意のコメント文字を入れられます。
(-comment 引数は入力画像ファイルより前にないとコメントが入りませんでした)

```shell
% convert -comment "This is TEST\n" input.png out.jpg
% identify -verbose out.jpg | grep -i comment
    comment: This is TEST
```

ガラケーの頃に、

```
kddi_copyright=on,copy="NO"
```

等と入れて画像のコピーをガードといいますか、プロテクトしてたのが懐かしいです。

# -profile:skip

JPEG ファイルを読み込む時にメタデータをスキップ出来ます。libjpeg の時点でスキップするので、ささやかながらメモリやCPUに優しいのが嬉しいですね。

- -profile:skip=APP (APP2,13,14 以外をスキップ)
- -profile:skip=ICC (APP2 のICCプロファイルをスキップ)
- -profile:skip=IPTC (APP13 のフォトショップリソース)

の３つがあります。

ちなみに APP14 スキップは指定できません。CMYK の時によく含まれるのですが、この内容次第で色の解釈が変わってしまうので、うっかり読み飛ばすと辛い事になります。妥当だと思います。

-trim でもメタデータを消せますが、一度読み込んでから処理するのは勿体無いですし、全てを消すのは危ないので、その意味でこの -profile:skip オプションはお勧めです。

# -define jpeg:

-define jpeg:~ で色々出来るのを一通り紹介します。
jpeglib v8 以降でないと動かないオプションが多いかもしれません。

## jpeg:size

主にサムネール画像を作る時に有用なオプションです。
JPEG は 8x8 DCT 係数でデータを保持するので、iDCT の際に高周波成分の係数を読み飛ばすだけで、1/2、1/4、1/8 のサイズに画像をデコード出来ます。
画像を縮小する際に JPEGデータから真面目に元のサイズのRGB(又はYCbCr)画像を展開して縮小するのでなく、はじめから小さ目のサイズでRGBを展開して、そこから少しだけ縮小する事で CPU にもメモリにも優しく処理が出来ます。

ただし、無条件で適用するのはまずいので、詳しくはこちらは参考にして下さい。

- JPEG の size hinting について
  - http://blog.awm.jp/2016/01/08/jpeghint/

要するに、拡大する時にこのオプションを使うと処理が重たくなります。
巨大な画像から小さなサムネール画像を作る時にとても良いです。

## jpeg:extent

指定したサイズを超えない範囲で、なるべく画質の良い圧縮を探ります。
以前は jpeg:extent があると -quality を無視したり -quality 100 の時だけ jpeg:extent が有効になったりしましたが、ImageMagick-6.9.2-5 からは、 jpeg:extent で指定したサイズを超えない範囲で -quality も反映します。

例えば、この画像で quality だけの場合最大 3MB まで膨らみますが。

```shell
% for q in `seq 10 10 100` ;
  do convert fujisan.png -quality $q fujisan-${q}.jpg ;
done
% ls -lSr fujisan-*.jpg
-rw-r--r--  1 yoya  staff    79118 12 10 11:57 fujisan-10.jpg

-rw-r--r--  1 yoya  staff   146142 12 10 11:57 fujisan-20.jpg
-rw-r--r--  1 yoya  staff   199023 12 10 11:57 fujisan-30.jpg
-rw-r--r--  1 yoya  staff   241849 12 10 11:57 fujisan-40.jpg
-rw-r--r--  1 yoya  staff   283685 12 10 11:57 fujisan-50.jpg
-rw-r--r--  1 yoya  staff   329283 12 10 11:57 fujisan-60.jpg
-rw-r--r--  1 yoya  staff   400925 12 10 11:57 fujisan-70.jpg
-rw-r--r--  1 yoya  staff   519535 12 10 11:57 fujisan-80.jpg
-rw-r--r--  1 yoya  staff   957076 12 10 11:57 fujisan-90.jpg
-rw-r--r--  1 yoya  staff  3445570 12 10 11:57 fujisan-100.jpg
```
jpeg:extent で 1MB 制限をかけると。収まるように画質を調整します。

```shell
%  for q in `seq 10 10 100` ;
  do convert fujisan.png -quality $q -define jpeg:extent=1024KB fujisan-${q}.jpg ;
done
% ls -lSr fujisan-*.jpg
-rw-r--r--  1 yoya  staff    71459 12 10 12:03 fujisan-10.jpg
-rw-r--r--  1 yoya  staff   140378 12 10 12:03 fujisan-20.jpg
-rw-r--r--  1 yoya  staff   199023 12 10 12:03 fujisan-30.jpg
-rw-r--r--  1 yoya  staff   239588 12 10 12:03 fujisan-40.jpg
-rw-r--r--  1 yoya  staff   283685 12 10 12:03 fujisan-50.jpg
-rw-r--r--  1 yoya  staff   329283 12 10 12:03 fujisan-60.jpg
-rw-r--r--  1 yoya  staff   391614 12 10 12:03 fujisan-70.jpg
-rw-r--r--  1 yoya  staff   503428 12 10 12:03 fujisan-80.jpg
-rw-r--r--  1 yoya  staff   737961 12 10 12:03 fujisan-90.jpg
-rw-r--r--  1 yoya  staff  1015086 12 10 12:03 fujisan-100.jpg
```

内部的には、2分探索で quality を変更して条件に合うまで何度も JPEG データを作成するので、重たいかも。。。？

## jpeg:dct-method

DCT の計算を真面目にするか雑にするかの選択です。エンコードにもデコードにも効きます。

- -define jpeg:dct-method=float
  - DCT 係数は通常整数で扱いますが、浮動小数点も対応しています。
  - default, fastest, float, ifast, islow(=default) の５種を指定できます。

```shell
% for m in default fastest float ifast islow ;
  do echo $m ; time convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg ;
done
default
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg  0.32s user 0.07s system 97% cpu 0.402 total
fastest
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg  0.30s user 0.07s system 95% cpu 0.391 total
float
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg  0.26s user 0.07s system 97% cpu 0.340 total
ifast
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg  0.32s user 0.08s system 95% cpu 0.412 total
islow
convert fujisan.png -define jpeg:dct-method=${m} fujisan-${m}.jpg  0.31s user 0.08s system 96% cpu 0.398 total
% ls -l popu-unity-*
-rw-r--r--  1 yoya  staff  66510 12 10 11:19 popu-unity-default.jpg
-rw-r--r--  1 yoya  staff  66538 12 10 11:19 popu-unity-fastest.jpg
-rw-r--r--  1 yoya  staff  66347 12 10 11:19 popu-unity-float.jpg
-rw-r--r--  1 yoya  staff  66538 12 10 11:19 popu-unity-ifast.jpg
-rw-r--r--  1 yoya  staff  66510 12 10 11:19 popu-unity-islow.jpg
```

特に float 指定でファイルサイズが節約できます。これはエントロピーが減る事を示唆するので、つまり画質は落ちるはずです。

なお、デコードでも同様です。ただ、手元で試した限りでは ifast と islow の画像を比較しても ±5 程度の差しかないので実害はないでしょう。ただ違いがある事は知っていて損はないでしょう。

- (参考) TensorFlowからjpeg画像を読み込むとほかのモジュールとちょっと品質が変わるお話
   - https://qiita.com/ppza53893/items/51e3e344b15b22c65b0d#comment-0af7a312a2083ae47c6d

## jpeg:q-table

周波数成分をどのくらい荒く(量子化)保存するのかを周波数別に定義する量子化テーブルを渡す事ができます。ちなみに、JPEG quality はこの2次元テーブル(8x8)を1つの値に代表させたものです。

ファイル名を指定して使います。 &lt;prefix&gt;/etc/ImageMagick-6/quantization-table.xml にサンプルがあるので、これを元に弄ると楽です。

```shell
% cp /usr/local/etc/ImageMagick-7/quantization-table.xml .
% vi quantization-table.xml
% convert fujisan.png fujisan.jpg
% convert fujisan.png -define jpeg:q-table=quantization-table.xml  fujisan-quantize.jpg
% ls -l fujisan.jpg fujisan-quantize.jpg
-rw-r--r--  1 yoya  staff  1149364 12 10 12:20 fujisan-quantize.jpg
-rw-r--r--@ 1 yoya  staff  1071489 12 10 12:20 fujisan.jpg
```

このケースだと、ファイルサイズ的にデフォルトの方が良さそうですね。

ちなみにテーブルは XML 形式。

```xml
<quantization-tables>
  <table slot="0" alias="luma">
    <description>Luma Quantization Table</description>
    <levels width="8" height="8" divisor="1">
      16,  16,  16,  18,  25,  37,  56,  85,
      16,  17,  20,  27,  34,  40,  53,  75,
      16,  20,  24,  31,  43,  62,  91,  135,
      18,  27,  31,  40,  53,  74,  106, 156,
      25,  34,  43,  53,  69,  94,  131, 189,
      37,  40,  62,  74,  94,  124, 169, 238,
      56,  53,  91,  106, 131, 169, 226, 311,
      85,  75,  135, 156, 189, 238, 311, 418
    </levels>
  </table>
```

この Levels ずばりの値が反映されない事があります。特定の係数をがっつり荒い量子化をしたいみたいな大雑把な操作が出来る程度に考えた方が良さそうです。なお、0 指定するとなぜか 1 になります。せめて極端に大きな値として解釈して欲しい。。

## jpeg:optimize-coding

デフォルトだと、最適化されたハフマン符号テーブルを作成しますが、

```shell
-define jpeg:optimize-coding=false 
```

を指定すると、決め打ちのハフマン符号表を用います。
少しだけ処理がはやく終わりますが、サイズが膨らみます。

```shell
% convert popu-unity.png popu-unity.jpg
% convert popu-unity.png -define jpeg:optimize-coding=false popu-unity-h.jpg
% ls -l popu-unity.png popu-unity.jpg popu-unity-h.jpg
-rw-r--r--  1 yoya  staff   73362 12 10 11:06 popu-unity-h.jpg
-rw-r--r--  1 yoya  staff   66347 12 10 11:06 popu-unity.jpg

% time convert fujisan.png fujisan.jpg
 0.27s user 0.08s system 97% cpu 0.351 total
% time convert  fujisan.png -define jpeg:optimize-coding=false   fujisan.jpg
 0.23s user 0.06s system 96% cpu 0.300 total
```

## jpeg:fancy-upsampling

fancy-upsampling は libjpeg のオプションです。
クロマサブサンプリングで Y に比べて CbCr のサンプル数が 1/2 又は 1/4 になるまで間引きされる事があり、画像をデコードして表示する時には Y と同じサンプル数に戻す必要があるので、その際の補間を頑張るか指示します。
なお、libjpeg v6 と v7 以降で true の時の挙動が変わります。turbo-jpeg や mozjpeg は v6 と同じです。

なお、JPEG 画像を読み込んだ時の画像データが画像エンジンによって大きく変わる時は、おそらくこれが原因です。YUV420 や YUV422 でないか疑うと良いです。

### libjpeg-v6, turbo-jpeg-2, mozjpeg-3

- false: Nearest Neighbor で補間する (偽の色が出やすい)
- true: Bi-Linear で補間 (色が淡くなり易い)

```c:jpeg-6b/jdsample.c

METHODDEF(void)
h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr,
                     JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)
{
（略）
        *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4);
        *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4);
```

隣接する CbCr を 3:1 で混ぜます。(横方向で考えると、左隣が lastcolsum で右隣が nextcolsum です)

|![image.png](https://qiita-image-store.s3.amazonaws.com/0/1798/bbf78055-3186-c979-32be-64b891cc96aa.png) |
|---|
|https://www.itu.int/rec/T-REC-T.871-201105-I page.5|

この図の配置の通りに復元しているものと思われます。

###  libjpeg-v7,8,9

true 時の挙動が変わっています。

- true: iDCT scaling デコード機能で補間。ImageMagick の JPEG size hinting と同じ。

JPEG は YCbCr のデータ列を DCT で周波数成分にして保存されています。元の YCbCr 値に戻す時は、周波数成分から iDCT にかけるので、周波数サンプリングが半分でも引き伸ばして復元できます。


```c:jpeg-7/jdmaster.c

  /* In selecting the actual DCT scaling for each component, we try to
   * scale up the chroma components via IDCT scaling rather than upsampling.
   * This saves time if the upsampler gets to use 1:1 scaling.
   * Note this code adapts subsampling ratios which are powers of 2.
   */
  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
       ci++, compptr++) {
    int ssize = 1;
    while (cinfo->min_DCT_h_scaled_size * ssize <=
           (cinfo->do_fancy_upsampling ? DCTSIZE : DCTSIZE / 2) &&
           (cinfo->max_h_samp_factor % (compptr->h_samp_factor * ssize * 2)) == 0) {
      ssize = ssize * 2;
    }
    compptr->DCT_h_scaled_size = cinfo->min_DCT_h_scaled_size * ssize;
    ssize = 1;
    while (cinfo->min_DCT_v_scaled_size * ssize <=
           (cinfo->do_fancy_upsampling ? DCTSIZE : DCTSIZE / 2) &&
           (cinfo->max_v_samp_factor % (compptr->v_samp_factor * ssize * 2)) == 0) {
      ssize = ssize * 2;
    }
    compptr->DCT_v_scaled_size = cinfo->min_DCT_v_scaled_size * ssize;
```

iDCT scaling decode の設定をしてます。

## その他

記事を書くのに疲れてきたので、以下のパラメータの解説は割愛します。

- jpeg:colors
  - libjpeg に減色を依頼するそうです
- jpeg:block-smoothing
  - ブロックノイズを目立たなくするフィルタ
- jpeg:sampling-factor
  - -sampling-factor ときっと同じ

# DPI

JPEG にはメタデータに DPI 情報が含まれる事があり、それを操作するコマンドです。

- 参照するコマンド

```shell
% identify -format '%x,%y\n' sample.jpg
```

なお、DPI 値が JPEG に含まれない場合でもデフォルト値として 72 が表示されます。

- 上書きするコマンド

```shell
% convert input.jpg  -units PixelsPerInch -density 72 output.jpg
```

JPEG の DPI 詳細は以下のエントリをご参照ください。

- JPEG の解像度 DPI 値うんちく
  - https://qiita.com/yoya/items/1133ea1fd6ff8effa46a

# 参考URL

- http://www.imagemagick.org/Usage/formats/#jpg