備忘録的なもの

Hugo の ImageProcessins で WebP 画像のサムネイルを生成すると色がおかしくなったので調べた

ブログに載せている画像を WebP 形式に統一しようとしていたのだが、 convert コマンドで単純に変換した場合に Hugo の ImageProcessing を使ったサムネイル画像の色がおかしくなった。 それを解消するために調べたことをまとめる。

一応最初に断っておくと、自分は画像処理について体系的に何か学んだわけでも無いし普段から趣味で色々やってるわけでも無いので、 あくまで画像素人が調べて試した程度のものとして読んで貰えればと思う。

まとめ

最初に結果だけまとめておくと、変換時に Lossy WebP ではなく Lossless WebP に変換すると解消する。

convert -quality 100 <input.png> <output.webp>cwebp -lossless <input.png> -o <output.webp>

背景

先日 Hugo のテーマを自作のものに切り替えて から、 PageSpeed Insights を回してどこから弄るかの参考にしているのだが、 その中で「 WebP とか最新のフォーマット使うと画像サイズ圧縮できて表示早くなるよ」みたいなことも提案されたので試している。

これまで画像のフォーマット変換は主に convert コマンドを使ってやっていて、 今回も単純にこんな感じのコマンドで WebP 形式に変換して差し替えてみていた。

# 変換コマンド1
convert original.png convert1.webp

のだが、これを Hugo の ImageProcessor を介してサムネイルを生成すると、何故か色が暗くなる。

PNG画像そのまま使った場合のサムネイル表示のキャプチャ画像
PNG画像をそのまま使った場合のサムネイルのキャプチャ
変換コマンド1で変換した場合のサムネイル表示のキャプチャ画像
変換コマンド1で変換したサムネイルのキャプチャ

自分の場合この程度色味が変わった所で困るような画像もそんなに無いとは思うのと、 クリックしてフルサイズで見ると違いは分からなくなる(少なくとも自分の目では)ので無視しても良かったが、 流石に変わり過ぎでは?となったので解消できないか調べたり試したりした。

自分の環境

一応今回作業した環境について触れておくとこんな感じ。

  • OS : Linux (ArchLinux)
  • Imagemagick バージョン : 7.1.1-7
  • libwebp バージョン : 1.3.0 ( libsharpyuv: 0.2.0 )

また、今回色々試すのに使った画像としては前述のブラウザの表示をスクリーンショットしたもので、オリジナル画像の解像度などは以下

❯ exiftool single.png 
ExifTool Version Number         : 12.60
File Name                       : single.png
Directory                       : .
File Size                       : 326 kB
File Modification Date/Time     : 2023:04:26 16:04:08+09:00
File Access Date/Time           : 2023:04:26 16:04:08+09:00
File Inode Change Date/Time     : 2023:04:26 16:04:08+09:00
File Permissions                : -rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 1685
Image Height                    : 1067
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Significant Bits                : 8 8 8 8
Image Size                      : 1685x1067
Megapixels                      : 1.8

カメラで撮影した写真の類もサイト内でいくつか使ってはいるが割合としては少ないし、これからも画面キャプチャなどが多いのは変わらないと思うので、 いろんな種類の画像を使って試すということはしてない。

調べたこと

convert コマンドについて

まず、画像変換に使っている convert コマンドについて改めてざっくりまとめておく。

convert コマンドは ImageMagick という画像処理を行うソフトウェアスイートに含まれているコマンドで、 ImageMagick は様々な形式の画像を処理するソフトウェア群をまとめたパッケージ。 そのソフトウェア群の中に Google が開発した WebP 画像フォーマットを扱うためのライブラリである libwebp も含まれており、 convert コマンドはこの libwebp ライブラリを用いることで WebP 形式の画像を扱っている。

また、libwebp には WebP への変換を行うコマンドである cwebp コマンドも含まれており、 convert コマンドは内部でコレを呼び出す形で処理を行っている。 そのため convert コマンドを経由しなくても、直接 cwebp コマンドを使って他形式から WebP への変換含め WebP 画像を扱うこともできる。

WebP 含め、 ImageMagick でネイティブ対応していない形式の変換はこういった各種ライブラリに委譲する形で処理を行っており、 その際にどういったコマンドを使っているかは ImageMagick のこの辺のコードを眺めるとそれっぽいことが書いてある。(雰囲気で読んでいる)

convert コマンドと cwebp コマンドについて

convert コマンドは、 PNG から WebP への変換を行う際に cwebp を使っていることが分かったが、 cwebp を呼び出す際に convert コマンド内でパラメータを注入してると結果が変わるだろうことが予想される。

本来なら前述のコードを追っかけるのが正しいとは思うのだが、 OSS のコードを読み慣れてないのもあり正しく追える気がしなかったので、実際に変換して結果を比較する形で見てみる。

~/work
❯ ls -lh
合計 320K
-rw-r--r-- 1 shida shida 319K  4月 27 02:34 single.png

~/work
❯ convert single.png single.convert.webp

~/work
❯ cwebp single.png -o single.cwebp.webp
Saving file 'single.cwebp.webp'
File:      single.png
Dimension: 1685 x 1067
Output:    113828 bytes Y-U-V-All-PSNR 43.11 43.22 43.65   43.21 dB
           (0.51 bpp)
block count:  intra4:       1862  (26.22%)
              intra16:      5240  (73.78%)
              skipped:      4873  (68.61%)
bytes used:  header:            462  (0.4%)
             mode-partition:  10948  (9.6%)
 Residuals bytes  |segment 1|segment 2|segment 3|segment 4|  total
    macroblocks:  |       3%|      11%|      21%|      65%|    7102
      quantizer:  |      36 |      34 |      28 |      23 |
   filter level:  |      11 |       7 |       6 |       3 |

~/work
❯ ls -lh
合計 544K
-rw-r--r-- 1 shida shida 112K  4月 27 02:37 single.convert.webp
-rw-r--r-- 1 shida shida 112K  4月 27 02:37 single.cwebp.webp
-rw-r--r-- 1 shida shida 319K  4月 27 02:34 single.png

~/work
❯ sha256sum ./*
6885f0057a6f1eba0e50e5b990010bbaf2364651a68f84009dd55d009e3d33cf  ./single.convert.webp
6885f0057a6f1eba0e50e5b990010bbaf2364651a68f84009dd55d009e3d33cf  ./single.cwebp.webp
03dd86c4ea4c43e2aa8d85d0287ecb15eb387ea14f1b6b66bd6d38f8aaad9ec1  ./single.png

画像比較的な何かをするつもりだったが、ファイルのハッシュ値が同じなのでそこまですることもなく同じ画像を生成しているっぽい。 ということで、convert 内で特別何かパラメータを注入したりはしてなさそう。

変換時の画像品質を変えての比較

Web サイトに載せる画像の圧縮の話でありがちな画像品質の話で、コレを変えることで改善しないかというのを見た。

JPG で画像を圧縮する話でよく見る -quality オプションがあるが、WebP を扱う場合でもそのまま使える。 convert コマンドの help を見ると以下のような形で書いていて、JPG/MIFF/PNG にしか効かないようにも見えるが実際に指定してみると利く。

❯ convert -h | grep quality
  -quality value       JPEG/MIFF/PNG compression level

-quality 100 を指定すると、 Lossless な WebP として処理するようなフラグを立ててくれるっぽい。(雰囲気で読んでいる)

-quality のデフォルト値は 75 になっている。 -quality 75 を指定したものと、 -quality 無指定で生成したものを用意して、 先程同様にハッシュを取ることでも同じファイルが生成されてるのが確認できる。

~/work
❯ ls
single.convert.webp  single.cwebp.webp  single.png

~/work 
❯ convert single.png single.quality-default.webp

~/work 
❯ convert -quality 75 single.png single.quality-75.webp

~/work 
❯ sha256sum ./single.quality-*
6885f0057a6f1eba0e50e5b990010bbaf2364651a68f84009dd55d009e3d33cf  ./single.quality-75.webp
6885f0057a6f1eba0e50e5b990010bbaf2364651a68f84009dd55d009e3d33cf  ./single.quality-default.webp

ということでいくつか -quality の値をいろいろ変えて試してみたのがこんな感じ。(サムネイルのサイズは 400x400)

quality50で生成場合の画像
quality 50
quality75で生成場合の画像
quality 75 (デフォルト)
quality85で生成場合の画像
quality 85
quality95で生成場合の画像
quality 95
quality100で生成場合の画像
quality 100 (Lossless)

-quality の値を上下させても変化はなかったのだが、 Lossless にすると解消することが分かった。 (ちなみにクリックして開くサムネイルに変換する前の画像の方だと自分の目では差が全然分からなかった)

画像品質を上げることによるファイルサイズへの影響も見てみるとこんな感じ。

❯ ls -lh single.png 
-rw-r--r-- 1 shida shida 319K  4月 26 16:04 single.png

❯ ls -lh quality-*
-rw-r--r-- 1 shida shida 111K  4月 27 09:55 quality-100.webp
-rw-r--r-- 1 shida shida  92K  4月 27 09:55 quality-50.webp
-rw-r--r-- 1 shida shida 112K  4月 27 09:55 quality-75.webp
-rw-r--r-- 1 shida shida 143K  4月 27 09:55 quality-85.webp
-rw-r--r-- 1 shida shida 211K  4月 27 09:55 quality-95.webp

個人的には意外な結果になった。 基本的に -quality の値を上げるとファイルサイズもそれに応じて大きくなるのだが、 -quality=100 にしたらデフォルトの -quality=75 と同じ(というかわずかに小さい)くらいのファイルサイズになった。

そうなの?と思って少し調べてみるとこんな感じの Qiita 記事が見つかった。

記事の主旨は若干違うが、 Lossless WebP ( quality = 100 ) は Lossy WebP ( quality < 100 ) と処理が全然違うらしいので、画像によってはこういうこともあるのかもしれない。

Hugo ImageProcessing でのサムネイルの色問題が解消して、画像品質も下げず、ファイルサイズも抑えられるならコレ( Lossless WebP 変換 )で良いじゃんとなった。

とはいえあくまでサンプル画像1個で試した結果でしか無いので、 しばらくは -quality=75-quality=100 の両方で変換してみて基本は Lossless を使う、 Lossless のサイズがすごく大きくなる様なケースがあればまたその時考える、という方針で行こうかなと思う。

感想

変換時の色の調整なども試す必要あるのかなと思っていたのだが、画像品質のパラメータだけで解決したので良かった。

また、 Lossy WebP と Lossless WebP は同じ WebP 形式ではあるものの中身はほぼ別物、 Lossless だからと言って必ずしも Lossy 圧縮よりもファイルサイズが大きくなるというわけでも無い、という知見も得られたのでそういう意味でも良かった。

若干腑に落ちてない部分としては、サムネイルになる前の画像を開くと(少なくとも自分の目では)差が分からないのに、 サムネイルになるとあれだけ色味が変わるのはやっぱり変な気はしている。 Hugo の ImageProcessing の処理に何かあるんじゃないかというのは疑っており、気が向いたらそっちの方向で調べて見ても良いかもしれない。

参考