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 を補正します。
R' = R * A / 255
G' = G * A / 255
B' = B * A / 255

PNG 差し替え処理

  • 素材Flash (grayorz.swf)

  • SWF 編集に使う IO_SWF_Editor は openpear で公開しています。pear の入っている環境では、以下のコマンドでインストールできます。
pear channel-discover openpear.org
pear install openpear/IO_Bit
pear install openpear/IO_SWF

パレット形式の PNG

GD を使うと以下のように吸い出せます。

  • パレットを吸い出す
$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 の倍数になるように)
$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 の形式に変換する
$format = 3;
$content = pack('v', $image_id).chr($format).pack('v', $width).pack('v', $height);
$content .= chr($colortable_size - 1).gzcompress($colortable.$pixeldata);
  • SWF 内の画像を入れ替える
$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)

  • コマンド実行
% 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 に変換。
$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 の形式に変換する
$format = 5;
$content = pack('v', $image_id).chr($format).pack('v', $width).pack('v', $height);
$content .= gzcompress($pixeldata);
  • SWF 内の画像を入れ替える
$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)

  • コマンド実行
%  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

次回未定

  • DefineEdit(テキストボックス), DefineShape(ベクター画像), DefineSprite(シンボル)、DoAction(AcrionScript2.0 ByteCode)等、ご要望があれば続きます。

*1: SWF 8 以降では DefineBitsJPEG 系のタグに PNG データを格納できるようですが、それには触れません

*2: PNG は R,G,B 各々を 16bit で表現する 48bit フルカラーも表現出来ますが、その説明は省きます

*3: alpha値や opaque 等と呼ばれる事が多いです

Author: よや