先日TIFFファイルの圧縮についての記事を書いた。これによって大量にTIFFファイルの圧縮を行い、テラバイトオーダーの領域を確保することができた。消費コスト*1を考慮しても、十分に経済合理性のある作業だった。
ところで、メジャーな画像フォーマットのうち、PNG(Portable Network Graphics)も可逆圧縮をサポートしている。私の認識では、その名の通りネットに流通させるのに適したファイルフォーマットで、Web画面のボタンや社名のロゴなど比較的小さい画像に使うことが多い。さらに、GIFと同様に透明も扱えるのが特徴。この程度の理解で居たので、高解像度画像の記録に適したフォーマットではなく、個人的にはマスクなどのアルファチャネルが必要な用途以外では制作環境ではほとんど使っていなかった。
先日のストレージ逼迫を契機として、圧縮済TIFFと比較して、PNGの圧縮率はどうなのだろうかという疑問が生じたので調べてみた。
PNGの圧縮の仕組み
A guide to PNG optimizationによると、
The PNG compression works in a pipeline manner.
In the first stage, the image pixels are passed through a lossless arithmetic transformation named delta filtering, or simply filtering, and sent further as a (filtered) byte sequence. Filtering does not compress or otherwise reduce the size of the data, but it makes the data more compressible.
In the second stage, the filtered byte sequence is passed through the Ziv-Lempel algorithm (LZ77), producing LZ77 codes that are further compressed by the Huffman algorithm in the third and final stage. The combination of the last two stages is referred to as the Deflate compression, a widely-used, patent-free algorithm for universal, lossless data compression. The maximum size of the LZ77 sliding window in Deflate is 32768 bytes, and the LZ77 matches can be between 3 and 258 bytes long.
A complete description of the PNG compression is beyond the scope of this guide. The PNG Specification describes the format completely, and provides a complete list of references to the underlying technologies.
とある。適当に意訳してみると、
PNGの圧縮処理は、パイプラインみたく段階がある。 始めにデルタフィルタリング(或いは単にフィルタリング)と呼ばれるロスレス変換演算を行う。これ自体はデータの圧縮や減量ではなく、圧縮しやすくするための処理である。 次にフィルタリングされたデータをLZ77アルゴリズムで処理する。 最後にLZ77で符号化されたデータをハフマンアルゴリズムで圧縮する。 完全な詳細はこのガイドの範囲外やから、知りたかったらPNG仕様書読むといい。
ざっくりと、フィルタリングで整形後、Deflate(LZ77→Huffman)で圧縮してると理解しておけばよいだろうか。
フィルタリングって何してるんだという疑問が生じるが、同じソースに以下の説明がある。
The role of filtering can be illustrated in the following example. Assume the sequence 2, 3, 4, 5, 6, 7, 8, 9. Although it has much redundancy, the sequence is not compressible by a Ziv-Lempel compressor, nor by a Huffman compressor. However, if one makes a simple and reversible transformation, replacing each value with the numerical difference between it and the value to its left, the sequence becomes 2, 1, 1, 1, 1, 1, 1, 1, which is highly compressible.
The PNG format employs five types of filters: None, Left, Up, Average, and Paeth. The first filter leaves the original data intact, and the other four are subtracting from each pixel a value that involves the neighbor pixels from the left, up, and/or the upper left.
A certain filter is assigned to each row, and is applied to all pixels from that row. Therefore, an image can be delta-filtered in a huge number of possible configurations (5 ^ height), and each configuration leads to a different compressed output. Two different filter configurations may make a difference in the compressed file size by a couple of factors, so a careful choice of filters is of paramount importance.
It is possible to apply a single filter to all rows, or to apply different filters to different rows. In the former case, the filtering process is fixed; in the latter, it is adaptive.
こちらも適当に意訳してみると、
フィルタリングの役割は以下の例で説明できる。 2,3,4,5,6,7,8,9というデータがあるとして、このままではLZ77やハフマンで圧縮できない。 が、左の値からの差分で表わせば、2,1,1,1,1,1,1,1となり、冗長性があるから超絶圧縮できる。しかも簡単に元のデータに逆変換できる。 PNGではNone,Left,Up,Average,Paethの5種類のフィルターが定義されてる。 Noneは何もしない、それ以外は近傍の画素からの差分を抽出する。近傍ってどこやねんというのを、左(Left)、上(Up)、その両方の平均(Average)、左・上・左上からの計算値(Paeth)で指定できる。 いくつかのフィルタは行毎に割り当てできて、その行以降の全ピクセルに対して適用可能。 故に、5^行数(フィルタの種類^画像の高さ)という膨大な数のフィルタパターンが取り得るけど、圧縮しやすい結果になるようにフィルタを選ぶことが大事やね。 単一のフィルタを全行に適用することも可能だし、行ごとに違うフィルタを適用することも可能で、前者のフィルタリング処理はfixed、後者はadaptiveやで。
なるほど。ベタ塗りはもちろん、差分を得るフィルタリングを行えば規則的なグラデーション的な画像も圧縮しやすいということだろう。PNGで高い圧縮率を実現するには、なるべく冗長的なデータを出力するフィルタの選定が肝だろう。
(フィルタを適用しなければDeflateによる圧縮だけなので、ZIP圧縮TIFFと似たような圧縮率になるのだろうか。)
ImageMagickによるPNGの圧縮パラメータ指定
パラメータを指定してPNGファイルを書き出すためには何らかのツールが必要であるが、ここではImageMagickを使う。
ImageMagickのリファレンス(Common Formats -- IM v6 Examples)には、以下の記述がある。
PNG compression
When used with PNG output, quality is regarded as two decimal figures. The first digit (tens) is the zlib compression level, 1-9. However if a setting of '0' is used you will get Huffman compression rather than 'zlib' compression, which is often better! Weird but true!
The second digit is the PNG data encoding filtering (before it is comressed) type: 0 is none, 1 is "sub", 2 is "up", 3 is "average", 4 is "Paeth", and 5 is "adaptive". So for images with solid sequences of color a "none" filter (-quality 00) is typically better. For images of natural landscapes an "adaptive" filtering (-quality 05) is generally better.
PNG圧縮 PNGで出力するときはqualityパラメータは2桁整数で指定する。 最初の桁(10の位): zlibの圧縮レベル(1~9)を指定する。0ならzlib圧縮じゃなくハフマン圧縮を指定する。妙な話だがハフマンだけの方が圧縮率がいいことが良くあるんだ! 次の桁(1の位): フィルタを指定(0:none, 1:sub, 2:up, 3:average, 4:Paeth, 5: adaptive)。 典型的にはカラーの塗り絵みたいな画像は、noneがベター。(-quality 00) 一般的には自然風景みたいな画像にはadaptiveがベター。(-quality 05)
2桁数字と見せかけて、有意コード混じりですって。そして、謎のハフマン推しである。
実測
ここではSigma Photo Proで出力したTIFFファイルをInputとして実際に検証してみる。
Inputデータ仕様
- 無圧縮TIFF
- マルチページ
- 1ページ目:4704x3136
- 2ページ目:160x106(サムネイル相当。※必要無いので以降は1ページ目の画像についての処理結果である)
- 微妙にTIFFの仕様を逸脱(本論とは特に関係ない。シグマさんには優先度最低で構わないので修正してほしいので書いた。)
- "Invalid TIFF directory; tags are not sorted in ascending order."とImageMagickに警告される。
測定結果
TIFFの圧縮アルゴリズム(LZW/ZIP)、PNGの圧縮パラメータ(フィルタ及びzlibの圧縮レベル)をそれぞれ指定した場合の、ファイルサイズは以下の通りとなった。
Pattern | Size | CompressionRatio |
---|---|---|
Original(page#1) | 88,539,024 | - |
TIFF-LZW | 89,839,688 | 101.5% |
TIFF-ZIP | 66,946,402 | 75.6% |
PNG-Huffman_Filter:None | 74,514,525 | 84.2% |
PNG-Huffman_Filter:Sub | 74,876,550 | 84.6% |
PNG-Huffman_Filter:Up | 74,946,178 | 84.6% |
PNG-Huffman_Filter:Average | 74,718,667 | 84.4% |
PNG-Huffman_Filter:Paeth | 74,889,119 | 84.6% |
PNG-Huffman_Filter:Adaptive | 71,166,039 | 80.4% |
PNG-zlibCompressionLevel:1_Filter:None | 50,123,421 | 56.6% |
PNG-zlibCompressionLevel:1_Filter:Sub | 70,170,471 | 79.3% |
PNG-zlibCompressionLevel:1_Filter:Up | 70,233,842 | 79.3% |
PNG-zlibCompressionLevel:1_Filter:Average | 73,381,819 | 82.9% |
PNG-zlibCompressionLevel:1_Filter:Paeth | 73,695,459 | 83.2% |
PNG-zlibCompressionLevel:1_Filter:Adaptive | 50,123,421 | 56.6% |
PNG-zlibCompressionLevel:2_Filter:None | 48,121,881 | 54.4% |
PNG-zlibCompressionLevel:2_Filter:Sub | 69,617,041 | 78.6% |
PNG-zlibCompressionLevel:2_Filter:Up | 69,694,262 | 78.7% |
PNG-zlibCompressionLevel:2_Filter:Average | 73,186,607 | 82.7% |
PNG-zlibCompressionLevel:2_Filter:Paeth | 73,514,599 | 83.0% |
PNG-zlibCompressionLevel:2_Filter:Adaptive | 48,121,881 | 54.4% |
PNG-zlibCompressionLevel:3_Filter:None | 45,762,448 | 51.7% |
PNG-zlibCompressionLevel:3_Filter:Sub | 69,212,186 | 78.2% |
PNG-zlibCompressionLevel:3_Filter:Up | 69,302,919 | 78.3% |
PNG-zlibCompressionLevel:3_Filter:Average | 73,117,290 | 82.6% |
PNG-zlibCompressionLevel:3_Filter:Paeth | 73,435,376 | 82.9% |
PNG-zlibCompressionLevel:3_Filter:Adaptive | 45,762,448 | 51.7% |
PNG-zlibCompressionLevel:4_Filter:None | 46,391,838 | 52.4% |
PNG-zlibCompressionLevel:4_Filter:Sub | 74,083,886 | 83.7% |
PNG-zlibCompressionLevel:4_Filter:Up | 74,233,869 | 83.8% |
PNG-zlibCompressionLevel:4_Filter:Average | 74,659,692 | 84.3% |
PNG-zlibCompressionLevel:4_Filter:Paeth | 74,823,990 | 84.5% |
PNG-zlibCompressionLevel:4_Filter:Adaptive | 46,391,838 | 52.4% |
PNG-zlibCompressionLevel:5_Filter:None | 44,999,907 | 50.8% |
PNG-zlibCompressionLevel:5_Filter:Sub | 73,951,305 | 83.5% |
PNG-zlibCompressionLevel:5_Filter:Up | 74,107,287 | 83.7% |
PNG-zlibCompressionLevel:5_Filter:Average | 74,655,451 | 84.3% |
PNG-zlibCompressionLevel:5_Filter:Paeth | 74,817,224 | 84.5% |
PNG-zlibCompressionLevel:5_Filter:Adaptive | 74,531,163 | 84.2% |
PNG-zlibCompressionLevel:6_Filter:None | 44,036,396 | 49.7% |
PNG-zlibCompressionLevel:6_Filter:Sub | 73,860,189 | 83.4% |
PNG-zlibCompressionLevel:6_Filter:Up | 74,016,429 | 83.6% |
PNG-zlibCompressionLevel:6_Filter:Average | 74,651,823 | 84.3% |
PNG-zlibCompressionLevel:6_Filter:Paeth | 74,810,352 | 84.5% |
PNG-zlibCompressionLevel:6_Filter:Adaptive | 74,518,888 | 84.2% |
PNG-zlibCompressionLevel:7_Filter:None | 44,014,603 | 49.7% |
PNG-zlibCompressionLevel:7_Filter:Sub | 73,840,699 | 83.4% |
PNG-zlibCompressionLevel:7_Filter:Up | 73,996,650 | 83.6% |
PNG-zlibCompressionLevel:7_Filter:Average | 74,651,232 | 84.3% |
PNG-zlibCompressionLevel:7_Filter:Paeth | 74,808,491 | 84.5% |
PNG-zlibCompressionLevel:7_Filter:Adaptive | 74,514,525 | 84.2% |
PNG-zlibCompressionLevel:8_Filter:None | 44,007,922 | 49.7% |
PNG-zlibCompressionLevel:8_Filter:Sub | 73,816,440 | 83.4% |
PNG-zlibCompressionLevel:8_Filter:Up | 73,973,877 | 83.5% |
PNG-zlibCompressionLevel:8_Filter:Average | 74,650,272 | 84.3% |
PNG-zlibCompressionLevel:8_Filter:Paeth | 74,807,293 | 84.5% |
PNG-zlibCompressionLevel:8_Filter:Adaptive | 74,507,658 | 84.2% |
PNG-zlibCompressionLevel:9_Filter:None | 44,007,896 | 49.7% |
PNG-zlibCompressionLevel:9_Filter:Sub | 73,816,176 | 83.4% |
PNG-zlibCompressionLevel:9_Filter:Up | 73,973,585 | 83.5% |
PNG-zlibCompressionLevel:9_Filter:Average | 74,650,265 | 84.3% |
PNG-zlibCompressionLevel:9_Filter:Paeth | 74,807,268 | 84.5% |
PNG-zlibCompressionLevel:9_Filter:Adaptive | 74,507,678 | 84.2% |
※所要時間は測定していないが、zlibの圧縮レベルが高いほど時間がかかった感覚がある。なお、激烈に遅いTIFFの圧縮にZIPを指定した場合よりはいずれも速かったと思う。
考察
- フィルタ無しが最も高圧縮率であることが判る。
- zlibの圧縮レベルが同一でもフィルタの選択次第で1.5倍程度も圧縮率が異なる。
- フィルタ無しでzlibを使用して圧縮したPNGファイルは、ZIP圧縮を指定したTIFFファイルより、最大25%程度も高圧縮率。
- zlibの圧縮レベルは高いほど圧縮率が高まるが、リニアではない。今回のInputではフィルタ無し圧縮レベル6~9で圧縮率0.1%の差しかない。
- 所要時間を加味すれば無闇に高い圧縮レベルを指定する合理的理由は無い。
- 圧縮レベルよりフィルタの選択がより重要。
- 高速な低圧縮レベルで全フィルタパターンを試行し、最も効果的なフィルタパターンを特定した後、圧縮レベルを上げる運用が実用的と考えられる。
まとめ
- ZIP圧縮を指定したTIFFより適切なオプションで圧縮されたPNGの方が高圧縮率
- この結果だけを以てTIFFを捨てPNGに乗り換える決断をするのは拙速。
- 異なるInputデータの場合は全く異なる結果となる可能性があるが、ImageMagickのハフマン推しは何だったのか。
- 今回のInputは空も地面も黒系の類似色の画素が大半を占めるので、そもそも検証用には不適切ではないか説。
- 他の画像パターンでも追試してみる必要がある。