Monthly archives "11月 2014"

画像を縮小してからサーバーへアップロードしよう

サーバーへ画像をアップロードしたい場合、クライアント-サーバー間の通信量軽減のため、クライアント側で画像を縮小してからサーバーへアップロードする方法を説明する。

前提条件は下記の通り。
・環境はherokuのMEANからCloudinaryに画像をアップロードしようで作った環境を流用する。
・画像の縮小にHTML5のcanvasを使う。

処理の流れは下記の通り。
・画像ファイル選択
・FileReaderで画像ファイルを読み込む
・ng-srcにFileReaderで読み込んだ画像ファイルのパスを設定する
・imgタグのonloadコールバックでcanvasを使って元の画像を縮小した画像を生成する
・angular-file-uploadで縮小した画像をサーバーへアップロード
※なお、画像の縮小表示にはCSSの縮小処理を使う。(CSSで縮小した画像は表示にしか使えないので、canvasで画像を縮小してサーバーにアップロードする)

手順は下記の通り。

CSSで縮小表示

参考文献:CSSだけで画像のリサイズを処理する(IE6に対応させて、しかもハイクオリティ)完全版
下記のCSSを作成する。

img {
	-ms-interpolation-mode: bicubic;
}

.image-large {
	max-width: 256px;
	max-height: 256px;
}
* html body .image-large {
	width: expression(this.width >= this.height ? "256px" : "auto");
	height: expression(this.width <= this.height ? "256px" : "auto");
}

 

htmlにimgタグを追加

参考文献:ngSrc | AngularJS 1.2 日本語リファレンス | js STUDIO
herokuのMEANからCloudinaryに画像をアップロードしようのHTMLを下記の通りimgタグを追加する。
ポイントは、
・classにはcssで定義したimage-largeを設定する
・srcはng-srcを使い、controllerでファイルパスを変更できるようにする
・オリジナルのディレクティブimg-onloadを設定する

<div ng-controller="MyCtrl">
  <input type="file" ng-file-select="onFileSelect($files)">
  <img class="image-large" ng-src="{{imgSrc}}" img-onload="ImageToBlob($img)"></img>
</div>

 

ディレクティブを追加

参考文献:
AngularJS 入門 – Directive
AngularJS – Image “onload” event
herokuのMEANからCloudinaryに画像をアップロードしようのjavascriptにディレクティブimg-onloadを追加する。
これによりimgタグのng-srcの値を変更した際のファイルロード完了後にimg-onloadで設定した関数が呼ばれる。
javascript

angular.module('myApp')
  .directive('imgOnload', [ '$parse', function ($parse) {
    return {
      link: function(scope, element, attrs) {
        element.bind("load" , function(e) {
          var func = $parse(attrs.imgOnload);
          func(scope, {$img:this});
        });
      }
    }
  }]);

 

コントローラーに縮小処理を追加

参考文献:
Get image data in JavaScript?
Canvasで描画した画像を送信してサーバに保存する
さらにコントローラーも下記の通りに変更する。

angular.module('myApp')
  .controller('MyCtrl', function($scope, $upload) {
    var imageType = '';
    $scope.onFileSelect = function($files) {
      for (var i = 0; i < $files.length; i++) {
        var file = $files[i];
        imageType = file.type;
        var fileReader = new FileReader();
        fileReader.onload = function(event) {
          $scope.imgSrc = event.target.result;
          if(!$scope.$$phase) {
            $scope.$apply();
          }
        };

        fileReader.readAsDataURL(file);
      }
    };

    var toBlob = function(image, imageType) {
      var canvas = document.createElement('canvas');
      if(canvas && canvas.getContext) {
        canvas.width = image.width;
        canvas.height = image.height;
        var ctx = canvas.getContext('2d');
        ctx.drawImage(image, 0, 0, image.width, image.height);
        var base64 = canvas.toDataURL(imageType);
        var bin = atob(base64.replace(/^.*,/, ''));
        var buffer = new Uint8Array(bin.length);
        for(var i = 0; i < bin.length; i++) {
            buffer[i] = bin.charCodeAt(i);
        }

        return new Blob([buffer.buffer], {type: imageType});
      }
      return null;
    };

    $scope.ImageToBlob = function($img) {
      var blob = toBlob($img, imageType);
      $upload.upload({
        url: 'api/myapi', // server側API例
        file: blob
      }).progress(function(evt) {
        console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
      }).success(function(data, status, headers, config) {
        console.log(data);
      });
    };
  });

0