2012年7月14日土曜日

canvasで描いた画像をローカルに保存する方法

HTML5のcanvas要素で生成した画像は,toDataURLメソッドを使ってdataスキーマ形式のpng画像に変換することができます.従って,この文字列を例えばa要素のhref属性に設定してやれば,ユーザーはこのリンクをクリックすることでファイルをダウンロードすることができます.

しかしこのやり方には一箇所落とし穴が存在します.chromeにおいて概ね長さが1MBを超えたurlを指定したリンクを開こうとした場合,あろうことかブラウザがクラッシュしてしまうのです.エラーを発生すること無しにいきなりクラッシュするため,try-catch構文による対応が出来ません.

更に困ったことにcanvas要素が出力するpng形式の画像はそのファイルサイズがまちまちで,カンバス領域のサイズから最終的なファイルサイズを推し量ることが出来ません.従ってこのやり方はクロスブラウザを念頭に入れた場合,非常に扱いにくいものとなってしまっています.この他に上手なやり方は無いのでしょうか?

※追記
BlobBuilderが非推奨となった模様です.
代替の記述法はhttps://developer.mozilla.org/en-US/docs/DOM/Blobこちらを参照して下さい.


実は存在します.ここではこの記事で話題に挙がっていたものについて解説します.

手順
  1. canvasのtoDataURLメソッドを実行し,dataスキーマ形式のpngデータを取得する.
  2. 上で得られた文字列からヘッダーとなる「data:image/png;base64,」を除去する.
  3. 上で得られたデータ部文字列をatobメソッドによりバイトデータ文字列に変換する.
  4. Uint8Arrayオブジェクトを生成する.長さは上出えたバイトデータ文字列長とする.
  5. バイトデータ文字列を一文字づつcharCodeAtメソッドを使って数値化し,Uint8Arrayオブジェクトに挿入していく.
  6. BlobBuilderオブジェクトを生成する.
  7. BlobBuilderのaddメソッドにUnit8Arrayオブジェクトのbufferプロパティ(実体はArrayBufferオブジェクト)を渡す.
  8. BlobBuilderのgetBlobメソッドを実行し,「image/png」形式のblobオブジェクトを生成する.
  9. URLオブジェクトのcreateObjectURLメソッドにより,上のblobをurl文字列に変換する.
  10. このurl文字列をa要素のhref属性に設定する.
実際の動作はこちらから. svgの内容をpng形式で取得可能としてみました.

一度エンコードされたデータを一旦バイナリデータに戻すなど,面倒ではありますが,この方法を使えば一切追加のスクリプトモジュールを使うこと無しにcanvasで生成したpng画像をダウンロードさせることが可能となります.この方法であればchromeでも問題なく動作します.また,blobを用いた処理は非常に軽快であることも付け加えておきます.

但し,operaではblobを生成することができないため,従来通りの方法を用いる必要があります. (従ってoperaだけパフォーマンスが悪いと言ったことになります.)

なお,将来HTMLCanvas要素にtoBlobメソッドが実装されたなら,上のような面倒な処理を省いて直接blobオブジェクトが取得可能となるかもしれません.

function getPngUrl(){
 var svg = getSVGSource();
 var c_svg = document.createElement("canvas");
 c_svg.width = canvas.width;
 c_svg.height = canvas.height;
 var ctx = c_svg.getContext("2d");
 ctx.clearRect(0, 0, canvas.width, canvas.height);
 ctx.drawSvg(svg, 0, 0, canvas.width, canvas.height);
 var dataUrl = c_svg.toDataURL("image/png");
 try{
  //var blob = c_svg.toBlob();
  //https://groups.google.com/a/chromium.org/forum/?fromgroups#!topic/chromium-html5/WOxmqfDAaqo
  var bin = atob(dataUrl.split("base64,")[1]);
  var len = bin.length;
  var barr = new Uint8Array(len);
  for(var i = 0; i<len; i++){
   barr[i] = bin.charCodeAt(i);
  }
  var bbConstractor = window.MSBlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder || window.OBlobBuilder || window.BlobBuilder;
  var bb = new bbConstractor();
  bb.append(barr.buffer);
  var URL = window.URL || window.webkitURL;
  return URL.createObjectURL(bb.getBlob("image/png"));
 }catch(e){
  return dataUrl;
 }
}

0 件のコメント:

コメントを投稿