ImageMagick のガンマ補正

# はじめに

ImageMagick のガンマ補正解説です。なお、PNG 出力結果は非直感的で個人的に不具合だと思いますが、それを含めて現状を説明します。

# ImageMagick のガンマ補正

ImageMagick のガンマ補正オプションには、-gamma, +gamma があります。あと実は -level もそうです。

- https://www.imagemagick.org/script/command-line-options.php#gamma

>
Gamma adjusts the image's channel values pixel-by-pixel according to a power law, namely, pow(pixel,1/gamma) or pixel^(1/gamma), where pixel is the normalized or 0 to 1 color value. For example, using a value of gamma=2 is the same as taking the square root of the image.

つまり、指定した値の**逆数**で輝度0〜1(max)の値をべき乗計算します。

| ![image.png](https://qiita-image-store.s3.amazonaws.com/0/1798/cf69d5c8-6105-85ee-926a-ea01a3cb146f.png)|
|---|
|(c) [https://ja.wikipedia.org/wiki/ガンマ値](https://ja.wikipedia.org/wiki/%E3%82%AC%E3%83%B3%E3%83%9E%E5%80%A4) |

-level は本来レベル補正(線形コントラスト補正)ですが、同時にガンマ補正も行えます。

- https://www.imagemagick.org/script/command-line-options.php#level

>
-level black_point{,white_point}{%}{,gamma}
   (略)
Gamma will do a -gamma adjustment of the values. If it is omitted, the default of 1.0 (no gamma correction) is assumed.

これらのうち、-gamma は出力先が JPEG や TIFF の際には直感的に使えますが、PNG では恐らく意図しない結果になるでしょう。
PNG の場合は画像のRGBデータと一緒にメタデータのガンマ値(gAMAチャンク)まで弄る為です。

PNG gAMA チャンクについては以下の記事を参考にどうぞ。

- PNG 画像のガンマ補正
   - https://qiita.com/yoya/items/ce8dffc8a8a19746d87c

## サンプル画像

以下のコマンドで生成した画像をサンプルとして用います。グレースケールとカラー画像の２つです。

```shell
% convert -size 200x100 xc:white -fx "floor(i/w*5)/4*u" -depth 8 white.png
% convert -size 200x100 xc:cyan  -fx "floor(i/w*5)/4*u" png24:cyan.png
```

|white.png|cyan.png|
|---|---|
| ![white.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/3a842703-342b-fc32-e968-4348e5d1afc3.png) | ![cyan.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/76f189e2-9037-fcdd-bc50-739c64193f12.png) |

## -gamma

このオプションは低輝度と高輝度をなるべくそのままに、中輝度の明るさを調整したい。といった事に便利です。

こちらだとブラウザで試せるので、単純なガンマ補正を体感したい方はどうぞ。

- https://app.awm.jp/image.js/gamma.html

### JPEG の挙動

まず、JPEG 出力で試してみます。

```shell
% convert white.png -gamma 2.0 white-2.0.jpg
% convert cyan.png  -gamma 2.0 cyan-2.0.jpg
```

| white-2.0.jpg | cyan-2.0.jpg |
|---|---|
|![white-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/ee7adfe2-6147-ab6d-e99a-611636dd6f45.jpeg) |  ![cyan-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/72e85586-778b-8f31-2e4d-02d8ca5be466.jpeg) |

この画像は確かに $ pixel^{1.0/2.0} $ の明るさに変化しています。
これが本来意図した -gamma の挙動でしょう。

### PNG の挙動

PNG 出力だと、結果が明るすぎます。(ただし、画像ビューアによっては JPEG と同じ明るさに見える事もある)

JPEG 出力だと -gamma 2.0 を二回適用したのと同等です。

```shell
% convert white.png -gamma 2.0 white-2.0.png
% convert cyan.png  -gamma 2.0 cyan-2.0.png
% convert white.png -gamma 2.0 -gamma 2.0 white-2.0-2.0.jpg
% convert cyan.png  -gamma 2.0 -gamma 2.0 cyan-2.0-2.0.jpg
```

| white-2.0.png | cyan-2.0.png |
|---|---|
| ![white-2.0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/cad16019-c469-e120-d6e6-90503a27574c.png) | ![cyan-2.0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/0e6abf05-5bb5-a396-b47b-0e9dc3b2f8b9.png)|
| white-2.0.jpg | cyan-2.0.jpg |
|![white-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/ee7adfe2-6147-ab6d-e99a-611636dd6f45.jpeg) |  ![cyan-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/72e85586-778b-8f31-2e4d-02d8ca5be466.jpeg) |
| white-2.0-2.0.jpg | cyan-2.0-2.0.jpg |
| ![white-2.0-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/f25732a7-e2d8-2406-44ba-06a2450a7878.jpeg)  | ![cyan-2.0-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/c17b9bf7-8025-1ba2-8253-4aa7c4e9ef00.jpeg) |

先ほど言及したように、PNG のメタデータのガンマ値が変化しているのが原因です。

```shell
% identify -verbose cyan.png | grep gamma
    png:gAMA: gamma=0.45455 (See Gamma, above)
% identify -verbose cyan-2.0.png | grep gamma
    png:gAMA: gamma=0.9091 (See Gamma, above)
```

0.45455=(1.0/2.2) から 0.9091=(1.0/(2.2/2.0) = 1.0/1.1) に変化しています。
まともな画像ビューアはこの gAMA 値で補正して表示する為、余計に色が明るくなるのです。(たまに無視するのもいます)

これ自体は、+gamma で対処可能です。
とはいえ、この挙動には自分も納得いっていませんし、ワークアラウンド扱いです。
あと、-level はガンマ補正もできて、かつ gAMA を弄らないので、こちらを使う方が良いかもしれません。

## +gamma

+gamma は画像データはそのままで、メタデータのガンマ値だけ上書きするオプションです。なお、0.45455 は 1.0/2.2 相当です。

```shell
% convert white-2.0.png +gamma 0.45455 white-2.0+0.45455.png
% convert cyan-2.0.png  +gamma 0.45455 cyan-2.0+0.45455.png
```

これでガンマ値が元に戻るので、JPEG と同様の表示になる PNG 画像が作れます。

| white-2.0+0.45455.png | cyan-2.0+0.45455.png|
|---|---|
|![white-2.0+0.45455.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/e5689a6d-00f8-55c7-5c69-fe1f0faf622d.png) | ![cyan-2.0+0.45455.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/0b961584-64e9-715e-13d2-366e561fad3a.png)
| white-2.0.jpg | cyan-2.0.jpg |
|![white-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/ee7adfe2-6147-ab6d-e99a-611636dd6f45.jpeg) |  ![cyan-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/72e85586-778b-8f31-2e4d-02d8ca5be466.jpeg) |

```shell
% identify -verbose cyan-2.0+0.45455.png | grep gamma
    png:gAMA: gamma=0.45455 (See Gamma, above)
```

なお、JPEG や GIF はメタデータとしてガンマ値を置けないので、+gamma を使っても表示が変わりません。

## -level

```shell
% convert white.png -level "0,100%,2.0" white-levelgamma-2.0.png
% convert cyan.png -level "0,100%,2.0" cyan-levelgamma-2.0.png
```

こっちは見た目も JPEG と同じです。

| white-levelgamma-2.0.png | cyan-levelgamma-2.0.png |
|---|---|
| ![white-levelgamma-2.0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/57fa4821-4fd4-4fab-fd62-0a606e5fc3ac.png) | ![cyan-levelgamma-2.0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/460597b0-e1cd-467f-b989-6ab012b60db2.png) |
| white-2.0.jpg | cyan-2.0.jpg |
|![white-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/ee7adfe2-6147-ab6d-e99a-611636dd6f45.jpeg) |  ![cyan-2.0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/72e85586-778b-8f31-2e4d-02d8ca5be466.jpeg) |

-gamma と違って gAMA が不変で、RGB データだけ変換するので直感的に使えます。

```shell
% identify -verbose cyan-levelgamma-2.0.png | grep gamma
  Filename: cyan-levelgamma-2.0.png
    png:gAMA: gamma=0.45455 (See Gamma, above)
```

## PNG 生成

ついでに -gamma/+gamma 関係なく、PNG 生成する時に gAMA がどう扱われるかの、簡単なまとめです。

### 新規 PNG 生成

ImageMagick で PNG ファイルを一から生成すると  0.45455(=1.0/2.2) の gAMA チャンクが付与されます。

```shell
% convert -size 200x100 xc:white -depth 8 -fx "floor(i/w*5)/4*u" white.png
% convert -size 200x100 xc:cyan  -fx "floor(i/w*5)/4*u" png24:cyan.png
```

### PNG から変換

画像ファイルを渡して PNG ファイルに変換する場合は、元のガンマ値を引継ぎます。

```shell
% identify -verbose cyan-2.0.png | grep gamma
    png:gAMA: gamma=0.9091 (See Gamma, above)
% convert cyan-2.0.png -resize 50% cyan-2.0-x50%.png
% identify -verbose cyan-2.0-x50%.png | grep gamma
    png:gAMA: gamma=0.9091 (See Gamma, above)

```

### JPEG から変換

JPEG のメタデータにはガンマ値を含められないので、2.2 相当(実際には 1.0/2.2)のガンマ値として PNG 生成されます。

```shell
% convert cyan-2.0.png   cyan-2.0.png.jpg  # メタデータのガンマ値が消える
% convert white-2.0.png  white-2.0.png.jpg # 〃〃
% convert white-2.0.png.jpg white-2.0.png.jpg.png  # ガンマ値がないので、0.45455 が埋まる
% convert cyan-2.0.png.jpg cyan-2.0.png.jpg.png    # 〃〃
% identify  -verbose white-2.0.png.jpg.png  | grep gamma
    png:gAMA: gamma=0.45455 (See Gamma, above)
% identify  -verbose cyan-2.0.png.jpg.png  | grep gamma
    png:gAMA: gamma=0.45455 (See Gamma, above)
```