pixiv insideは移転しました! ≫ https://inside.pixiv.blog/

php-ext-zopfliでPNG画像を再圧縮

MacBook Proを買ったのはいいもののそろそろLinuxに戻りたくなってきたbokkoです。

今回は先月末にGoogleから発表・公開されたばかりのzopfliの紹介と、 そのPHP拡張であるphp-ext-zopfliPNG画像を再圧縮する関数を追加した時の話をします。

zopfli

zopfliはzlibと比べて3〜8%ほど圧縮率が高く、 それでいてgzipやzlib等で広く使われているdeflateアルゴリズムと互換性のある圧縮アルゴリズムです。Google CodeでCによる実装が公開されています。

繰り返しになりますが、単にdeflateアルゴリズムよりも圧縮率が高いだけでなく、 deflateアルゴリズムと互換性がある、つまりzopfli圧縮したものは従来のgzipやzlibで展開できるというのがミソです。

zopfliによるPNG画像の再圧縮

PNG画像はデータ部分(IDATチャンク)がdeflateアルゴリズム圧縮されているので、 一旦IDATチャンクを展開してzopfliで再圧縮をかけることでさらに容量を小さくすることができます。

ただ、zopfli自体はデータ圧縮の機能を提供しているだけなので、

  1. PNG画像からIDATチャンクを取り出す
  2. IDATチャンクを展開
  3. IDATチャンクを再圧縮した新しいPNG画像を生成する

といった一連の処理は自分で書く必要があります。そのため、zopfliを使ってPNG画像の再圧縮を行うにはPNGの内部構造についてある程度知識が必要となります。(そのあたりの面倒を見てくれるライブラリがあったらぜひ教えてほしいです!・・・libpng?)

非常にざっくりですが、PNG画像はまず先頭に8バイトのシグネチャ(16進数で 89 50 4e 47 0d 0a 1a 0a)があり、それ以降は複数のチャンクで構成されています。 チャンクの種類は非常にたくさんありますが、上記の操作に必要なチャンクは以下の3つです。

チャンクの種類バイト数備考
IHDRチャンク 25バイト ヘッダチャンク
IDATチャンク 可変長 データチャンク(複数存在してもよい)
IENDチャンク 12バイト 終端チャンク

各チャンクの内容は以下のようになります。

IHDR

内容バイト数備考
チャンクサイズ 4バイト 13
チャンクの種類 4バイト 16進数で49 48 44 52(ASCIIコードで”IHDR”)
4バイト  
高さ 4バイト  
ビット深度 1バイト  
カラータイプ 1バイト  
圧縮メソッド 1バイト  
フィルターメソッド 1バイト  
インタレースメソッド 1バイト  
CRC 4バイト チャンクのサイズと種類を元に計算

IDAT

内容バイト数備考
チャンクサイズ 4バイト データのサイズ
チャンクの種類 4バイト 16進数で49 44 41 54(ASCIIコードで”IDAT”)
データ 可変長  
CRC 4バイト チャンクの種類とデータを元に計算

IEND

内容バイト数備考
チャンクサイズ 4バイト 0
チャンクの種類 4バイト 16進数で49 45 4e 44(ASCIIコードで”IEND”)
CRC 4バイト チャンクの種類とデータを元に計算

PNGのさらなる詳細についてはこちらをご覧ください。

zopfliによるPNG画像の再圧縮PHP

元々zopfliPNG画像を小さくすることに関心があったのですが、単にCでzopfliを使ってPNG画像を再圧縮するプログラムを書くよりは 既にあるLLのバインディングに組み込む方が実用的かなぁと思っていたところ、ちょうどphp-ext-zopfliというPHPからzopfliを使うための拡張モジュールがあったので、これにzopfli_png_recompressという関数を追加する形で実装しました。

上述の仕様を元に実装して最初にコミットしたのがこちらになります。(せっかくなので本家にpull requestを送ったところ、無事マージされました)

使い方はこんな感じです。

1
2
3
4
5
<?php
// PNG画像を読み込む
$png_image = file_get_contents("/home/bokko/image.png");
// PNG画像をzopfliで再圧縮
$png_image_recompressed = zopfli_png_recompress($png_image);

zopfliの使いどころ

zopfliはdeflateアルゴリズムよりも圧縮率が高いかわりに圧縮にかかる時間が非常に長くなるという欠点があります。 あまり詳細にベンチマークを取ったわけではありませんが、実際に作成したzopfli_png_recompressを使ってみたところ、 ピクセル数の小さい画像(400Kpx)の再圧縮でも数秒、大きい画像(約30Mpx)の再圧縮だと数分以上かかってしまったのでGoogleの発表にもある通り、静的コンテンツ配信のような事前に圧縮した形で配布するような用途に向いていると言えるでしょう。

まとめ

zlibやgzipで使われている圧縮アルゴリズムであるdeflateアルゴリズムと互換性があり、より圧縮率が高いzopfliについて紹介しました。

また、zopfliPNG画像を再圧縮するプログラムをPHPの拡張モジュールであるphp-ext-zopfliにzopfli_png_recompressという関数を追加する形で作成しました。

参考URL