SWFバイナリ編集のススメ第五回 (PNG)
こんにちは。メディア開発のよやです。
今回は、PNG 画像入れ替えについてお話します。
PNG の情報を格納できるタグ
- DefineBitsLossless, DefineBitsLossless2 が利用出来ます。(*1)
- DefineBitsLossless に透明度情報を加えたのが、DefineBitsLossless2 です。
PNG の特徴 (基礎知識)
- 可逆圧縮のフォーマットです。(JPEGと違って画像の細部が潰れません)
- パレット形式とトゥルーカラー形式(24bit(*2)フルカラー)の両方に対応します。
- 色毎、ピクセル毎に透明度(半透明も可)が指定できます。 (GIFは半透明を扱えません)
パレット形式
- 前回の GIF 編の説明と似ていますが、(GIFと異なり)半透明も扱う為、格納方式が異なります。
- 以下のは輪郭の外が透明で、黄色を少しだけ半透明した例です。
- PLTE chunk にカラーパレット(=カラーテーブル)が入り、IDAT chunk に各ピクセルの色インデックス一覧が格納されるのは GIF と実質的に変わりませんが、透明度情報を tRNS chunk に配列で格納します。
- PLTE (カラーパレット)の先頭から順に対応する不透明度(*3)を tRNS に格納します。透明又は半透明な分だけで良くて省略した分は不透明(0xff)として扱われます。
トゥルーカラー形式
- pixel 毎に RGB の 24bit や RGBA の 32bit を並べる格納方法です。
- RGBA を例にとると以下のようになります。
- こちらは pixel毎に透明度を指定できます。勿論、半透明も表現できます。
DefineBitsLossless タグ
以下に、DefineBitsLossless, DefineBitsLossless2 のタグを種類毎に図で示します。DefineBitsLossless の方には format=4 (15bit RGB 形式)も存在して、RGB 各々を 4 bit で表す 12bit カラーを格納するのに便利ですが、説明を省略します。
DefineBitsLossless (透明色なし)
format=3 (カラーマップ形式= パレット形式)
- このパターンでは 4 の倍数での padding 処理が必要です。(format=5 や DefineBitsLossless2 では padding は不要)
- GIF とほぼ同じですので詳細は前回の記事を参考にして下さい。 > http://labs.gree.jp/blog/2010/10/1263/
format=5 (24bit RGB形式)
- カラーマップ無しで画像上の各 pixel の RGB をそのまま並べて格納します。
- RGB の頭に 00(=X) を付けて、XRGB で並べます。
DefineBitsLossless2 (透明色情報つき)
- DefineBitsLossless とほぼ同じで、色の並びについて、カラーマップの RGB が RGBA に、(24Bit) RGB の XRGB が ARGB に変わります。
format=3 (カラーマップ形式= パレット形式)
- カラーマップに透明度情報も付加します。
- カラーマップ上の並びは RGBA です。format=5 は ARGB なのでご注意下さい。
format=5 (32bit ARGB形式)
- 各pixel 毎に透明度を含めた ARGB 32bit を単純に並べる形式です。
注意点
- RGB(24bit)のカラーマップ形式では横のライン毎に 4byte 境界に合うように padding を入れます。前回のGIF編を参考にして下さい。
- GD で抽出した A(alpha値)は 0(不透明)=>127(透明)で、SWF の方は 0(透明)=>255(不透明)なので、変換する必要があります。
- ARGB, RGBA 表現は、A に応じて以下の式で RGB を補正します。
1 2 3 |
R' = R * A / 255 G' = G * A / 255 B' = B * A / 255 |
PNG 差し替え処理
- 素材Flash (grayorz.swf)
- 尚、これから説明するコードは以下のファイルから切り出したものです。
- SWF 編集に使う IO_SWF_Editor は openpear で公開しています。pear の入っている環境では、以下のコマンドでインストールできます。
1 2 3 |
pear channel-discover openpear.org pear install openpear/IO_Bit pear install openpear/IO_SWF |
パレット形式の PNG
GD を使うと以下のように吸い出せます。
- パレットを吸い出す
1 2 3 4 5 6 7 8 9 10 |
$im = imagecreatefrompng($pngfile); $colortable = ''; $colortable_size = imagecolorstotal($im); for ($i = 0 ; $i < $colortable_size ; $i++) { $rgb = imagecolorsforindex($im, $i); $colortable .= chr($rgb['red']); $colortable .= chr($rgb['green']); $colortable .= chr($rgb['blue']); } |
- パレットを指すピクセルデータを吸い出す (横一列毎に 4 の倍数になるように)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$pixeldata = ''; $i = 0; $width = imagesx($im); $height = imagesy($im); for ($y = 0 ; $y < $height ; $y++) { for ($x = 0 ; $x < $width ; $x++) { $pixeldata .= chr(imagecolorat($im, $x, $y)); $i++; } while (($i % 4) != 0) { // 4 の倍数に合わせる $pixeldata .= chr(0); $i++; } } |
- 吸い出した画像データを DefineBitsLossless の形式に変換する
1 2 3 |
$format = 3; $content = pack('v', $image_id).chr($format).pack('v', $width).pack('v', $height); $content .= chr($colortable_size - 1).gzcompress($colortable.$pixeldata); |
- SWF 内の画像を入れ替える
1 2 3 4 5 6 7 8 9 10 |
$swf = new IO_SWF_Editor(); $swf->parse($swfdata); $swf->setCharacterId(); $tag_code = array(6, 21, 35, 20, 36); // Bitmap Tags $tag = array('Code' => 20, 'Content' => $content); $ret = $swf->replaceTagByCharacterId($tag_code, $image_id, $tag); echo $swf->build(); |
- PNG画像 (GREElabs-palette.png)
- コマンド実行
1 |
% php swfreplacepng.php grayorz.swf 1 GREElabs-palette.png > GREElabs-palette.swf |
- 結果(GREElabs-palette.swf)
32Bit RGBA形式のPNG画像(透明度付き)
- 32Bit RGBA を吸い出して ARGB で並べる (RGB は A に応じて補正)
- GD library の alpha 値は特殊なので 127~0 を 0~254 に変換。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$pixeldata = ''; $i = 0; $width = imagesx($im); $height = imagesy($im); for ($y = 0 ; $y < $height ; $y++) { for ($x = 0 ; $x < $width ; $x++) { $i = imagecolorat($im, $x, $y); $rgba = imagecolorsforindex($im, $i); $alpha = $rgba['alpha']; $alpha = 2 * (127 - $alpha); $pixeldata .= chr($alpha); $pixeldata .= chr($rgba['red'] * $alpha / 255); $pixeldata .= chr($rgba['green']* $alpha / 255); $pixeldata .= chr($rgba['blue'] * $alpha / 255); } } |
- 吸い出した画像データを DefineBitsLossless2 の形式に変換する
1 2 3 |
$format = 5; $content = pack('v', $image_id).chr($format).pack('v', $width).pack('v', $height); $content .= gzcompress($pixeldata); |
- SWF 内の画像を入れ替える
1 2 3 4 5 6 7 8 9 10 |
$swf = new IO_SWF_Editor(); $swf->parse($swfdata); $swf->setCharacterId(); $tag_code = array(6, 21, 35, 20, 36); // Bitmap Tags $tag = array('Code' => 36, 'Content' => $content); $ret = $swf->replaceTagByCharacterId($tag_code, $image_id, $tag); echo $swf->build(); |
- PNG画像 (GREElabs-rgba.png)
- コマンド実行
1 |
% php swfreplacepng.php grayorz.swf 1 GREElabs-rgba.png > GREElabs-rgba.swf |
- 結果(GREElabs-rgba.swf)
簡単ですね!
備考
- SWF 仕様書には、DefineLossless2 の format = 3 (カラーマップ形式) に対する A 値による RGB 補正が書かれていませんが、実際には format = 5 同様に補正が必要なようです。(経験談)
- ColormapNum は色数を -1 した値が入っているので注意。1byte で 1色~256色を表現する為だと思われます。(そのままだと 255色で打ち止めなので)
- 画像データを圧縮する際に使用する gzcompress は第二引数で圧縮レベルを指定できます。指定しない場合は Zlib ライブラリのデフォルト値 6 が適用され、CS2,3,4 等が出力する SWF の Zlib圧縮も 6 相当です。CPU とファイルサイズのトレードオフで、この値を調整するのも手です。
参考URL
- http://www.adobe.com/devnet/swf.html
- http://www.m2osw.com/swf_tag_definebitslossless
- http://pwiki.awm.jp/~yoya/?Flash/SWF/format/Lossless
- http://www.w3.org/TR/PNG/
- http://www.geocities.co.jp/Playtown-Knight/6845/sd_doc/format_png.html
- http://homepage2.nifty.com/sophia0/png.html
- http://bb.watch.impress.co.jp/cda/bbword/15612.html
次回未定
- DefineEdit(テキストボックス), DefineShape(ベクター画像), DefineSprite(シンボル)、DoAction(AcrionScript2.0 ByteCode)等、ご要望があれば続きます。
*1: SWF 8 以降では DefineBitsJPEG 系のタグに PNG データを格納できるようですが、それには触れません
*2: PNG は R,G,B 各々を 16bit で表現する 48bit フルカラーも表現出来ますが、その説明は省きます
*3: alpha値や opaque 等と呼ばれる事が多いです