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

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

# Canvasから画像を取得

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

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

## Base64への変換

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

```js
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を作成する方法とならざる得ない。

```js
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,`の部分）は不要なため削除
* サーバ側はデコードしてファイルに書き出してやれば画像として保存される

### クライアント側

```js
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);
```

### サーバ側

```rb
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をそのまま書き出してやると画像として保存される

### クライアント側

```js
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);
```

### サーバ側

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

## 3. Blob版

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

### クライアント側

```js
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);
```

#### サーバ側

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

# 参考
* [Canvas Images and Rails](http://rohitrox.github.io/2013/07/19/canvas-images-and-rails/)
* [XMLHttpRequest2 に関する新しいヒント](http://www.html5rocks.com/ja/tutorials/file/xhr2/)
* [XHR2 Uploads and Downloads - Touch 2.3.0 - Sencha Docs](http://docs.sencha.com/touch/2.3.0/#!/guide/xhr2_guide)