ushiroadFlash Player 実装のための描画モデル変換

最近Flashムービー(swf)の仕様に関する情報が増えてきましたが、シェイプの描画方法についての記事を見かけないのでここに書いておきます。

Flash Player内部の描画モデル

図1のように、Flashのシェイプは、パスの右側と左側を同時に、別々の色で塗ることができる特殊な描画モデルを採用しています。対して、Apple CoreGraphicsやcairoといったライブラリや、その上に実装されているSVGやcanvasは、一つパスが一つの塗色のみを持つモデルを採用しており、これは一般によく使われています。

swf paint model
図1 Flashの描画モデル(swf仕様書より)

以降、Flash内部で使われている特殊な描画モデルを「double-fill モデル」、canvas等の一般的な描画モデルを「single-fill モデル」と呼びます。

ややこしいことに、FlashがActionScriptに提供しているベクタ描画APIは、single-fill モデルを採用しています。よって、デザイナがFlashのオーサリングツール上で描いた絵(double-fillモデルのシェイプとして出力される)と、プログラマがActionScriptを使って描画する絵(single-fillモデルで描画される)は、同じFlash Playerの上で動きながら全く別の描画モデルを使っていることになります。

描画モデル変換の手順

さて、あなたがFlash Playerの再実装をしようとした時、多くの場合使える描画APIはsingle-fillモデルです(*1)。これを使ってdouble-fillモデルのシェイプを描画するにはどうすればよいでしょうか。これは一見すると複雑な問題に見えますが、実は割とシンプルなアイデアで解決できます。

*1: 例外が無いわけではなく、GNU Gnashは、double-fillモデルをサポートしたAGGというラスタライザを組み込む正攻法をとっています。

以降、分かりやすいようにFillStyle0を「左塗り」、FillStyle1を「右塗り」と呼びます。

塗り方向の統一

図2のように、左塗りに橙、右塗りに黄が指定されているパスを考えます。これを、片側の塗り情報だけをもったパス2本が組合わさっていると考え、2本に分解します。続いて、左塗りのパスの始点と終点を入れ替えると、右塗りのパスに変換することができます。この変換を全てのパスに適用すれば、double-fillモデルのシェイプを右塗りだけのパスで表現することができます。

Path split
図2 パスの分解

フィル用ループ生成

続いて、同じフィルスタイルを持つパスの断片を繋いで行きます。ある断片の終点と同じ座標が起点になっている断片を拾っていくだけです。こうして繋いだパスは必ず始点に戻ってきてループを形成する筈です。

フィル描画

ここでループごとにフィルを描画します。

ストローク用パス生成

続いて、ラインスタイルごとにパスの断片を繋いで行きます。ただし、フィルと違い、ラインスタイルは1つのパスに1つだけなので、2本に分解されたパスのうち1本は不要です。よって、ある断片を拾ったらそれと対になる(元々一本だった)断片にも「拾った」というフラグを付けて、重複を防ぐようにします。

ストローク描画

ストロークを描画します。フィルと違いストロークは互いに重なる場合があるので、ここでは描画する順番が問題になります。Adobe公式の仕様書には見当たらなかったのですが、Flash Playerの描画結果を見る限りでは、スタイルのインデックス順に描画すればいいようです。

完成

以上のステップで、single-fillモデルのシステムでもFlashのシェイプを完全に描画できます。図3〜7にcanvasで描画した例を掲載します。

Flash Player
図3 元のSWFファイル(Flash Playerで表示)
Path and Fill
図4 パスの向きとフィルスタイル; ■は右塗り、◆は左塗りを表す
Right fill paths
図5 右塗り成分だけを分離した結果
Reversed left fill paths
図6 左塗り成分だけを分離し、右塗りに変換した結果
Final result
図7 canvasによる最終的な描画結果

このサンプルは shape-render.html で実際に実行できます。

余談

Flashがこのような複雑な描画モデルを採用している理由は、容量を節約するためです。一つのパスで2か所を同時に塗れば、パスの数を削減することができます。加えて、データを可変ビット長にし、最低限のビット数を割り当てるなどの工夫もしています。こういった工夫をするとプレイヤーがどんどん複雑になりますが、Flashが登場した当時は、「CPUはいくらでも使えるが、ネットワークは貧弱で高価」という情勢だったので、CPUの負担を上げてネットワークの負担を下げる、という設計方針が正義だったのでしょう。

2012-10-04 ushiroad