ブラウザで(複数)選択されたローカル画像をリサイズしてPOST

# この記事について
画像アップロードを作ろうとした際、サーバ側の負荷(主にメモリ)を減らす方法として、
クライアントでリサイズ済みの画像を送信する選択を取りました。

サーバに送信する画像形式の選択肢として、

- Base64
- BLOB

が調べた中で出てきました。

Base64は元データよりデータサイズが33%増えるため今回は扱いません。
デコードする処理もサーバ側へ実装する必要があり面倒なのでクライアントで全部やっちゃいます。
容量削減も目標であるためBLOBでサーバに画像データを送ります。

## 処理の流れ

1. JavaScriptで指定したサイズで縦横比を維持したままリサイズし、かつセンタリングされた状態でcanvasに描写
2. canvasからBLOB型に変換
3. BLOBを`FormData`につめて`POST`する



# 以下コードと簡単な解説

コード自体は難しいものではないです。

## ファイル取得 -> キャンバス描写
```JavaScript
const imagePreviewBox = document.getElementById('preview')
const MAX_SIZE = 256
// ファイルが選択されたら
$('input[type=file]').change(function () {
  let canvasList = new Array()
  // 画像をリサイズする
  for (i = 0; i < this.files.length; i++)
    let image = new Image();
    let reader = new FileReader();
    let file = this.files[i];

    let canvas = document.createElement('canvas')

    reader.onload = function (e) {
      image.onload = function () {
        let width,
          height,
          marginTop = 0,
          marginLeft = 0;

        if (image.width > image.height) {
          // 横長の画像は横のサイズを指定値にあわせる
          let ratio = image.height / image.width;
          width = MAX_SIZE;
          height = MAX_SIZE * ratio;
          marginTop = (MAX_SIZE - height) / 2
        } else {
          // 縦長の画像は縦のサイズを指定値にあわせる
          let ratio = image.width / image.height;
          width = MAX_SIZE * ratio;
          height = MAX_SIZE;
          marginLeft = (MAX_SIZE - width) / 2
        }
        canvas.setAttribute('width', MAX_SIZE)
        canvas.setAttribute('height', MAX_SIZE)
        imagePreviewBox.appendChild(canvas)

        let ctx = canvas.getContext('2d')
        ctx.drawImage(image, 0, 0, image.width, image.height, marginLeft, marginTop, width, height)
        canvasList.push(canvas)
      }

      image.src = e.target.result;
    }
    reader.readAsDataURL(file);
  }
});
```
説明するほどでもないですが。

`input type=file multple`で複数選択されたファイルをforで回して処理していきます。
`Image`と`FileReader`インスタンスは選択されたファイルの数だけ必要になるためforの内側で生成します。
`if (image.width > image.height) `のifブロックで縦横比を維持するための高さ、幅、センタリングするためのtop,leftのマージンを計算。
計算したものを`ctx.drawImage`で描写

※コード内でcanvas要素をDOMへappendしていますが、canvasはメモリで保持しておけばcanvas->Blob変換は可能なのでDOMとして書きだす必要はありません。

## canvas -> BLOB変換　-> FormDataに設定

```javaScript

toBlob(canvas) {
  let 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 new Blob([buffer.buffer], { type: 'image/png' })
}

let fd = new FormData(document.getElementById('upload-form'))
this.canvasList.forEach((canvas) => {
  fd.append("何かキーとなる名前", toBlob(canvas))
})
```
## ajaxでpost

```JavaScript
let ajaxParam = {
  url: 'http://example.com/foo',
  method: 'POST',
  data: fd,
  dataType: "text",
  cache: false,
  contentType: false,
  processData: false
}
$.ajax(ajaxParam)
.done()
.fail()
```

これでマルチパートで送信されます。


## 詰まったところ

Q:FormDataに詰めたデータを`console.log(fd)`しても詰めたデータ見れないじゃん。
A:

```JavaScript
fd.get('キー')
// or
for(item of formData){
    console.log(item);
}
```

## 参考
https://qiita.com/0829/items/a8c98c8f53b2e821ac94
http://matz.hatenablog.jp/entry/2017/03/26/014615