はじめに

この記事は Graphical Web Advent Calendar の 16 日目の記事として書かれました。
Graphical Web ということで、PNG フォーマットの簡単な説明と Web ブラウザ上で動作する PNG 解析ツールを作ったので使い方と解析結果の見方について書いていこうと思います。
また、人気のある PNG 画像最適化ツールがどのような最適化を行っているのか調べていきます。

PNG の仕様に入る前に

ここから、PNG の仕様について最低限の説明を書いていきます。
PNG の最適化や検証するときに必要になるので、退屈かもしれませんが軽く目を通してください。
以下の項目について、なんとなく分かれば良いです。

  • シグネチャ
  • 必須チャンクの役割
    • IHDR
    • PLTE
    • IDAT
    • IEND

PNG 仕様概要

PNG フォーマットは、先頭にシグネチャがあり、そこからチャンクと呼ばれるデータが連続する形で構成されています。

structure

シグネチャ

シグネチャは PNG ファイルであることを確認するために使用されます。
シグネチャは必ず以下の8バイトとなっています。

0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a

PNG ファイルをテキストエディタなどで開いたときに先頭のあたりに「PNG」という文字を見かけた事はないでしょうか?
それはこのシグネチャを ASCII にしたときの表示の一部です。

00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010  00 00 01 00 00 00 01 00  08 06 00 00 00 5c 72 a8  |.............\r.|
00000020  66 00 00 00 19 74 45 58  74 53 6f 66 74 77 61 72  |f....tEXtSoftwar|
00000030  65 00 41 64 6f 62 65 20  49 6d 61 67 65 52 65 61  |e.Adobe ImageRea|
...

チャンク

チャンクはデータを格納する形式で、PNG ではシグネチャをのぞいて全てのデータがこの形式で記述されています。
チャンクは以下のような形になっています。

データの大きさチャンクタイプデータ破損チェック
4 Byte4 Byte可変4 Byte

チャンクの大きさは計算可能なので、対応していないチャンクタイプでも読み飛ばすことで表示に影響を与えないようにする事ができます。

  • データの大きさ
    • 文字通り、データの大きさが整数で入っている
    • つまり、チャンクの大きさは 12 + この数値となる
  • チャンクタイプ
    • ASCII で 4 文字はいっている。先頭が大文字なら必須チャンク
  • データ
    • チャンクタイプごとに決められた形式ではいっているデータ
  • 破損チェック
    • CRC-32 という形式でチャンクタイプ+データが破損していないか確認するためのデータ

必須チャンク

PNG では画像として最低限必要な情報を必須チャンクと呼び、以下のものが必須チャンクとされています。

  • IHDR
  • PLTE (ただし、画像タイプが Indexed Colour のときのみ必須)
  • IDAT
  • IEND

PNG はこの4つのチャンクさえサポートしていれば大体なんとかなるので、今回は必須チャンクに絞って説明します。

IHDR チャンク

IHDR チャンクは画像の形式に関する情報を扱います。

横幅縦幅ビット深度カラータイプ圧縮方法フィルタ方法インタレース方法
4 Byte4 Byte1 Byte1 Byte1 Byte1 Byte1 Byte
  • 横幅
    • 画像の横幅
  • 縦幅
    • 画像の縦幅
  • ビット深度
    • 色情報につかうビット数を指定します
    • RGB の時、ビット深度が 8 だったら R8G8B8 で 24 bit になります
    • グレイスケールの時、ビット深度が 8 だったら 8 bit になります
    • 1, 2, 4, 8, 16 が有効ですが、カラータイプによって選択できない組み合わせもあります
  • カラータイプ
    • 0: Grayscale (グレイスケール)
    • 2: True Colour (RGB形式)
    • 3: Indexed Colour (パレット形式、カラーマップ形式)
    • 4: Grayscale with Alpha (アルファチャンネル付きグレイスケール)
    • 6: True Colour with Alpha (RGBA形式)
  • 圧縮方法
    • 0: Deflate
  • フィルタ方法
    • 0: Basic
  • インタレース方法
    • 0: None
    • 1: Adam7

PLTE チャンク

カラータイプが Indexed Colour のときだけ必須となり、パレットの情報を扱います。
それ以外のカラータイプでは優先して表示する色を表すチャンクとなります。
データ形式は以下のように RGB でそれぞれ 1 Byte ずつのものが最大 256 個まで連続しています。
いくつ連続しているかはチャンクのデータサイズを 3 で割って算出します。

1 Byte1 Byte1 Byte

また、 Indexed Colour 形式でアルファチャンネルが必要なときは、この後にアルファチャンネルのみの tRNS チャンクが追加されることがあります。

IDAT チャンク

画像本体です。IDAT チャンクでは ZLIB という形式で圧縮された画像の色情報がはいっています。
画像の色情報は、行ごとに分かれていて以下のようになっています。

フィルタ色情報色情報色情報...
1 Byte可変可変可変...

色情報はビット深度と Colour Type によって変わります。

  • Grayscale: (ビット深度) bit
  • Indexed Colour: (ビット深度) bit
  • Truecolor: (ビット深度 * 3) bit
  • Truecolor with alpha: (ビット深度 * 4) bit
  • Grayscale with alpha: (ビット深度 * 2) bit

フィルタについては以下のようになっています。

  • 0: None (そのまま)
  • 1: Sub (左の Byte との差)
  • 2: Up (上の Byte との差)
  • 3: Average (左と上のバイトの平均との差)
  • 4: Paeth (Paethアルゴリズムによる値(詳細は仕様を見てください))

フィルタというとぼかしたりなどのエフェクトを思い浮かべる方もいるかもしれませんが、PNG でいうフィルタとはデータを偏った形で表現するための仕組みです。
グラデーションが多用される画像などでは、隣接する色との差分にした方が圧縮しやすくなるためです。

フィルタに関して注意する点が一つあり、一部の解説では「隣の色」とされていることがありますが「隣の色を含むバイト」との比較です。例えば、RGBA それぞれ 8bit ずつだとすると、隣の対応するバイトとの距離は 4 バイトになります。以前は「隣のバイト」と書いてしまっていたので修正しました。(2013/06/28追記)

また、IDAT チャンクはデータ部分を分割して複数の IDAT チャンクにすることができます。その際、IDAT は必ず連続していなければいけません。

IEND チャンク

画像の終わりを示す空チャンク。データ部分にはなにも入りません。

PNG ファイルの解析

ここまで、PNG の仕様について説明してきました。
いきなり、延々と仕様の話しをされてうんざりした人もいるかもしれません。
しかし、多くの Web 開発関係者は画像サイズを小さくしようとしていますが、そのために PNG とはどういうものか把握しておくのはとても重要な事です。
残念な事に Web 上の情報を見ていると、PNG がどういうものか理解せずにサイズを小さくすると謳って、結果誤った事を書いたものが多く見られます。

ここからは、最近人気のある最適化ツールが一体どのような最適化を行っているのか検証していきます。
最適化については、別のブログで簡単に書いたのでそちらも参考になるかもしれません。

png Identify

では、解析をどのようにするかですが、ちょうど良いツールが見当たらなかったので JavaScript で自作しました。
これは他の PNG の情報ツールでは見られないような圧縮関連の情報も表示するツールとなっていて、圧縮効率の調査や最適化アルゴリズムの調査に活用する事が出来ます。

調査するツール

以下の最近人気のある最適化ツールなどについて調べていきたいと思います。

最適化対象の画像

ライセンスがはっきりしていて、PNG に向いているイラスト調の画像なので「うぶんちゅ!」という漫画の表紙絵を例に使わせていただきます。

ubunchu01_01

サイズ比較

まずは、一番気になるサイズ比較から行っていきましょう。

元画像PNGGauntletImageOptimAzConvPNG
サイズ1,698,5061,629,6681,631,5061,702,386
差分--68,838-67,000+3,880

Indexed Colour 形式での挙動も調べるため、ImageAlpha で 256 色に減色した画像も比較します。

元画像PNGGauntletImageOptimAzConvPNG
サイズ520,806494,704493,989519,658
差分--26,102-26,817-1,148

Truecolor 形式では PNGGauntlet, Indexed Colour では ImageOptim が優れた結果となりました。
では、ここから各ツールでどのような最適化が行われたか見ていきます。

チャンク構成

Truecolor

元の画像では以下のチャンクが付与されていますが、各ツールでは除去されています。

  • pHYs (Physical pixel dimensions)
  • iCCP (Embedded ICC profile)
  • cHRM (Primary chromaticities and white point)
元画像のチャンク構成
chunk_source
ImageOptim で最適化した後のチャンク構成
chunk_imageoptim

これらは、表示する際に色や大きさを決定する際に使われることもありますが、ブラウザによっては無視される事もあるため削除しても特に問題にならない事が多いです。
(これらのチャンクに対応したブラウザで、きっちりと表示色などを制御したい場合は削除してはいけません。)

Indexed Colour

元画像を ImageAlpha で減色した後のチャンク構成
chunk_imagealpha
上記のものを PNGGauntlet で最適化した後のチャンク構成
chunk_imagealpha_pnggauntlet

ImageAlpha で減色した画像では gAMA (Image gamma) という表示に関するチャンクが付与されていますが、これも無視されることがあるので削除してもあまり影響しません。

それよりも特徴的なのは、IDAT が細かく分割されている点です。これはファイルサイズが増えるだけなので最適化ツールにかけると一つにまとめられています。

フィルタ最適化

PNG の特徴であるデータを偏らせる行毎のフィルタですが、どういう状況でどのフィルタを選択するのがベストかというのは実際にやってみるしかありません。
また、仕様にも「Indexed Colour の時はフィルタを使わない方が良い」と書いてあるように、最適化ツールでも Indexed Colour の時はすべて None になっています。(ので省略します)

元画像

filter_source

PNGGauntlet

filter_pnggauntlet

ImageOptim

filter_imageoptim

AzConvPNG

filter_azconvpng

PNGGauntlet と ImageOptim のフィルタの並びが同じになりましたが、これは共に PNGOUT, OptiPNG を使用しているので、同じ結果になったのだと思います。AzConvPNG はフィルタの最適化は行っていないようです。

パレット最適化

ImageAlpha で減色した画像は (Indexed Colour なので当然ですが) パレットをもっており、このパレットの並び順によっても ZLIB の圧縮率に影響する事があります。

記事が縦長になってきたので画像は省略して結論だけ以下にまとめます。

  • AzConvPNG: 独自の並び替え(元のパレットと全然違う)
  • PNGGauntlet: 元のパレットと似ているけど多少異なる
  • ImageOptim: 並び替えを行っていない

ZLIB の圧縮率

ここからが本題です。

フィルタやパレットの最適化を行っても、それだけではファイルサイズは変わりません。
しかし、フィルタやパレット最適化によって「圧縮しやすい状態」になった画像は最終的なファイルサイズで大きな差がでます。
実際に見てみます。

Truecolor

元画像
zlib_source
PNGGauntlet
zlib_pnggauntlet
ImageOptim
zlib_imageoptim
AzConvPNG
zlib_azconvpng

Truecolor では PNGGauntlet と ImageOptim が優秀な結果を出しています。ファイルによって優劣が入れ替わる事も多々あるので、この二つはどちらもほぼ互角といって良い性能ではないでしょうか。
それぞれの各ブロックの圧縮をみると、LZSSの平均長(LZSS-Avg) が似た傾向にあり、どちらもかなりの高圧縮となっています。

Indexed Colour

元画像
zlib_azconvpng
PNGGauntlet (すいません、ブロック数が多すぎたので省略してます)
zlib_pnggauntlet_p
ImageOptim
zlib_imageoptim_p
AzConvPNG
zlib_azconvpng_p

こちらも PNGGauntlet と ImageOptim がほぼ互角です。しかし、パレットの入れ替えを行っていない ImageOptim の方が最終的に小さくなっているため、ImageOptim はこの結果を見越して、あえて入れ替えなかったのかもしれません。フィルタとパレットの選択によっても結果が変わるため、このあたりのアルゴリズムが当たるかどうかで結果が変わるのかもしれません。

また、全体的に各ツールでの圧縮の特徴ですが、元画像や AzConvPNG ではブロックごとの圧縮後のサイズが大体同じサイズになっているのに対し、ImageOptim と PNGGauntlet は大きくばらついています。 ImageOptim と PNGGauntlet の特徴が似ている理由は推測になりますが後述します。

まとめ

最終的な ZLIB の再圧縮では ImageOptim と PNGGauntlet が優秀な結果となりました。

AzConvPNG がこの二つと比べてあまり結果が出せなかったのは、ZLIB 最適化のアルゴリズムにあるとおもいます。
これは推測ですが、ImageOptim と PNGGauntlet はどちらも PNGOUT というツールを使っているのですが、そこで採用されている ZLIB 部分の最適化を担う Kflate (と一部で呼ばれている最適化アルゴリズム)の影響があると思います。
(AzConvPNG は zlib ver 1.2.5 の Deflate パラメータの中で最もサイズの小さくなるものを選択している模様。 zlib のパラメータはバッファのサイズなどを一律で扱うために、圧縮後のブロックサイズがほぼ平坦になるものだと思われます。)

今回は一つのファイルのみを対象としましたが、対象となるファイルによって優劣が変わる事もあるので、どのツールで最適化するか悩んだ場合は自分で解析してみて比べるのも一つの手段です。

また、PNGGauntlet と ImageOptim どちらを選択したほうが良いかについては、わずかでも小さくしたい場合は両方使用して優れている方を採用、そうでない場合は使用しているOSで選択すれば良いのではないでしょうか。

以上、長くなりましたが PNG の仕様と最適化の挙動について書いてきました。
何かの参考になれば幸いです。