Canvasで描いた絵をFlashで保存する方法

こんにちは、八木です。
最近、node.jsやFacebooアプリ作成など、JavaScriptを触る機会があり、JavaScriptも結構使いこなせるようになってきました。
私が作成したモザイクアートアプリの「Profile Photo Mosaic」もJavaScriptCanvasを使用しています。
Facebookにログイン | FacebookFacebookアカウントが必要になります

Canvasを使用すればJavaScriptだけで、お絵かきアプリや画像の編集などが出来て、Flashで作成するのとはまた別の楽しさがありますね。
このアプリも開発当初はJavaScriptのみで作成しようかと考えていたのですが、Canvasはファイルの保存/アップロードがサポートされておらず、作成した画像をFacebookにアップロード出来ないという問題がありました。

対処として画像のアップロードのみをFlashで実装するという方法をとったのですが、今回はそのJavaScriptからFlashに渡すときの実装の仕方を紹介したいと思います。

まず、画像をアップロード/ダウンロードするためには、Canvasのデータから画像データを作成しないといけません。
Canvasのデータを取得するにはtoDataURL()というメソッドを使うのがよさそうです。

//javascript
var canvas = document.getElementById('canvas');
console.log(canvas.toDataURL('image/png'));
//出力
//data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAY...

toDataURL()を使えば、画像のデータを取得することが出来ます。この値は画像をBase64エンコードしたテキストデータになります。
これをバイナリデータに変換しなければならないのですが、JavaScriptはバイナリデータを直接触ることはできず、他のものを使用しないといけません。

画像の生成の方法として、一つはtoDataURL()で変換したデータをサーバに投げてサーバサイドで画像データに変換するやり方、もうひとつはtoDataURL()で変換したデータをFlashに渡して、Flash側で画像データに変換するやり方があります。

「Profile Photo Mosaic」ではあまりサーバサイドで処理をさせたくなかったので、Flashで処理するようにしました。

Flashではバイナリデータを直接扱うことが出来、ファイルの保存/アップロード/ダウンロードも自由自在に出来ます。

CanvasFlashの連携サンプル

Flash側にバイナリデータを渡す場合、JavaScriptFlashを連携して処理する必要があります。
Canvasでおえかきして、それを保存するサンプルを作成したので、それを使って説明していきます。


マウスでドラッグして色を塗り、saveボタンでローカルに保存するだけのサンプルです。色を塗る領域はCanvasを使用して、saveボタンはFlashを使用しています。
以下がhtmlのコードになります。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>CanvasToFlash</title>
</head>
<body>
  <canvas width="300" height="300" id="canvas"></canvas>
  <div id="save"></div>
  <script src="js/swfobject.js" type="text/javascript"></script>
  <script src="js/main.js" type="text/javascript"></script>
</body>
</html>

Canvasがお絵かきの領域で、#saveがFlashのセーブボタンになります。セーブボタンはswfobjectを使用してswfを読み込んでいます。
CanvasFlashとの連携はmain.jsで処理しています。

//main.js
(function(){
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    var pressed = false;
    
    //マウスドラッグで色を塗る
    canvas.addEventListener('mousedown', function (e) {
        ctx.beginPath();
        ctx.moveTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
        pressed = true;
    });
    window.addEventListener('mousemove', function (e) {
       if(pressed){
            ctx.lineTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
            ctx.stroke();
       }
    });
    window.addEventListener('mouseup', function (e) {
        ctx.lineTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
        ctx.stroke();
        pressed =false;
    });
    
    var canvasToFlash = window.canvasToFlash= {};
    
    //flash側から呼ばれるメソッド。
    //dataURLを返す。
    canvasToFlash.getDataURL = function(){
        return canvas.toDataURL('image/png');
    };
    
    //呼び出すメソッド名をFlash側に渡す。
    var flashvars = {
        getDataURL:'canvasToFlash.getDataURL'
    };
    var params = {
        menu: 'false',
        scale: 'noScale',
        allowFullscreen: 'true',
        allowScriptAccess: 'always',
        bgcolor: '#FFFFFF',
        wmode: 'direct'
    };
    //swfobjectでswf読み込み
    swfobject.embedSWF(
        'CanvasToFlash.swf'
        'save',
        '100',
        '30',
        '10.0.0',
        'expressInstall.swf', 
        flashvars, 
        params
    );
})();

コードの上半分は、マウスの操作で色を塗る処理です。下半分がswfの読み込みと、flash側から呼ぶメソッドの定義になります。
JavaScriptからFlashに値を渡す場合は、flashvarsに渡したい値を入れることによって、Flash側から呼び出すことが出来ます。

次にFlash側(ActionScript3.0)の処理を見ていきましょう。

//Main.as
package {
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.external.ExternalInterface;
    import flash.net.FileReference;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.utils.ByteArray;
    import mx.utils.Base64Decoder;
    public class Main extends Sprite {
        public function Main():void {
            var saveButton:TextField = new TextField();
            saveButton.border = true;
            saveButton.borderColor = 0xaaaaaa;
            saveButton.text = 'save';
            saveButton.selectable = false;
            saveButton.autoSize = TextFieldAutoSize.LEFT;
            addChild(saveButton);
            
            buttonMode = true;
            var params:Object = loaderInfo.parameters;
            var getDataURL:String = params.getDataURL;
            
            ExternalInterface.call(send,'init');
            addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void {
                var dataURL:String = ExternalInterface.call(getDataURL);
                if (dataURL == null) {
                    throw new Error(dataURL);
                }
                
                var decoder:Base64Decoder = new Base64Decoder();
                //'data:image/png;base64,iVBORw0K...'の「,」以降のデータを取得してそれをデコードする
                decoder.decode(dataURL.split(",")[1]);
                var bytes:ByteArray = decoder.flush();
                var file:FileReference = new FileReference();
                file.save(bytes, 'img.png');
            });
        }
    }
}

flashvarsで設定した値は、loaderInfo.parametersの中に入っており、それを参照することによって値を取得することが出来ます。また、Flash側からJavaScriptの関数を呼び出す場合は、ExternalInterface.call()メソッドを使用することによって実現できます。例えば、ExternalInterface.call('alert','hello');でJavaScriptのalert('hello')が実行されて、ポップアップが表示されます。

今回の例では、ExternalInterface.call(getDataURL);で、JavaScriptで定義したcanvasToFlash.getDataURL()を呼んでいます。

さて、dataURLのテキスト形式からバイナリデータのByteArrayに変換する方法ですが、変換にはmx.utils.Base64Decoderクラスを使用します。このクラスは「flex_sdk/frameworks/libs/framework.swc」の中に定義されており、使用するにはコンパイラ設定のパスを通してあげる必要があります。

最後に、FileReferenceクラスを使用して、バイナリデータをローカルに保存しています。

まとめ

CanvasのおかげでJavaScriptのみで出来ることは増えましたが、ファイルの保存やアップロードしようとするとFlashやサーバサイドに処理を任せないといけないところが多々あります。ファイルの保存/アップロードを実装しないといけない場合は、画像処理をすべてFlashで実装した方が手っ取り早く作ることができると思います。作成するものによってCanvasFlash、使い分けて実装していきましょう。