« アジャイルセミナー傍聴録 | メイン | n個のサイコロの目がk種類になる確率 »

 JavaアプレットでSVGを再生する

ベクターグラフィクスのフォーマットに、SVGというのがある。GIMPやDiaなど、画像をベクターグラフィクス形式で保存することをサポートしている場合、そのフォーマットはSVGであることが多い。SVGがオープンな規格であることがその理由であることは想像に難くない。特に理由が無ければSVG形式を採用することになるのであろう。
SVGは、フォーマットがXML形式であり、それでいて、Adobe Flashと同様、アニメーションや、マウス操作等の入力に対する動作を記述することもできる。何て理想的でパーフェクトなのであろうか。
アニメーションするベクターグラフィックだとFlash形式が使われることの方が圧倒的に多いが、特に静止画像だとSVGはWikipediaでもよく使われており、マルチページで文書用途のPDFやPostScriptを除くと、既にデファクト・スタンダードになっていると思う。

筆者としては、SVGはテキストエディターで編集できるというのが特にポイントが高い。EmacsやPerlやPythonで扱い易いかどうかというのは根本的な問題であり、それを満たすものがあるならそれを満たさないものは無いのである。よって、筆者としてはFlashは無いのである。

しかしながら、SVGは環境によっては正しく再生されないことがしばしばある。筆者の自宅にあるWindows XPでは.svgファイルをダブルクリックしても開かないし、Internet Explorerでは、表示されてもアニメーションは動かない。Mac OS X 10.5のSafariではアニメーションも大体動くようだが、実に惜しいことに、α付きのフィルターが働かないようである。そんなのは些細なことだという話もあるが、たまたまそれを試したくてSafariでプレビューしながらSVGをEmacsで書いて思い通りに表示されず、何が間違ってるのだろうと悩んだ果てにSafariがそれに対応していないと知った時の心の傷は、深かったのである。

そんな訳でSVGの別の再生方法を探していると、Batik SVG ToolkitというJavaのパッケージを見つけた。まあまあ再生の成功率が高いようである。このサイトにあるMoving GearsというSVGのアニメーションが正しく表示されるのは、筆者が試したフリーのプレーヤーの中では、このBatikに含まれるSquiggleというプレーヤーだけであった(同サイトで指定されているOpera 9やAdobe SVG Viewer(サポート終了)は試していない)。
しかし、わざわざSVGを再生するのにJavaを起動することは無かろう、とパスしようとしたが、JSVGCanvasのデモのページを見て、気が変わった。何と、SVGファイルが、HTMLのフォームへの入力に従って書き換えられながら再生されているのである。

Webブラウザで表示されている画像ファイルそのものがユーザーの入力に従って変化するというのは、意外と難しい。HTMLのsubmitボタンやJavascriptによりユーザーの入力がサーバーに送信され、サーバー側プログラムが動的に画像ファイルを生成すれば、似たようなことができるだろうが、クライアントサイドで画像ファイルを書き換えながら再生できるのは、プラグインのプレーヤーならである。また、再生時の画像ファイルの加工方法として、例えばFlash VideoのメタデータをActionScript(Flash Videoファイル内の)やJavascriptで書き換えながら再生するというような、付属的なメタデータを動的に変更することはよくなされているようだが、上の例では、SVGファイルの相当コアな部分にあるベクトル図形が、Javaのコードで、しかも単なるXML APIで書き換えられているのである。

これは、筆者のようなJavaをかじった者に、触ってみろと言っているに違いない。

SVG書き換え再生テストの起動ページ
ソースコード
SVGファイル

Restartボタンを押すと、SVGファイルのid="TheString"の要素のテキストが書き換えられる。


それだけのことであり、SVGファイルを読み込んで再生する部分はJSVGCanvasのデモのページAppletDemo.javaから丸ごと拝借したのだが、実際に作ってみると、色々あった。

まず、アニメーションの再生終了後にSVGファイルをリロードせずにもう一度最初から再生する方法がわからなかった。
それくらいのI/FはJSVGCanvasクラスにあっても良さそうに思ったのだが、それらしいのは見当たらず、実際にいろんなメソッドを呼んでみたが、再開はしなかった。
結局、batik-users MLのアーカイブで見つけた、SVGAnimationEngine#setCurrentTime()を使う方法でできたが、そのメールでもこれが本当に正しいかどうかわからないと書いてあるし、Batikのjavadocを読んでも妥当性がさっぱりわからない。


次に、Safariでは発生しなかったが、IEでは、ネットワーク越しのSVGファイルのロード開始後になかなかアニメーションの再生が始まらないという問題が起こった。
当初はHTTP上のXML parserが何らかの理由で完了しないからかと思ったが、ファイル全体をStringとして保存してからparseするようにしても、さらにSVGファイルをJARに含めてそれを読み出すようにしても、直らなかった。
SVGファイルが参照するファイルの読み込みに時間がかかるのかと思って、svg11.dtd等の参照ファイルを全てローカルに置いても変化が無かった。
たまたま、JavaコンソールでGCさせるとアニメーションが開始することに気付いたので、色々なイベントリスナーでSystem.gc()を呼ぶようにしてみたが、効果が無かった。しかし、苦し紛れに、スレッドを作ってSystem.gc()を繰り返してみたら、すぐに再生が始まるようになった。
従って、やはり何らかのタイミングでSystem.gc()すれば良いのだと思うのだが、改めてSVGLoadのlisterやSVGLoadEventDispatcherListenerなど色々なイベントリスナーで、invokeLaterやsleepもしながらSystem.gc()してみたが、適当な場所は見つからず、結局別スレッドでGCする以外では解決しなかった。

後に、batik-users MLのアーカイブに詳しい原因が書いてあるのを見つけたが、Batikを改造しなくて済む回避策は書かれていなかった。Subversionには何らかの修正が入れられたようだが、それはBatik 1.7のリリース後のことのようである。


さらに、アニメーションが始まるまで要素を表示しない方法ではまった。
今回のSVGのテキストの下の角丸矩形は、テキストによって長さを変える必要があるので、その長さをECMAScriptで設定するようにした。そのため、それが呼ばれるまでは矩形の長さが不正であり、アニメーション開始前から表示するとちらつきが発生してしまう。
当初は、SVGの各要素をvisibility="hidden"にしておいて、onload時に矩形の長さを設定し、アニメーションのonbegin時にscriptでsetAttribute("visibility", "visible");するようにしていたのだが、Safari 5.0では<animation>タグのonbeginが効かないことが判明した。MacではSVGファイルをダブルクリックするだけでSafariが開いてアニメーションがほぼ完璧に再生できるので、SafariでレビューしながらEmacsでSVGを編集していた。

今回、その便利さを諦める気になれなかったので、Safariに合わせて、onbeginを使わないことにした。
それではと、onload時にvisibleにすると、それが矩形のサイズ変更後であっても、ちらつきが発生してしまったり、アニメーションの開始まで間があると画面が固まって見えてしまったりした。
アニメーションの開始直前にvisibleにすることができないのであれば、アニメーションの途中でvisibleにするしか無い。<animation>タグでvisibleとかinvisibleとかの定性値を変化させることもできるようだが、今回はopacityを0から1に変化させることで徐々に見えるようにした。

その前に、scaleが1だと小さく表示されるような座標で作って、アニメーション中にscaleを増やすことにより、アニメーションが始まるまでは不正な表示になっても目立たないようにしていたのだが、アニメーションの最後にopacityを変化させてフェードアウトするようにすると、Batikでもう一度最初から再生するとopacityが1に戻らず、opacityを変化させるまで表示されないということが起こってしまった。
SVGファイルの作り方の定跡は調べていないが、小さく表示してごまかすのは災いの元だと教えられた。


さらに、Batikで単にSVGAnimationEngine#setCurrentTime()でリピートするとonload時のスクリプトが実行されない問題があった。
SVGファイルのECMAScriptの関数を明示的に呼び出す方法がわからなかったので、ScriptingEnvironment#dispatchSVGLoadEvent()でSVGLoadイベントを発生させてonloadのスクリプトが実行されるようにして解決した。
SVGファイルの作り方の常道は知らないが、アニメーションの時刻を戻す可能性があるなら、時刻変更の度にonloadの処理を実行しなくて済むよう、初期化処理はonloadでせず全てonbeginでやる方がいいのかも知れない。

辛うじて動くようになったが、Batik 1.7のJSVGCanvasを使用するアプリは20以上のJARをロードする必要があり、Webで公開するには非常に不便である。アプレットとしてブラウザ上で実行するよりJNLP(JWS)を使ってブラウザ外で実行する方が動作が速いが、それをしようとすると、JARの署名など、さらに面倒なことになる、という問題が、上記のbatik-users MLの同じスレッドの議論でも認識されているようだ。
Java3DのようにWeb公開用にprimaryなサイトからJARをダウンロードできるセット(.jnlpファイル含む)が用意されておらず、JAR単位での配布もされていないので、Batikを使用するアプリを公開するには、必要なJARを自分でサーバーに用意しないといけない。

その問題を意識して、Batikに対抗するように、Web配布用のシンプルなライブラリとしてSVG Salamanderというものの開発が始められているようである。JavaはWeb公開用にしか使わない筆者のニーズにぴったり合っているのだが、筆者が手で書いたSVGが全然思い通りに再生されなかったので、今回は諦めた。

トラックバック

このエントリーのトラックバックURL:
http://ynomura.dip.jp/cgi-bin/mt/mt-tb.cgi/177

コメント (1)

Posts like this brighten up my day. Thanks for taknig the time.

コメント投稿フォーム

※投稿されたコメントはオーナーが承認するまで表示されません。


Powered by Movable Type 3.35