ボクココ

個人開発に関するテックブログ

画像の縦横比を合わせてアップロードするまで

ども、@kimihom です。

前回の記事で、画像ファイルのドラッグ&ドロップの手法を紹介した。これで File オブジェクトを取得することができる。

www.bokukoko.info

さて、今回はその持ってきた画像の縦横比を合わせてS3にアップロードしてみよう。

画像の縦横比の重要性

まぁ説明なんていらないと思うけど、Webの画像はできるだけ縦横比を合わせた方が扱いやすいことが多い。例えばアイコンであれば正方形の形が望まれるし、記事であれば長方形っぽい写真の方がいい。縦横比をしっかりと合わせないと統一感のないWebサイトとなってしまう。(縦横比を合わせなくても上手くやった Pinterest はやっぱすごい)

そんなこんなで縦横比を合わせるわけだが、それをサーバー側で処理するとなると、どの部分を切り取ればいいかユーザーが指定できない。ページ上(JavaScript)でどの部分を切り取ればいいのかを指定できるようにしたいところだ。

縦横比を合わせる処理(クロップ)を実現しよう

f:id:cevid_cpp:20160326203802p:plain

今回利用したのは Cropper っていう JS ライブラリ。かなりよく出来てた。具体的にどう使うのってのはGithubのドキュメントを読めばわかるけど一応紹介。

<div>
  <img id="image" src="picture.jpg">
</div>

$('#image').cropper({
  aspectRatio: 16 / 9,
  crop: function(e) {
    console.log(e);
  }
});

こんな感じ。 cropper を任意のタイミングで呼べば、divのエリアがクロップできるようになる。んでここで前回のファイルアップロード方式の際に問題が起きる。画像 src に URL を指定できないのだ。でもこれも Base64 エンコードを使えばうまくいく。Base64エンコードってのはまぁ基本的なことなんだけども、画像のようなバイナリを文字列で表現しちゃおうってもの。生画像より容量は大きくなるけど、文字列で表現できるのがGood。

では、早速 クロップした画像を取得できるようにし、S3にアップロードする手順に移っていこう。

クロップデータの取得

クロップデータは getCroppedCanvas を呼べば取得できる。今回はたまたま jcanvas を使っていたので、それを使ってのサンプル。jcanvas を使わないで実現する方法は調べていない。cropImage メソッドの引数にある file はファイルのドラッグ&ドロップでで取得した Fileオブジェクトだ。ちょっと長いけどこんな感じ。だいぶ自分の環境で動いている元のコードを修正しているので、動作保証はしない。

  var cropImage = function(file) {
    var fr = new FileReader();
    fr.onload = function(evt) {
      // evt.target.result に Base64エンコードされた画像が入る
      $(".crop-image").attr("src", evt.target.result);
      $('.crop-image').cropper({
        aspectRatio: 1,
        minContainerWidth: 360,
        minContainerHeight: 230,
        crop: function(e) {
          var base64img = $($('.crop-image').cropper('getCroppedCanvas')).getCanvasImage('png');
          uploadtoS3(base64img);
        }
      });
    };
    fr.readAsDataURL(file); // base64へ変換
  };

  var uploadtoS3 = function(base64img) {
    var s3 = AWS. // AWS SDK から S3 オブジェクト取得
    var filename = "";
    s3.putObject({
      'Key': filename,
      'ContentType': "image/png",
      'Body': toBlob(base64img),
      'ACL': "public-read"
    }, function(err, data){
      // after upload
    });
  };

  // base64 -> blob
  var toBlob = function(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);
    }
    return new Blob([buffer.buffer], { type: 'image/png' });
  };

結構めんどいんだけど、ファイルオブジェクトのBlobを一旦 Base64 に変換して Cropper へ渡し、その結果(Base64)をまた Blob に戻すってやり方をやっている。 S3 へ Base64形式でアップロードしても、正しく画像を表示してくれなかったので、こんな対応になった。

ちなみに画像の圧縮に関してはどうするのか意見が分かれるところだ。一番手っ取り早いのは File オブジェクトからファイルサイズが取得できるので、例えば 3M バイト以上のファイルをアップロードできないようにすることがある。より丁寧にやるにはどんな画像でもアップロードできるようにして、サーバー側で圧縮してあげる方法もある。ただそれでもファイルサイズの限度を定めないと、とんでもないファイルをアップロードされる可能性もあるので注意しよう。私なら AWS Lambda を利用して圧縮する方法を選ぶ。これはググって調べてみてほしい。

終わりに

Web上の画像処理は割と難しいように見えるが、うまくライブラリを駆使するともろもろうまくいく。なかなかこうしたテクニックは体系的に解説されていないので、本記事が参考になれば幸いだ。

より良い画像を扱うWebサービスが増えることを祈る。