先日PNGエンコーダを自作してみたのですが、なんでかC#もとい.NET FrameworkにはPNGフォーマットに必須のzlib圧縮(RFC1950)が見当たりませんでした。
無いものは作るべし、ということで自作してみました。

正直なところサードパーティ製のライブラリ使えば終わりですが、PNG圧縮のためだけに逆に大げさすぎるのではと思ったので、自力でなんとかしたいと思います。

必要なもの

データ圧縮のコアとなるDeflate(RFC1951)については幸いにも.NET Frameworkに盛り込まれているので、それを使います。
zlib形式ではDeflate圧縮したデータに独自のヘッダーとフッターが必要になります。

ヘッダーは内容を説明する単純なものですが、フッターはAdler-32という32bitのチェックサムになります。
なおプリセット辞書を使う場合はヘッダーと圧縮データの間に辞書情報が挟まるようです。(まず使わないので無視)

ということでAdler-32アルゴリズムを使いたいのですが、これもまた.NET Frameworkにはありませんので自作することにします。

ヘッダーの組み立て

ヘッダーはCM、CINFOを含むCMF(1バイト)とFLEVEL、FDICT、FCHECKを含むFLG(1バイト)の合計2バイトから成ります。

以下Wikipediaからまんま引用。

1バイト目
上位4ビットは圧縮情報であり LZ77 のウィンドウサイズ。7なら32KBのウィンドウサイズ。
下位4ビットが圧縮方式。通常は数値の8。
2バイト目は
上位2ビットは圧縮レベル。デフォルトは2。
6ビット目はプリセット辞書があるかどうか。
下位5ビットがヘッダー2バイト分のチェックビット。

RFCを読む限り、PNGやgzipでは圧縮方式(CM)は8、ウィンドウサイズ(CINFO)は32KBが使われるそうで、最初の1バイトは0x78で良さそうです。
.NETのDeflateはその辺は調整効かないみたいですがそれで大丈夫そうです。

次にFLG。FLEVELはCM=8の場合は以下の定義になっています。

0 – compressor used fastest algorithm
1 – compressor used fast algorithm
2 – compressor used default algorithm
3 – compressor used maximum compression, slowest algorithm

しかし.NETのCompressionLevel列挙体にはFastest,NoCompression,Optimalという大雑把なものしかありません。。。
というか、FLEVELは展開時には必要なく、ぶっちゃけどうでもいい値らしいです。とりあえずデフォルトの2にしておきましょう。

FDICTは単一ビットのフラグ値ですが、辞書はまず必要ありませんので0にします。

最後のFCHECKはヘッダー内容が可変の場合は都度生成する必要がありますが、今回のケースでは不変な為、固定値で対応します。
ヘッダー2バイトをビッグエンディアンの16ビットの符号なし整数とみなし、それが31の倍数となるように調整します。
つまり以下の式が成り立つようにします。

(CMF * 256 + FLG) mod 31 == 0

これからFCHECKを計算すると。

FCHECK = 0x1F – 0x7890 mod 0x1F = 0x0C

ということで最終的なヘッダーは『78 9C』となります。

Adler-32の実装

アルゴリズムの説明はRFC1950にプログラムコード付きで載っているので割愛します。
細かい説明もWikipediaを参照すれば良いと思います。
今回はHashAlgorithmを継承して実装しました。

zCompress関数作成

PHPだとなぜかRFC1950相当の圧縮関数がgzcompressとかいう名前になっているんですが、それを真似するのはなんかぱっとしないので(gzipとは別だし!)、zCompressと名付けることにしました。
ということでDeflateStreamクラスを使ってさくっと実装。

以上です!
ご意見ありましたらどうぞ。
クラス2個も作って、こっちの方が大げさじゃねーか!って突っ込みはなしでどうぞ(:3 」∠)

それにしてもDeflateStreamのコンストラクタにCompressionLevel指定しなかったらどうなるのか謎…