Python + SWFバイナリ解析 その4 : パレット形式の png 埋め込み
Python による パレット形式の png データ解析処理サンプルを作成しました。
・参考
GREE Engineers' Blog : SWFバイナリ編集のススメ第五回 (PNG)
http://labs.gree.jp/blog/2010/12/1902/
png-8 に対する認識の誤り
上記参考サイトの png の形式に関する説明を読んでみたところ、どうも今まで自分は png に対して誤った認識を持っていた事がわかりました。
Photoshop からしか png を書き出した事がなかったせいか、png-8 形式で出力する png は半透明の情報を持たせることはできず、gif と同じような透明化しかできないものだ、と考えてしまっていました。
しかしこの考えは誤りで、本来は png-8 形式でも半透明の情報を持たせることができるとのこと。Photoshop が、半透明の情報を持たせた png-8 の出力に対応していない、という訳でした。
過去、半透明の情報をもった png (png 32bit)の利用は メモリの消費を多く使い容量も大きくなるため利用は避けて欲しい、と答えていた事がありましたが、半透明の情報をもった png-8 形式の png ならば可能になったこともあったのやもしれません。
とはいえ、png-8 に半透明の情報を持たせて出力するための何かいいフリーのツールはあるのかとざっと探してみましたが、なかなか見つからず。Adobe Fireworks が有名のようですが、書き出しのために購入するのも考えものでもあります。
gif 解析処理と共通化
png は gif と同じタグにデータを設定するということから処理内容もほぼ同じとなりました。よって、パレット形式の画像を扱う親クラスを用意し、gif と png(パレット形式)のデータ解析用処理クラスを子クラスとすることに。
true color 形式の png データを解析するクラスを作成にとりかかると、またクラス構造は変化するかもしれません。
gif データを元にした パレットに半透明情報をもった画像データ
透明化 gif の ColorTable 作成処理にて、アルファ値 0x00 の色以外は全て アルファ値を 0xff に設定する必要がある、という仕様は少々無駄があるのではないか、と疑問に思っていましたが、png も同じタグで扱う、という点で納得できました。
png はカラーパレット内各色に対して一つずつアルファ値が設定可能
↓
png を扱うタグの ColorTable 各色にはアルファ値を設定できるようにする必要がある
↓
gif と png は同じタグで扱うため
透明化 gif の ColorTable 各色全てにもアルファ値の設定が必要
逆に言うと、gif データ解析時、作成する ColorTable の各アルファ値を任意の数値に設定すれば、swf 内には「gif データを元にした パレットに半透明情報をもった画像データ」を埋め込む事も可能になります。
swf 上では 元画像が png, gif であれ、同じ形式の独自のデータに変換されている、と言ったところでしょうか。
swf 内画像埋め込み処理の段階で パレットのアルファ値の変更を行うことはないかもしれませんが、豆知識としてメモしておきます。
PIL png tRNS 情報取得
PIL 内のソース調査をまだあまり行っていないという点と、PIL のバージョンによって動作が異なるようなので確実ではありませんが、PIL は PNG のパレット内 各色のアルファ値情報を どこか変数に保存するようにはなっていないとのことです。それを解決するための方法として、PIL 内クラスを拡張して対応する方法が以下のサイトに記述されています。
methaneの日記 : Python Imaging Library (PIL) で、パレット形式のPNGを扱う
http://d.hatena.ne.jp/methane/20100214/1266119110
(※上記サイトの chunk_tRNS メソッド内「PngStream.chunk_PLTE」メソッドの呼び出しは誤りと思われるため「PngStream.chunk_tRNS」メソッド呼び出しに修正して利用させてもらいました。)
ソースコード
以下今回作成したパレット形式の画像を扱うクラスと、それを継承した gif, png 処理用クラスとなります。前回の gif データ差し替えと表示内容は変わらないため、サンプル swf は省略。
・パレット形式の画像を扱う親クラス
Image クラスの open メソッドで生成されるインスタンスを PaletteImageParser.parse メソッドの引数に指定します。その後、PaletteImageParser.getBinary メソッドから swf 内に埋め込むためのデータを取得することが可能です。
# coding: UTF-8 import zlib from struct import * class PaletteImageParser: def __init__(self, image): self._TRANSPARENCY_KEY = "transparency" self._image = image def parse(self): self.__width = self._image.size[0] self.__height = self._image.size[1] palette = self._image.palette.palette self._colorTableSize = len(palette) / 3 - 1 #(1~256) - 1 self.__colorTableBinary = "" if self.checkTransparent(): transparentTable = self._getTransparentTable() self.__colorTableBinary = self.__createColorTableBinaryForTransparency(palette, transparentTable) else: self.__colorTableBinary = self.__createColorTableBinary(palette) self.__colormapPixelData = self.__createColormapPixelData(self.__width, self.__height) def checkTransparent(self): return self._TRANSPARENCY_KEY in self._image.info # override def _getTransparentTable(self): return "" def toString(self): print "--- palette image ---" print "width", self.__width print "height", self.__height print "colorTableSize", self._colorTableSize print "colorTableBinary length", len(self.__colorTableBinary) print "colormapPixelData", len(self.__colormapPixelData) def getBinary(self): widthBinary = pack("<H", self.__width) heightBinary = pack("<H", self.__height) colorTableSizeBinary = pack("B", self._colorTableSize) binary = "\x03" + widthBinary + heightBinary + colorTableSizeBinary binary = binary + zlib.compress(self.__colorTableBinary + self.__colormapPixelData) return binary def __createColorTableBinary(self, palette): colorTableBinary = "" paletteSize = len(palette) n = 0 while n < paletteSize: colorTableBinary = colorTableBinary + palette[n] colorTableBinary = colorTableBinary + palette[n + 1] colorTableBinary = colorTableBinary + palette[n + 2] n = n + 3 return colorTableBinary def __createColorTableBinaryForTransparency(self, palette, transparentTable): colorTableBinary = "" paletteSize = len(palette) tableSize = len(transparentTable) n = 0 t = 0 while n < paletteSize: alpha = transparentTable[t] colorTableBinary = colorTableBinary + self.__revisionPaletteColor(palette[n], alpha) colorTableBinary = colorTableBinary + self.__revisionPaletteColor(palette[n + 1], alpha) colorTableBinary = colorTableBinary + self.__revisionPaletteColor(palette[n + 2], alpha) if t < tableSize: colorTableBinary = colorTableBinary + alpha else: colorTableBinary = colorTableBinary + "\xff" n = n + 3 t = t + 1 return colorTableBinary def __revisionPaletteColor(self, paletteColor, alpha): return pack("B", ord(paletteColor) * ord(alpha) / 255) def __createColormapPixelData(self, width, height): colormapPixelData = "" if (width % 4) == 0: maxWidth = width else: maxWidth = width + (4 - (width % 4)) pix = self._image.load() for y in range(height): xList = "" for x in range(maxWidth): n = 0 if x < width: n = pix[x, y] xList = xList + pack("B", n) colormapPixelData = colormapPixelData + xList return colormapPixelData
・gif 処理用クラス
# coding: UTF-8 import zlib from struct import * from pylib.swf.parser.PaletteImageParser import PaletteImageParser class GifParser(PaletteImageParser): # override def _getTransparentTable(self): transparentTable = "" for n in range(self._colorTableSize + 1): if self._image.info[self._TRANSPARENCY_KEY] == n: transparentTable = transparentTable + "\x00" else: transparentTable = transparentTable + "\xff" return transparentTable
・png(パレット形式) 処理用クラス
# coding: UTF-8 import zlib from struct import * from pylib.swf.parser.PaletteImageParser import PaletteImageParser import pylib.another.pil.PngImagePlugin PNG_TRNS_KEY = "PNG_tRNS" #PNG_PLTE_KEY = "PNG_PLTE" PngStream = pylib.another.pil.PngImagePlugin.PngStream class PngStreamA(PngStream): """ def chunk_PLTE(self, pos, len): s = PngStream.chunk_PLTE(self, pos, len) self.im_info[PNG_PLTE_KEY] = s return s """ def chunk_tRNS(self, pos, len): s = PngStream.chunk_tRNS(self, pos, len) self.im_info[PNG_TRNS_KEY] = s return s pylib.another.pil.PngImagePlugin.PngStream = PngStreamA class PngParserForPalette(PaletteImageParser): # override def _getTransparentTable(self): return self._image.info[PNG_TRNS_KEY]