じーろぐ

日々の記録。コンピュータやプログラミングの話題が多め。

PNG画像最適化ツールOptipngとZopfliを使ってどこまでファイルサイズを小さくできるか検証する

画像のファイルサイズを小さくすることは、Webページのロード速度の高速化に繋がり、これは回線品質が低いモバイル環境などでは重要です。 ここでは、代表的なPNGファイル最適化ツールOptipngとZopfliを使って、ファイルサイズがどこまで小さくなるか検証します。

これらのツールを使う場合、様々な最適化に関するオプションの値をどのように設定するかが問題になります。 このエントリでは、最適化オプションの組み合わせや値を変えて、ファイルサイズがどのように変化するか検討しました。

以前投稿したエントリ「JPEG最適化ツールMozjpegとGuetzliの性能を徹底検証」のPNG版でもあります。

そもそもPNGはどのようにしてファイルサイズを小さくしているか?

PNGで用いられている圧縮アルゴリズムはdeflateと呼ばれるもの(zipなどで使われています)で、フォーマットはzlib形式が用いられます(逆に言うと必ずzlib形式である必要があります)。 圧縮率を上げるため、画像を(deflateアルゴリズムで)圧縮する前に前処理(PNGではフィルター処理と呼んでいます)を行う点が大きな特徴です。 フィルターは5種類用意されていてラインごとに変更できるため、フィルターの施し具合によってファイルサイズは変化します。

すなわち、PNGファイルのサイズを削減するということは、

  • 適切なフィルター処理を行う(適切な前処理を行って圧縮しやすくしておく)
  • zlibのパラメータ(圧縮レベルなど)を最適化する

ことを意味します。

PNGファイルのフォーマットに関する詳細は以下のサイトがわかりやすいです。

可逆圧縮不可逆圧縮か?

PNGJPEGの大きな違いはロスレス圧縮かどうかという点です。画像ファイルのフォーマットとしてPNGファイルを選択した場合、画質を保ちたい(できれば同一)場合が多いかと思います。ここでは、可逆圧縮のまま最適化した場合、どのぐらいファイルサイズを削減できるか検証してみました。

検証環境

JPEG最適化ツールMozjpegとGuetzliの性能を徹底検証と同じWindows10+AMD環境です。

  • OS : Windows10 1803 (ビルド 17134.590)
  • CPU : AMD Ryzen 5 2600 (6C12T, base clock:3.4GHz)
  • Mem: 16GB (DDR4)
  • DISK; SSD 480GB (SATA6G接続; SanDisk SDSSDXPS480G)

検証に使った画像

テストには4種類のpng形式の画像を使いました。解像度とファイルサイズは以下のとおりです。

Name File size (KB) Resolution (px)
CG 2973 2048 x 1536 Wikipediaにあるサンプル画像
figure 115 1173 x 852 Python+pandasで生成されたグラフ画像
manual 454 2331 x 1654 電化製品のマニュアルを画像化したもの
screenshot 753 720 x 1280 スマートフォンスクリーンショット

それぞれの実際の画像以下の通りです。PNG形式でよく扱いそうな画像を選択しました。 (25%縮小しているので実際のファイルとは異なります)

f:id:z_logger:20190403022529p:plain

ロスレス最適化ツールの検証

Optipng

まずは、代表的なPNGファイルの最適化ツールであるOptipngの性能をテストしました。 Optipngは、zlibとlibpngのパラメータを変えながら最もファイルサイズが小さくなるパラメータの組み合わせを探すことで画像の最適化を試みます。

テストにはOptiPNG version 0.7.7を使用しました。

最適化オプション詳細

最適化に関連するオプションは以下の通りです。

Option
-f PNG delta filters (0-5) [default: 0,5]
-zc zlib compression levels (1-9) [default: 9]
-zm zlib memory levels (1-9) [default: 8]
-zs zlib compression strategies (0-3) [default: 0-3]
-zw zlib window size (256,512,1k,2k,4k,8k,16k,32k)
-nb no bit depth reduction
-nc no color type reduction
-np no palette reduction
-nx no reductions
-nz no IDAT recoding

この中で、-fオプションがフィルター処理に、zで始まるオプションが、圧縮処理で関係するオプションです。 わかりにくいのが、fオプションですが、0~5の数字を複数指定でき、0~4はそれぞれ仕様で定義されている5種類のフィルターに対応します。 5は、libpngで定義されている adaptive filteringに影響するオプションみたいです。したがって、-f0-5と指定するとフィルターについて全6パターン試して最もファイルが小さくなるフィルターを探します。

全部手動でセットすると大変ですが、 -o[0-7]オプション を使うことで簡単に8段階で最適化レベルを選択できます。具体的には以下の通りになります。 最適化レベルが上がるに連れて試すパラメータのパターンが多くなるため処理時間も長くなります。

Optimization levels:
    -o0         <=>     -o1 -nx -nz                             (0 or 1 trials)
    -o1         <=>     -zc9 -zm8 -zs0 -f0                      (1 trial)
                (or...) -zc9 -zm8 -zs1 -f5                      (1 trial)
    -o2         <=>     -zc9 -zm8 -zs0-3 -f0,5                  (8 trials)
    -o3         <=>     -zc9 -zm8-9 -zs0-3 -f0,5                (16 trials)
    -o4         <=>     -zc9 -zm8 -zs0-3 -f0-5                  (24 trials)
    -o5         <=>     -zc9 -zm8-9 -zs0-3 -f0-5                (48 trials)
    -o6         <=>     -zc1-9 -zm8 -zs0-3 -f0-5                (120 trials)
    -o7         <=>     -zc1-9 -zm8-9 -zs0-3 -f0-5              (240 trials)
    -o7 -zm1-9  <=>     -zc1-9 -zm1-9 -zs0-3 -f0-5              (1080 trials)

(デフォルトの)-o2だと、zlib compression levelとmemory levelはそれぞれ、9と8に固定し、zlib compression strategiesを全4パターン、フィルターを2パターンについて総当たり(すなわち4x2で8回)で画像生成処理を行い、ファイルサイズの削減を試みます。

また、-zwオプションは触らないみたいですが、デフォルトのウインドサイズが最大の32kだからだと思われます。

最適化レベルを変えたテスト結果

最適化レベルを変化させて、用意した4種類のファイルを圧縮した結果が以下になります。(オリジナルのファイルサイズを100%としたファイルサイズ)

Parameter CG Figure Manual Screenshot
-o0 99.85% 99.85% 99.85% 99.85%
-o1 99.85% 87.81% 76.35% 97.74%
-o2 99.85% 78.16% 76.06% 97.74%
-o3 99.72% 78.16% 76.06% 97.69%
-o4 99.85% 78.16% 76.06% 97.74%
-o5 99.72% 78.16% 76.06% 97.69%
-o6 99.85% 78.16% 76.06% 97.74%
-o7 99.72% 78.16% 76.06% 97.69%
-o7 -zm1-9 99.72% 77.54% 75.80% 97.69%

わかったことは、

  • 用意したファイルのうち2種類(CG, Screenshot)はほとんど最適化の効果なし
  • 効果があったファイル(Figure, Manual)の関しては、20~25%ほどファイルサイズが減少
  • 最もファイルサイズが小さくなったオプションは、全てのファイルで-o7 -zm1-9
  • とはいえ、-o3でも-o7 -zm1-9とほぼ同等の効果が得られる

最適化レベルと処理時間の関係は以下の通りでした(CG画像の結果を抜粋、最適化レベル8は-o7 -zm1-9に対応)。

f:id:z_logger:20190403013355p:plain

最適化レベル5以上は非常に時間がかかるので、実用的には-o3が良さそうです。

Zopfli

次に、Googleが開発したZopfliを使ってみます。このアルゴリズムは、

  • deflateより3~8%程度圧縮率が高い
  • ただし、圧縮にかかる時間が数十倍
  • 圧縮後のデータは、deflate互換でデコードにかかる時間も同じ

という特徴があります。一瞬「PNGってdeflateしか使えないんじゃないの?」と思いますが、デコードはdeflate互換なので、PNGファイルの圧縮に使えるんですね。 PNGの圧縮時にこのZopfliアルゴリズムを使うことでファイルサイズの低減を試みようというわけです。

なお、本家のページでは、バイナリーファイルは提供されていませんが、以下のサイトからダウンロードできます。

github.com

最適化オプション詳細

以下が、最適化に関連するオプションです。

Option 説明
-m compress more: use more iterations (depending on file size)
--lossy_transparent remove colors behind alpha channel 0. No visual difference, removes hidden information.
--lossy_8bit convert 16-bit per channel image to 8-bit per channel.
-q use quick, but not very good, compression (e.g. for only trying the PNG filter and color types)
--iterations=[number] number of iterations, more iterations makes it slower but provides slightly better compression. Default: 15 for small files, 5 for large files.
--filters=[types] filter strategies to try:
0-4: give all scanlines PNG filter type 0-4
m: minimum sum
e: entropy
p: predefined (keep from input, this likely overlaps another strategy)
b: brute force (experimental)
--keepchunks=nAME,nAME,. keep metadata chunks with these names that would normally be removed, e.g. tEXt,zTXt,iTXt,gAMA, ...

--filtersオプションで、フィルター処理についても最適化できるようです。しかも結構凝っています。

以下、ヘルプの説明を引用。

By default, if this argument is not given, one that is most likely the best for this image is chosen by trying faster compression with each type. If this argument is used, all given filter types are tried with slow compression and the best result retained. A good set of filters to try is --filters=0me.

テスト結果

まずは、オプション無しで圧縮したときの結果から。

Image file Size Execution time (sec)
CG 96.27% 103.96
Figure 75.25% 9.81
Manual 69.79% 33.37
Screenshot 96.21% 19.44

確かに圧縮率はすべてのファイルでOptipngを使ったときより高くなりました!

圧縮率の傾向はOptipngと完全に一緒です。つまり、

  • 用意したファイルのうち2種類(CG, Screenshot)はほとんど最適化の効果なし
  • 効果があったファイル(Figure, Manual)の関しては、25~30%ほどファイルサイズが減少

つづいて、他のオプションも試してみます。 まずは、フィルター周りについて検討してみます。

  • --filters=0me : ヘルプに「もっと圧縮したいとき試してみて」みたいに書いてあったので。
  • --filters=01234me : p(predefined), b(brute force)以外
  • --filters=01234mepb:フィルター全のせ

まずは、ファイルサイズの違いを見てみます。

Image file default --filters=0me 01234me 01234mepb
CG 96.27% 96.27% 96.27% 95.43%
Figure 75.25% 72.65% 72.65% 71.52%
Manual 69.79% 69.79% 69.79% 69.79%
Screenshot 96.21% 96.21% 96.21% 96.21%

試すフィルターが多いほど圧縮率は高くなりますね。 ManualやScreenshotみたいに、ほとんどファイルサイズの変わらないものもあります。

当然ですが、実行時間(秒)は長くなります。

Image file default 0me 01234me 01234mepb
CG 102.65 183.77 485.72 721.56
Figure 9.77 25.73 68.15 86.66
Manual 33.13 60.52 145.01 206.65
Screenshot 19.33 40.72 113.94 174.74

--filters=0meでデフォルトの約2倍時間がかかるようです。--filters=01234meとか--filters=01234mepbは実行時間的に非現実的ですね。

つづいて、繰り返し回数。回数が多いほど圧縮率は高くなります。

f:id:z_logger:20190403013436p:plain

  • 繰り返し回数が20回を超えるとほとんどファイルサイズに変化がありませんでした。
  • 繰り返し回数が増えるとそれに比例して実行時間が伸びるので、--iterations=20以上を設定する必要はなさそうです。
  • ファイルサイズに合わせて反復回数を調整してくれる-mオプションがあるので、これを指定するのもあり

結論

結局どのツールを使って、どういうオプションを付けるべきかということになるのですが、以下のような感じかと思います。

  • ファイルサイズを最適化したいけど、処理に時間はとにかくかけたくない人
    • optipngで-o3オプションを付けて処理
  • ブログやウェブサイト用に最適化処理したい人。
    • Zopfliを使ってデフォルトオプションで処理
  • そこそこアクセスのあるサイトのトップページの画像など。とにかくサイズを小さくしたい人
    • zopflipng --lossy_transparent --lossy_8bit --iterations=20 --filters=0me

関連記事

PNGファイルのサイズを減色ツールを使って半分以下にする

zlog.hateblo.jp

JPEG最適化ツール検証:

zlog.hateblo.jp

画像の最適化をドラッグアンドドロップで行えるバッチファイル:

zlog.hateblo.jp