ひろこま Hack Log

プログラミングや機械学習などの知識を記録・共有します

ブラウザで画像を縮小してサーバにアップロードするJavaScript

f:id:twx:20190820122845p:plain
ブラウザで画像を縮小してサーバにアップロードするJavaScript

画像アップロードは案外重い

ブラウザからサーバへ画像をアップロードする際、画像のサイズに注意を払う必要があります。最近のスマホカメラの性能は凄く、iPhone 6S以降のiPhoneで撮った写真のサイズは 4032 x 3024 もあります。

このサイズのままアップロードすることは、もちろん不可能というわけではないのですが、上りネットワーク通信が遅い環境では結構な時間がかかったり、通信量がかかったりと色々な課題があります。

f:id:twx:20190820123839p:plain
大きな画像の課題

そこで、ブラウザ側で画像サイズを縮小してからアップロードできるようにしようと思います。アップロード時間を短縮できたり、通信量を抑えられたり、サーバ側の容量消費を抑えられたりと様々なメリットがあります。

f:id:twx:20190820125650p:plain
小さな画像のメリット

画像縮小の流れ

ブラウザで画像を縮小する流れは以下のようになります。

  • ファイルを選択し、ブラウザで画像を読み込む
  • 縮小後の画像サイズを計算して canvas に描画する
  • canvas から縮小済み画像をbase64として取り出す
  • base64を画像データに変換する

基本的には以上です。

あとは、この画像データをajax等でアップロードできるようにすればOKなのですが、この部分は今回の記事では扱いません。

要素を取得するところでjQueryを使っていますが、別に素のJavaScriptを使っていただいても問題はありません。

ソースコード

以下のhtmlを適当な名前で保存してください。ここでは index.html とします。

index.html

<!-- ファイル選択ボタン -->
<input type="file" accept="image/*">

<!-- アップロードボタン -->
<button id="upload">アップロード</button>

<!-- 縮小画像の表示領域 -->
<canvas id="canvas" width="0" height="0"></canvas>

<!-- 以下、JavaScript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
  var file = null; // 選択ファイルが格納される変数
  var blob = null; // 画像(BLOBデータ)が格納される変数
  const THUMBNAIL_MAX_WIDTH = 500; // 画像がヨコ長の場合、横サイズがこの値になるように縮小される
  const THUMBNAIL_MAX_HEIGHT = 500; // 画像がタテ長の場合、縦サイズがこの値になるように縮小される

  // ファイルが選択されたら実行される関数
  $('input[type=file]').change(function() {

    // ファイルを取得する
    file = $(this).prop('files')[0];

    // 選択されたファイルが画像かどうか判定する
    // ここでは、jpeg形式とpng形式のみを画像をみなす
    if (file.type != 'image/jpeg' && file.type != 'image/png') {
      // 画像でない場合は何もせず終了する
      file = null;
      blob = null;
      return;
    }

    // 画像をリサイズする
    var image = new Image();
    var reader = new FileReader();
    reader.onload = function(e) {
      image.onload = function() {

        // 縮小後のサイズを計算する
        var width, height;
        if(image.width > image.height){
          // ヨコ長の画像は横サイズを定数にあわせる
          var ratio = image.height/image.width;
          width = THUMBNAIL_MAX_WIDTH;
          height = THUMBNAIL_MAX_WIDTH * ratio;
        } else {
          // タテ長の画像は縦のサイズを定数にあわせる
          var ratio = image.width/image.height;
          width = THUMBNAIL_MAX_HEIGHT * ratio;
          height = THUMBNAIL_MAX_HEIGHT;
        }

        // 縮小画像を描画するcanvasのサイズを上で算出した値に変更する
        var canvas = $('#canvas')
                     .attr('width', width)
                     .attr('height', height);

        var ctx = canvas[0].getContext('2d');

        // canvasに既に描画されている画像があればそれを消す
        ctx.clearRect(0,0,width,height);

        // canvasに縮小画像を描画する
        ctx.drawImage(image,
          0, 0, image.width, image.height,
          0, 0, width, height
        );

        // canvasから画像をbase64として取得する
        var base64 = canvas.get(0).toDataURL('image/jpeg');

        // base64から画像データを作成する
        var barr, bin, i, len;
        bin = atob(base64.split('base64,')[1]);
        len = bin.length;
        barr = new Uint8Array(len);
        i = 0;
        while (i < len) {
          barr[i] = bin.charCodeAt(i);
          i++;
        }
        blob = new Blob([barr], {type: 'image/jpeg'});

      }
      image.src = e.target.result;
    }
    reader.readAsDataURL(file);
  });


  // アップロードボタンがクリックされたら実行される関数
  $('#upload').click(function(){

    // ファイルが指定されていなければ何も起こらない
    if(!file || !blob) {
      return;
    }

    // 送信するフォームデータを作成する
    var name, fd = new FormData();

    // 先ほど作った縮小済画像データを添付する
    fd.append('file', blob);

    // ajax でアップロード
    $.ajax({
      url: "http://exapmle.com", // 送信先のURL
      type: 'POST',
      dataType: 'json',
      data: fd,
      processData: false,
      contentType: false
    })
    .done(function( data, textStatus, jqXHR ) {
      // 送信成功
    })
    .fail(function( jqXHR, textStatus, errorThrown ) {
      // 送信失敗
    });

  });

});
</script>

上から順に簡単に解説していきます。

  var file = null; // 選択ファイルが格納される変数
  var blob = null; // 画像(BLOBデータ)が格納される変数
  const THUMBNAIL_MAX_WIDTH = 500; // 画像がヨコ長の場合、横サイズがこの値になるように縮小される
  const THUMBNAIL_MAX_HEIGHT = 500; // 画像がタテ長の場合、縦サイズがこの値になるように縮小される

コメントに書いてある通りですが、ここでグローバルな変数を定義しています。 THUMBNAIL_MAX_WIDTHTHUMBNAIL_MAX_HEIGHT はそれぞれ、縮小後の画像サイズの最大横pxと最大縦pxです。ここでは 500 としていますが、任意の数字で構いません。

元画像が横長の場合、横サイズを 500px にします。元画像が縦長の場合、縦サイズを 500px にします。なお、縦横比は保たれます。

f:id:twx:20190820131551p:plain
横長の場合
f:id:twx:20190820131630p:plain
縦長の場合

次に、この部分です。

    // 選択されたファイルが画像かどうか判定する
    // ここでは、jpeg形式とpng形式のみを画像をみなす
    if (file.type != 'image/jpeg' && file.type != 'image/png') {
      // 画像でない場合は何もせず終了する
      file = null;
      blob = null;
      return;
    }

ここでは、選択されたファイルが jpeg形式とpng形式のいずれでもない場合、何も処理をしないようにしています。扱いたい画像形式を増やしたい場合は、この条件式を変えてください。

次にこの部分です。

    var image = new Image();
    var reader = new FileReader();
    reader.onload = function(e) {
      image.onload = function() {
         .....

ここは若干トリッキーなのですが、ファイルのロードが完了した時と、画像のロードが完了した時の、それぞれのコールバック関数を書いています。おまじないと思ってもらって大丈夫です。

次にこの部分です。

        // canvasに既に描画されている画像があればそれを消す
        ctx.clearRect(0,0,width,height);

        // canvasに縮小画像を描画する
        ctx.drawImage(image,
          0, 0, image.width, image.height,
          0, 0, width, height
        );

ここではcanvasに画像を描画しています。引数の指定の仕方はこちらのサイト様で詳しく解説されています。 drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)-Canvasリファレンス

最後にこの部分。

        // canvasから画像をbase64として取得する
        var base64 = canvas.get(0).toDataURL('image/jpeg');

        // base64から画像データを作成する
        var barr, bin, i, len;
        bin = atob(base64.split('base64,')[1]);
        len = bin.length;
        barr = new Uint8Array(len);
        i = 0;
        while (i < len) {
          barr[i] = bin.charCodeAt(i);
          i++;
        }
        blob = new Blob([barr], {type: 'image/jpeg'});

ここでは、まずcanvasからbase64としてデータを取り出し、次にbase64を画像データに変換しています。 ここでは、jpeg画像を作るために type'image/jpeg' としています。

デモンストレーション

では index.html を開いてみましょう。

こんな画面が表示されます。

f:id:twx:20190820133213p:plain
初期画面

ファイルを選択してみます。まずは横長の画像を選択してみました。

f:id:twx:20190820133302p:plain
横長画像を選択

画像が表示されました。画像サイズを測ってみると、たしかに横幅が 500px になっていました。

次に縦長の画像を選択してみます。

f:id:twx:20190820133411p:plain
縦長画像を選択

今度は縦サイズが 500px になっています。

この状態でアップロードボタンを押せば、この縮小画像のアップロードが始まります。 ただし、今回のコードでは、アップロード先のURLはダミーになっていますので、実際にどこかにアップロードされることはありません。

以上、ブラウザで画像を縮小してサーバにアップロードする方法をまとめました。良い記事だと思っていただいた方は、以下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリック、Twitterのフォローをお願いします!

Koma Hirokazu 's Hacklog ―― Copyright © 2018 Koma Hirokazu