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 等によって,「クラス」概念をサポートした
  • 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 8 action model

action model に変更はありません。

Flash Lite 3.x のベースになっています。

SWF 9 action model
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 ファイル生成のためのツール

だいたい挙動を把握しているものだけをあげておきます。

  • swfmill
    • プレインテキスト XML と SWF ファイル(各チャンク)を変換するもの
      • 画像(PNG, JPEG),TrueType フォント(サブセット化可能)バイナリを XML から参照(して埋め込み)可能
      • (あれ?サウンドは?)
    • written in C++
    • その気になれば ActionScript byte code*8も含め,これだけで SWF ファイルを生成可能
    • どちらかというと MTASChaXe と絡めてリソース定義用サブコマンドとして使われる
    • (ライブラリとして使って API を定義すれば libming 的に使えるのではないか?)
  • libming
    • C API(ならびに他言語バインディング)により SWF を各チャンクとして生成するもの
    • written in C
    • これだけで SWF ファイルを生成可能(フォントまわりは独自仕様ですが)
    • byte code optimization はしてない

先に述べたように,SWF 5 以降では Flash Machine のクラスを呼び出せるようになったので,ActionScriptMTASChaXeコンパイルし,スクリプトでは記述できない画像やフォントを 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 機構のように働くことができます。

*8:スクリプトソースではないことに注意してください。

*9:と書くと FUD みたいですね。libming はどちらかというと API によるチャンクの生成がメインであり,ActionScript compiler はおまけでしたから,私感として質がどうもなぁ。