WebP の色劣化問題の改善

昔々、WebP が世に知られはじめた頃、JPEG に比べてファイルサイズが節約できると評判の一方、色がくすむ悪評を耳にし距離を置いた人も多いと思います。
実は、libwebp は2年前(2017/1/31)にこの色の問題をオプション扱いで改善しています。libwebp を使っている ImageMagick もようやく昨日(2019/2/18)対応して気軽に試せるようになりました。この機会に少し情報をまとめてみます。 
(2019年02月19日投稿)

# はじめにまとめ

2017年1月リリースの libwebp-0.6.0 から use-sharp-yuv オプションが追加され、ImageMagick は大幅に遅れて 2019年2月に対応しました。
この use-sharp-yuv を有効にすると色劣化の問題はかなり改善されます。少し処理が重たいのと少しファイルサイズが増えるデメリットはあります。(詳細は後述)

# 前提知識

JPEG と同様 WebP は RGB でなく YCbCr でピクセルデータを保持します。その際、JPEG は主に YUV444/YUV422/YUV420 形式を使い分けて色味をどのように間引くか、もしくは間引かないかを選択できますが、WebP は必ず YUV420 形式で色味を間引きます。

YCbCr や YUV420 の詳細は、こちらを参考にしてください。

- https://blog.awm.jp/2016/02/06/ycbcr/ JPEG の YCbCr について
- https://blog.awm.jp/2016/02/10/yuv/ JPEG のクロマサブサンプリングと YUVabc

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/ec550344-2fd8-5e90-5284-0a11e4429759.png)


尚、WebP は Lossless としてもエンコード出来ますが、当エントリは Lossy に限った話をします。

# libwebp-0.4.0 の頃の話 (2013年12月リリース)

- 吉里吉里Z 開発:: なぜ JPEG XR ？
   - http://www.kaede-software.com/2014/05/_jpeg_xr.html
<img alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/ec099df1-778e-5171-6a0a-249d80eeb385.png">

実写だとレアケースですが、イラストやCGのように鮮やかな赤/青の細い線が使われる画像だと問題になる可能性があります。例えば、ギリギリ読める小さな字が読めなくなるとかですね。

JPEG でも YUV420 だと似たように色が黒くなるのと、WebP 内画像エンコード方式の VP8 が YUV420 固定なので、将来に渡って改善されようのない絶望感が広がっていたように記憶しています。今でも WebP がディスられる際の筆頭ネタでしょう。

でも、クロマサブサンプリングの処理自体を知ってる人は当時から思っていたはずです。これは YUV420 自体の限界ではない。444 を 420 に変換する具体的な処理方式の問題であると。。

とまぁ、処理方式の詳細を書くと長くなるので。とりあえず改善された libwebp の話をします。

# libwebp-0.6.0 での改善 (2017年1月リリース)

libwebp v0.6.0 で追加された use-sharp-yuv オプションによって色の改善がなされています。

## 実験画像

```shell
% convert -size 256x256 xc:white \
    -fx "(i%33==0)?red:(j%33==0)?blue:(i+j>w)?0:u" \
    rb-grid.png
```

|rb-grid.png(実験用サンプル画像)|
|---|
|<img alt="rb-grid.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/85186238-dbbe-461b-ca68-fdb1a8c9fe83.png">|

なお、Qiita は WebP 画像を貼り付けられないので、WebP から PNG に戻した画像を代わりに貼ります。

## 画像比較

```shell
% convert rb-grid.png rb-grid.webp
% convert rb-grid.png -define webp:use-sharp-yuv=1 rb-grid-sharp-yuv.webp
% convert rb-grid.png -sampling-factor 4:2:0 rb-grid-YUV420.jpg  # (参考)
```
| rb-grid.png | rb-grid.webp(.png) |
|---|---|
| <img alt="rb-grid.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/85186238-dbbe-461b-ca68-fdb1a8c9fe83.png"> | <img alt="rb-grid.webp.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/721f72b0-3d35-6e3b-5c38-72e2b68b1759.png"> |
| rb-grid-sharp-yuv.webp(.png) | (参考) rb-grid-YUV420.jpg |
| <img alt="rb-grid-sharp-yuv.webp.png" src="https://qiita-image-store.s3.amazonaws.com/0/1798/da1b9dbb-9976-fdb8-ea13-04a1e8f4d453.png"> | <img alt="rb-grid-YUV420.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/1798/6f58c8de-9ad5-aebb-638c-9b6a245a5a00.jpeg"> |

YUV420 の限界で 1pixel 分の滲みは出ますが、色のくすみ具合は目に見えて改善されています。

## デメリット

サイズが少し増える傾向があります。以下の例だと1〜2割増えます。

```shell
% ls -l rb-grid.webp rb-grid-sharp-yuv.webp
-rw-r--r--  1 yoya  staff  3118  2 19 19:40 rb-grid-sharp-yuv.webp
-rw-r--r--  1 yoya  staff  2770  2 19 19:39 rb-grid.webp
```

あと、少し時間がかかります。色味があうまで繰り返し処理する為です。以下の例では2割ほど増えます。

```shell
% convert -size 4032x3024 xc:white -fx "u*rand()" 4032x3024.ppm
% time ~/ImageMagick/6.9.10-28/bin/convert 4032x3024.ppm t.webp

real	0m2.486s
user	0m2.294s
sys	0m0.172s
% time ~/ImageMagick/6.9.10-28/bin/convert 4032x3024.ppm  -define webp:use-sharp-yuv=1 t.webp

real	0m2.907s
user	0m2.651s
sys	0m0.210s
```

色が鮮やかになる分、偽色が出るのではという懸念については、繰り返し処理で色味が変わらないものを探すので、デグレードしにくいはず。ただ検証は必要でしょう。誰かお願いします。(他力本願)

# ImageMagick の対応 (2019年2月リリース)

7系は 7.0.8-28、 6系は 6.9.10-28 で webp:use-sharp-yuv オプションにて対応しました。2月18日真夜中のリリースです。

- ImageMagick-6.9.10-28差分
   - https://yoya.hatenadiary.jp/entry/20190218/im

それ以前のバージョンの ImageMagick で -define webp:use-sharp-yuv=1 をつけても無視されるだけです。

蛇足ですが、ImageMagick-7.0.8-26, 6.9.10-26 でこの機能を入れたつもりが、libwebp バージョンの #if スイッチを間違えていて無効のまま、-28 でやっと使えるようになりました。(直したの僕)

- https://github.com/ImageMagick/ImageMagick/commit/aa5bcab23fe09e317eea0fc8775df6452ba90471
- https://github.com/ImageMagick/ImageMagick/commit/5ce163fffe13d3307a1bf7cb43047c6382f3de8a

```c
WebPConfig
  configure;
（略）
#if WEBP_ENCODER_ABI_VERSION >= 0x020e
value=GetImageOption(image_info,"webp:use-sharp-yuv");
if (value != (char *) NULL)
  configure.use_sharp_yuv=StringToInteger(value);
#endif
```

このように libwebp を直接使う場合は、WebPConfig の use_sharp_yuv で操作できます。
libwebp-6.0.0 未満との互換性を気にするのであれば、WEBP_ENCODER_ABI_VERSION >= 0x020e でスイッチすると良いです。

# 最後に

古い libwebp を使っててバージョンアップ出来ないという方は、バージョンを上げると速くなるかもしれないのを説得材料に、併せて対処すると良いと思います。

- libwebp のバージョンをあげると速くなる
   - https://qiita.com/yoya/items/bd5f8ff3be6a57586d03

ImageMagick で WebP を処理する時の参考にこちらをどうぞ。

- ImageMagick と WebP
   - https://qiita.com/yoya/items/0848a6b0b39db4cd57c2