SWF フォーマットの変遷からみる Flash
ふと思い立って,プログラマーから見た Flash について書くことにしました。SWF and AMF Technology Center | Adobe Developer Connection からダウンロードできる SWF file format specification をもとに*1。
といっても,ActionScript 1.0 / 2.0 / 3.0 の違いについて説明するものではありませんよ*2。
SWF ファイルフォーマット
ヘッダのあとに,複数の「タグ」と呼ばれる構造体が続く構造になっています。「タグ」という言葉は紛らわしいので以降はチャンク(chunk)と呼ぶことにします。
ある SWF ファイルの例*3を示します。ヘッダは省略しています。
- [D]
DefineBitsJPEG2
(ビットマップの定義) - [D]
DefineShape
(シェイプの定義) - [D]
DefineSound
(サウンドの定義) - [D]
DefineFont
(フォントの定義) - [C]
PlaceObject
(シンボルの配置) - [D]
DefineText
(静的テキストの定義) - [C]
PlaceObject
(シンボルの配置) - [C]
ShowFrame
(フレームの表示) - [D]
DefineMorphShape
(シェイプトゥイーンの定義) - [D]
DefineButton2
(ボタンの定義) - [C]
StartSound
(サウンドの配置) - [C]
PlaceObject
(シンボルの配置) - [C]
RemoveObject
(シンボルの削除) - [A]
DoAction
(フレームアクション) - [C]
ShowFrame
(フレームの表示)
おおまかにいうと,
- [D] シェイプ,テキスト,ビットマップ,サウンドといった「シンボル」の「定義」をするチャンク
- シンボル ID あり(Dictionary に登録される)
- [C] シンボルの表示やフレームのラベル定義など「制御」をするチャンク
- シンボルを表示したり削除したりする Display List tags
- 背景色を指定したり外部アセットを読み込んだりする Control tags
- [A] フレームアクションを示すチャンク
- 厳密には「制御」用チャンクに分類されるが,あえてわけた
にわけられます。また,上記ではメインタイムラインの処理しか記述していませんが,DefineSprite
というチャンクで MovieClip を定義することが可能です。この Sprite のチャンクの中には制御用チャンク(やフレームアクション)を入れ子のように含むことができます(末尾ファイル例を参照)。
以下,注意書き。
- 制御用チャンクで利用されるシンボルは,事前に定義用チャンクにより定義されている必要がある。前方参照はできない。
- 前方参照を許さない構造のため,Flash Machine(ActionScript VM ではない)は,ファイルを頭から読み込んでシーケンシャルにレンダリングしていけばよい。
- このため HTTP Stream など遅いストリーム上の SWF ファイルを,順次レンダリングしていくことが可能である。
- レスポンス,インタラクティビティの向上
- 「レイヤー」は Flash IDE が自動的に depth を割り振っているだけである。
- モーショントゥイーンやイージングは,専用タグ(命令)があるわけではなくて,各フレームごとに Transformation Matrix を指定した
PlaceObject
がある(Flash IDE が各フレームに分解している),と思われる。
何がいいたいかというと,Flash IDE での操作から見える構造(FLA ファイル)というのは IDE 上の表現であり,SWF 上ではより「低レベル」の表現がされている,ということです。
SWF action model の変遷
原典の「Actions」章を参照してください。
SWF 3 action model
- まだ「ActionScript」とはいえない(基本的に MovieClip 操作命令だけ)
Play
,Stop
,GotoFrame
等最小限の「命令」のみSetTarget
によって action の対象を切り替えることができるだけ
たとえば button over などのイベントハンドラはどのようになっているのかというと,DefineButton2
チャンクに該当するイベント用の ACTIONCODE が含まれています*4。
SWF 4 action model
Push
,Pop
,Add
,Equals
,And
,SetVariable
等,スタックマシン型言語処理系として最低限の ACTIONCODE が定義された- フロー制御は
Jump
,If
,Call
のみ- つまりユーザー定義関数(・プロシージャ)の呼び出しのみ可能
Flash Lite 1.x のベースとなっています。
SWF 5 action model
CallFunction
,CallMethod
等によって「名称ベース」の動的バインド呼び出し((OLE automation 的というか……いや別にDISPID
をあらかじめ取得しなきゃいけないわけじゃないですが。))ができるようになった- つまり,実行系で定義されている複雑なクラス・関数(たとえば
MovieClip.beginFill()
とか)をも call できるようになった
- つまり,実行系で定義されている複雑なクラス・関数(たとえば
NewObject
,GetMember
,Enumerate
等によって,「クラス」概念をサポートした- プロトタイプベースのオブジェクト指向(JavaScript と同じ)
Subclass.prototype = ...
DefineLocal
によってローカル変数,グローバル変数の別ができた- VM として配列をサポートした*5
- 文字列定数プールが定義できるようになった
Increment
やビット演算など,いくつかの便利な演算用 byte code が実装された(FlashLite 1.x では一部実装済み)
SWF 6 action model
- これまで action chunk を定義していた
DoAction
に加えて,DoInitAction
でも action chunk を定義できるようになったDoAction
がフレーム内に DisplayList を配置してから処理を開始していたのに比べ,DoInitAction
は到達した瞬間に実行される(ただし初回のみ)
Flash Lite 2.x のベースとなっています。
SWF 7 action model
Extends
によって単一ステップによって継承を表現できるようになった- これまではコンパイラががんばっていたところを,AVM が裏側でやるようになっただけ?
CastOp
によってクラス(・インタフェース)間キャストができるようになった?ImplementsOp
によってインタフェースをサポートした?Try
,Throw
によって例外処理できるようになった
SWF 9 action model
DoABC
チャンクによる AVM2 用バイトコードのサポート
action model の変遷のまとめ
以下のように,だいたい3回の大変革がなされています。
SWF 3 | 単純なフレームアクション |
SWF 4 | 単純な演算が可能なスタックマシンとしての実装 |
SWF 5 | 外部関数(システム組込関数)の動的バインド呼び出しのサポート,クラスのサポート |
SWF 9 | AVM2 のサポート |
とくに SWF 5 における「外部関数の動的バインド呼び出し」が大きいです。SWF 4 までは ActionScript のみで Flash movie を作成するのは事実上不可能でしたが,SWF 5 では「定義用チャンク」に相当するクラスが Flash Machine に準備されたため,これを外部関数・クラスとして呼び出すことにより ActionScript のみでかなりの Flash movie を作成することができるようになりました。といっても,ビットマップやフォントについては ActionScript でイチからどうこうできないため((フォントはともかくビットマップレベルであれば flash.display.BitmapData
を利用すれば描けないことはありませんけど。)),リソース埋め込みなどの外部ツールが必要になります。
逆にいうと,SWF 4 までであれば Flash file format specification を読みこなせば自作 Flash Player を作成することができましたが,SWF 5 以降は(specification に記述されていない)Flash Machine 組み込みクラスを実装する必要があります。
さらに付け加えると,ActionScript VM は byte code interpreter として働いているだけであり,ソースとなる言語の文法を規定しません。実際,ActionScript 1.0 から 2.0 では文法的に大幅な改変がなされましたが,byte code compiler の部分で吸収しています*6。また,haXe は型パラメータ*7をもつなど ActionScript 2.0 とは言語仕様に異なる部分がありますが,これも byte code を経由しているおかげです。
SWF ファイル生成のためのツール
だいたい挙動を把握しているものだけをあげておきます。
- Flex SDK
- Adobe 製
- written in Java?
- えーっとどう説明すればいいんですかね?
- 少なくとも ActionScript コンパイラとしては働くといえる
- mxml をどう SWF に落としているかはわかんない
- MTASC
- ActionScript 2.0 互換? ActionScript コンパイラ
- written in OCaml
- これ単体だと ActionScript のコンパイルしかしない(
DoAction
,DoInitAction
,ShowFrame
のチャンクしか吐いていない気がする) - byte code optimization をしているかは不明(OCaml 読めないんですもの); してない気がする。
- haXe
- ActionScript 2.0 的 + 独自仕様 ActionScript コンパイラ
- written in OCaml
- MTASC と同じベンダの後継ツール
- これ単体だと ActionScript のコンパイルしかしない
- AVM1 byte code のほかに,下記のものにトランスレートできる
- AVM2 byte code
- ActionScript 3.0 source
- JavaScript source
- PHP source
- Neko VM 用 byte code
- byte code optimization をしているかは不明; してない気がする。
- libming
- C API(ならびに他言語バインディング)により SWF を各チャンクとして生成するもの
- written in C
- ActionScript compiler 部は,lex, yacc 利用
- これだけで SWF ファイルを生成可能(フォントまわりは独自仕様ですが)
- byte code optimization はしてない
先に述べたように,SWF 5 以降では Flash Machine のクラスを呼び出せるようになったので,ActionScript を MTASC や haXe でコンパイルし,スクリプトでは記述できない画像やフォントを swfmill で定義用チャンクとして埋め込む,という手法が流行っているようです。swfmill でシェイプを定義することも可能ですが,XML でシェイプを描いていくより ActionScript で描くほうが面倒くさくないし柔軟性もありますからね。
FlashLite 1.x は SWF 4 ベースなので ActionScript でシンボルの定義・生成が行えないため,サーバサイド SWF 生成の分野では libming + 他言語バインディングが利用されているようです。ActionScript メインで使うのなら個人的に libming はおすすめしません*9。
SWF ファイルの詳細例
実際にはテキスト形式になっているわけではありませんが,「構造」を疑似言語で示しました。
HEADER { Signature: CWS; Version: 6; FileLength: 1234; FrameCount: 100; ... } chunk "DefineBitsJPEG2" as character(1) { JPEGData: 0xff, 0xd8, ...... } chunk "DefineShape" as character(2) { ShapeBounds: (0,0)-(100,100); Shapes: [ SHAPE { ... }, ... ]; } chunk "DefineSound" as character(3) { format: MP3; rate: 44kHz; data: 0xff, 0xaa, 0x90, 0x40, ...... } chunk "DefineFont" as character(4) { glyph[0]: SHAPE { ... }; glyph[1]: SHAPE { ... }; ... } chunk "PlaceObject" { place character(2) on depth(10) with matrix { 1, 0, 0, 1 }; } chunk "DefineText" as character(5) { bound: 0, 0, 300, 40; TextRecords: [ Font(1001).glyph[0], ... ]; } chunk "PlaceObject" { place character(5) on depth(20) with matrix { 1, 0, 0, 1 }; } chunk "ShowFrame"; chunk "DefineMotionShape" as character(6) { StartBounds: (0, 0)-(100, 100); EndBounds: (0, 0)-(100, 100); MorphFillStyles: FillStyles: ...; ...... } chunk "DefineButton2" as character(7) { Characters: BUTTON { character(1), ... } Actions: on over { SetTarget "spinner" GotoFrame(0); SetTarget ""; } } chunk "StartSound" { play(3) with loop(5) from ...; } chunk "PlaceObject" { place character(7) on depth(30) with matrix { ... }; } chunk "RemoveObject" { remove character(2) on depth(10); } chunk "DoAction" { GotoFrame(1); } chunk "ShowFrame"; ... chunk "DefineSprite" as character(100) { chunk "StartSound" { play(53); } chunk "PlaceObject" { place character(54) on depth(10) with matrix { ... }; } chunk "ShowFrame"; chunk "PlaceObject" { place character(58) on depth(20) with matrix { ... }; } chunk "RemoveObject" { remove character(54) on depth(10); } chunk "DoAction" { GotoFrame(1); } chunk "ShowFrame"; } chunk "PlaceObject" { place character(100) on depth(10) with matrix { ... }; } chunk "ShowFrame"; ... chunk "End";
*1:かつては Macromedia のややこしい制限(→DSAS開発者の部屋:SWFファイルフォーマットとライセンス)に配慮したり,unofficial な SWF File Format Reference | Made to Order Software Corporation で勉強したりしなくてはいけませんでした。いい時代になったものです。
*2:Adobe の文書だと AVM の違いと言語仕様の違いがごたまぜになっている印象があります。ニワカには 2.0 と 3.0 の言語仕様の違いがわかりませんでした。
*3:実在するものではありません。原典「Introduction」章の「The dictionary」節の例をやや改変しています。
*4:これは SWF 4 action model でも同様です。SWF 5 以降でも同じですが,SWF 5 以降はインスタンスのイベントハンドラプロパティとして ActionScript からアクセス可能になります。
*5:SWF 4 でも変数名を工夫することにより,無理やりハッシュ的なものを使うことはできましたが。
*6:もちろん新文法・構造をサポートするため追加された byte code が存在します。SWF 7 action model など。
*7:C++ の template 機構のように働くことができます。
*9:と書くと FUD みたいですね。libming はどちらかというと API によるチャンクの生成がメインであり,ActionScript compiler はおまけでしたから,私感として質がどうもなぁ。