2017年9月13日水曜日

PNG

 C#で64bit(16bit x 4ch)のPNGを吐き出すライブラリを作っていました。といっても、まだライブラリと言うほどまとまってはいませんが。

 気付いた点とか。

・フィルタについて。
 フィルタは1バイト毎に行われる。1色16bitでも、フィルタは1バイト単位で行われる。なので8bitでも16bitでも同じ関数を使いまわすことができる(1行の幅を、画素数ではなく、バイト数で持たせる)。

追記:2017/09/15
 フィルタの計算量は、None<Sub<Up<Average<Paethという感じか。Noneは計算量ゼロ。SubはUpより少し少ない(ソースコード量とトレードオフ)。
 フィルタは各行毎に設定されるので、forで行を回し、その中でフィルタに応じてforで列を回すことになる。
 Subはfor[y]の中で直接for[x-1]を回せるので、計算量が最小(Noneを除くと)。
 Upはfor[y]の中でy==0をチェックし、trueならNoneとして扱う。それ以外はfor[x]で回すので、ifがあること、1列多く回すこと、等からSubよりも計算量が若干多い。
 Averageはfor[y] for[x]の中で2個のifがあり、また四則演算も多いので遅くなる。ただ、四則演算を除けば、分岐予測が間違わない限りはかなり早いと思う。分岐が必用なのはx==0とy==0のときだけなので、これもソースコード量とトレードオフかな。
 Paethもfor[y] for[x]のループだが、中にifが6個ある。内3個はx==0、y==0、x==y==0なので、予測分岐がミスらなければ早く、ソースコード量とトレードオフになる。一方、残りの3個はピクセル値によって変化するので、それなりの確率で予測が外れることになる(if-else if-else ifなので、1回目で通ればハザードは少なくて済む。が、コレばっかりは画像次第)。
 予測分岐については、CPUとかコンパイラの判断になるだろうから、こればっかりはどうしようもない気がする。

 フィルタリングは、エンコードは減算、デコードは加算で、計算式自体は共通。ただしループの向きが違うので、そこはエンコードとデコードで作り分ける必要がある。
 ループの中でテーブルに対して関数の加算・減算を行う、という流れが楽でいい。ただし計算内容で必要な情報が多いので、関数を呼ぶとスタック操作が発生する。計算式自体はかなり少ないので、インライン展開を期待するか、あるいはべた書きで同じコードを2箇所に書いてしまうか。

・圧縮について。
 PNGの圧縮はDeflateのラッパーであるzlibを使う。が、なぜかDeflateでもpaint.netで読めた(詳細未確認)。


 とりあえず、64bitRGBAのPNGを保存することができるようになった。IDATの圧縮がなんとなくわかってきたので、多倍長独自チャンクも似たような感じで作れそう。

 PNGにはテキストを保存できるチャンクがあるので、中間データとして使う際は、ここにファイルの内容を入れると良さそう。
 基本的にNULL文字終端っぽいので、PNGで使われるテキスト2組の後ろに、バイナリデータを入れられないかと考えている。ソフトウェアで中間データを扱う際に、続くデータチャンクの内容を知らせるためのモノ。
 バイナリデータを入れるのがマズければ、テキスト形式である程度種類を識別させて、続くデータチャンクにバイナリデータを入れても良い。

 以前に考えた時は、IDATが8bit、idBTが16bit、idCTが32bit、のようにしようと思っていたが、コレだとPNGのフォーマットにより8bitデータが1個しか持てない。これはちょっとまずいと思うので、もうちょっと違う形式にする必要がありそう。ま、idATとかでも良いのだけど。
 あと、データチャンクの符号をどうするか。ベクトルデータとかを扱うなら、当然符号なしはマズイわけで、とはいえすべてを符号ありとして扱うと画像データを扱う時にマズイわけで。3文字目の大文字小文字で分けてもいいけど、いちおう予約されてる以上はいじらずに置いたほうが良い気がする。
 例えばidATがu8bit、idBTがs8bit、idCTがu16bit、idDTがs16bit、みたいな感じだとどうだろうか。この形式でも最大65536bitまでの値を持てるから、30年先くらいは安心して使えると思う。


 とりあえず、中間データをPNGに埋め込むライブラリは、PNG画像に中間データを注入する、あるいは中間データを抽出するような動きになると思う。もしくは、画像はBitmapかImageにして扱うとか。画像処理ライブラリまで作り込むのは面倒なので。
 ま、そのあたりは追々。

追記:2017/09/14
 よく考えれば、というか、普通に考えれば、PNGパケットからデータを抜き出す際に、画像を別に取り出す必要性は皆無なんだよな。だってC#のImageなりBitmapで開けば画像は取り出せる。ということは、PNGパケットからデータを取り出す部分と、PNGパケットにデータを埋め込む処理を作ればいい。

0 件のコメント:

コメントを投稿