PNGバイナリをデコードするPNGDecoder

タイトルの通り、PNGのデータをデコードしてBitmapDataとパレットなどの情報を取得するクラスを作りました。ソースはひとまず

に置いてあります。MITライセンス。

PNGの仕様を全部実装してあるわけではないので、バージョン 0.1としてあります。基本的な部分しか実装していないので、画像によっては正しく読み込めなかったり表示できなかったりするかもしれません。手持ちのPNGファイルは全部普通に読み込めたので、たぶん実用上はそれほど困らない範囲だと思います。ちなみにグレイスケール対応してなかったりします。だって使ったことないし。

PNGPaletteDecoderのほうは、画像本体のデコードはしませんが、パレット形式だけ読み込んでくれます。実用性を考えると、PNGDecoderよりもまだ需要がありそうです。ちなみに、PNGDecoderはPNGPaletteDecoderを継承させてます。

使い方・実用例

大体こんな感じ

// PNGのByteArrayをBitmapDataに変換する
private function getPNGBitmapData(bytes:ByteArray):BitmapData
{
  var decoder:PNGDecoder = new PNGDecoder();
  
  try{
    decoder.decode(bytes); // デコードする
  }catch(e:Error){
    trace('invalid PNG ByteArray');
  }

  // パレット形式のとき、1番最初のパレットに格納されている色をtraceする
  if(decoder.colorType = 3)
    trace(decoder.paletteTable[0] as uint)
  
  return decoder.image; // デコードされたBitmapDataを返す
}

まだテスト段階ですが、パレット形式のPNGを読み込んで特徴領域抽出と近似領域生成するやつPNG読み込みに使ってみました。表示にはLoaderではなくPNGDecoderを使っています。基本的にドット絵用なので使い道は限られていますが、そのうちこれを活用してなにかを作る予定です。速度重視していてあまり時間のかかる判定処理は組み込んでいないのですが、64〜256程度のサイズだとけっこううまくいきます。

作った背景

普通、画像のByteArrayからDisplayObjectもしくはBitmapDataに変換したいときは、Loader.loadBytes()を使えばよいです。むしろ中途半端な機能しかないPNGDecoderをわざわざ使って変換するよりよい方法です。なので、PNGPaletteDecoderはともかく、PNGDecoderはどちらかというと画像処理の勉強のために作った感じです。

PNGPaletteDecoderは前に作ったパレット変換用に作ったものを綺麗にしただけです。PNGDecoderを作ったきっかけは、PNGEncoderというPNGエンコードをするクラスのソースをみたら、IDATチャンク(PNGの画像データで、deflate/inflate圧縮がかかっている)の圧縮処理部分がByteArray.compress()で実装されていて、通常は面倒な圧縮・解凍処理もAS3なら簡単にできるということがわかったからです。解凍処理がByteArray.uncompress()だけで出来るわけで、それならDecoderも簡単に作れるんじゃね?と思ったのが運の尽きでした。

ちなみに、PNG以外の画像形式のデコーダーはいくつかあって、GIFDecoder(Loaderと違ってGifアニメも一応対応)やBMPDecoderなんてのがあります。

一般的なPNG画像を表示するのための最低限の実装

PNGのバイナリをBitmapData(要は、RGB+透明度の値が入った表)にすることがデコーダーの目的です。PNGの仕様書を読むとわかるとおり、色々と補助的な情報が入っているのですが、全部実装するのは面倒です。今回は一般的に使われているであろう範囲でのPNG画像を読み込んで表示できればよいとします。

細かいことは参考文献を読んでもらえばいいのですが、PNGはチャンクと言うデータブロックで構成されています。最低限読み込んだほうがいいチャンクは以下の通りです。

  • IHDR(画像サイズやタイプなどの基本情報)
  • PLTE(パレット表)
  • IDAT(画像データ本体)
  • bKGD(背景色)
  • tRNS(透過色)

IDAT以外は参考文献を読めばすぐに解釈できると思うのでここでは触れません。バイナリを数値に変換するだけです。

IDATのほうもByteArray.uncompress()でデータ解凍して、ビットの深さと透過・背景色に注意して色(パレット形式のときはパレット番号)に変換すれば基本的にはOKです。ただ、圧縮率を上げるために、画像の色データに対して、行単位でフィルターがかかっていることがあります。フィルター逆変換処理をしないと、フィルターのかかっている行の色がおかしいことになるので、フィルターの章も読んでちゃんと実装しておきましょう。

あと、今回のPNGDecoderでは実装していませんが、余裕があればcHRM(基本色度)、gAMA(ガンマ値)、sRGB(色空間)あたりもたまに埋め込まれてるのでちゃんと解釈してやるとよりベターだと思います。

今後の発展とか

Flex3から標準でPNGEncoderがあるのですが、実はαチャンネル付きPNGしか出力してくれません。なので、ファイルサイズは無駄にでかくなります。また、ドット絵などパレット形式で保存したいときには使えません。

ビット深度やアルファチャンネル・背景色の有無、パレット形式での保存など、もう少し細かい設定ができるPNGEncoderがあると、もしかすると便利なことがあるかもしれません。基本的にはPNGDecoderと逆のことをすればよいので、誰か作ってもいいと思います。圧縮性能を上げるためにはいろいろ工夫も必要ですが。