Help us understand the problem. What is going on with this article?

Canvasで描画した画像を送信してサーバに保存する

More than 5 years have passed since last update.

タイトルどおりのことをやった際の実装時メモ。

Canvasから画像を取得

2通りの方法が見つかった。

  1. Base64への変換
  2. Blobへの変換

Base64への変換

canvas.toDataURL()を呼び出すだけで取得可能であるが、
画像サイズに比例した巨大な文字列となり、変換によって元サイズより33%データ量が増える。

var base64= this.canvas.toDataURL('image/png');

取得したBase64を<image>srcに指定すれば画像表示され、
<a>hrefに指定すればリンククリックでダウンロードできてこれはこれで便利。

Blobへの変換

canvas.toBlob()を呼び出すだけで取得可能。
Blob形式で表現すれば、createObjectURL(blob)によりURL参照が取得できるので、
画像サイズに依存せずメモリ使用量を抑えられるらしい。(未確認)

これは便利と思いきやcanvas.toBlob()はFirefoxでのみサポートされており、
Firefox以外ではBase64経由でBlobを作成する方法とならざる得ない。

toBlob: function() {
    var base64 = this.canvas.toDataURL('image/png');
    // Base64からバイナリへ変換
    var bin = atob(base64.replace(/^.*,/, ''));
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    // Blobを作成
    var blob = new Blob([buffer.buffer], {
        type: type
    });
    return blob;
}

ちなみにサイズを確認すると、確かにBase64は33%増量していた。

type size
base64.length 132506
blob.size 99361

Serverへ送信する

クライント側は Sencha Touch 2.3 を使用。
というのも2.3から XMLHTTPRequest Level 2(XHR2)を完全にサポートしたので、
バイナリデータも送信できるはずなのでその試験を兼ねて。

サーバは勉強も兼ねてSinatraを利用。

  1. Base64のまま送信する
  2. Binaryに変換して送信する
  3. Blobに変換して送信する

1. Base64版

  • Base64は先頭のminetype(data:image/png;base64,の部分)は不要なため削除
  • サーバ側はデコードしてファイルに書き出してやれば画像として保存される

クライアント側

var base64 = this.canvas.toDataURL('image/png');
var request = {
    url: 'http://localhost:4567/base64',
    method: 'POST',
    params: {
        image: base64.replace(/^.*,/, '')
    },
    success: function (response) {
        console.log(response.responseText);
    }
};
Ext.Ajax.request(request);

サーバ側

require 'base64'

post '/base64' do
    File.open('imageBase64.png', 'wb') do|f|
        f.write(Base64.decode64(params['image']))
    end
    "OK"
end

2. Binary版

  • xhr2: trueを明示的に指定する
  • rawDataとして渡す(dataだと文字列と認識される)
  • サーバ側はbodyをそのまま書き出してやると画像として保存される

クライアント側

toBinary: function(canvas) {
    var base64 = canvas.toDataURL('image/png'),
        bin = atob(base64.replace(/^.*,/, '')),
        buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    return buffer;
}

// Binaryデータを作成
var buf = toBinary();
var request = {
    url: 'http://localhost:4567/binary',
    method: 'POST',
    xhr2: true,
    rawData: buf.buffer,
    success: function (response) {
        console.log(response.responseText);
    }
};
Ext.Ajax.request(request);

サーバ側

post '/binary' do
    File.open('imageBin.png', 'wb') do|f|
        f.write(request.body.read)
    end
    "OK"
end

3. Blob版

  • Blobの場合ファイルとして扱えるので、FormData形式で送信する
  • rawDataとして渡す
  • サーバ側はアップロードされたファイルを書き出す

クライアント側

var buf = toBinary();
// Blobを作成
var blob = new Blob([buf.buffer], {
    type: 'image/png'
});

// FormDataとして送信
var fd = new FormData();
fd.append("image", blob);
var request = {
    url: 'http://localhost:4567/blob',
    method: 'POST',
    xhr2: true,
    rawData: fd,
    success: function (response) {
        console.log(response.responseText);
    }
};
Ext.Ajax.request(request);

サーバ側

post '/blob' do
    img = params[:image]
    File.open('imageBlob.png', 'wb') do|f|
        f.write(img[:tempfile].read)
    end
    "OK"
end

参考

0829
データ駆動はじめました
https://dd1.work/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away