iPhoneで撮った写真をアップロードする際の画像補正

iPhoneやデジカメで撮った写真をフォームのfile等からそのままアップロードすると、iPhoneでは正常に表示されるんですがPC等で見ると横になってたり逆さまになったりして表示されてしまいます。これはiPhone等で撮った写真にExifのOrientationで向きが記録されておりiPhoneではその補正をかけて表示してくれるのにPC等ではそこが無視されるから起こるようです。

今回はこのiPhone等で撮った写真の画像補正をブラウザ側のJavaScriptだけで行えるか試してみました。

JavaScript-Load-Imageで一撃で解決

解決方法を探しているとJavaScript-Load-Imageというライブラリに行き当たりました。

JS Client-Side Exif Orientation: Rotate and Mirror JPEG Images – stackoverflow

iPhone等で撮った問題の写真をPCから下記のデモサイトにアップロードしてみます。すると、通常だと縦横が正しく表示されないのにこのサイトだとうまく表示されるはずです。

JavaScript Load Image Demo

JavaScript-Load-Imageの使い方

使い方はREADME.mdを読むとよく分かります。

blueimp/JavaScript-Load-Image

今回のExifのOrientationを使った画像補正だけに限って言えば下記のようなコードで補正することができます(デモページの該当箇所)。JavaScript-Load-ImageがFile/Blob/Urlを受け取りExifがあるかどうかを調査しあればOrientationを取得、その後そのOrientationも含めたoptions引数をもとにloadImageを行うことで補正済みのCanvasを受け取る事ができます。

loadImage.parseMetaData(file, function (data) {
  if (data.exif) {
    options.orientation = data.exif.get('Orientation')
    displayExifData(data.exif)
  }
  displayImage(file, options)
});

function displayImage (file, options) {
  currentFile = file
  if (!loadImage(
      file,
      replaceResults,
      options
    )) {
    result.children().replaceWith(
      $('<span>Your browser does not support the URL or FileReader API.</span>')
    )
  }
}

Canvasで受け取るので結果のアップロードはCanvasから取得できるDataURIか、AjaxであればBlobをそのままアップロードするという形になりますね。

Dropzone.jsとの組み合わせ

普段ぼくは画像をアップロードする際はDropzone.jsをよく使うんですがこれと組み合わせてみました。画像をアップするとプレビュー表示されてボタンクリックでアップロードされるといったページです。ES6 + Webpackで書いてます。

import $ from 'jquery';
import Dropzone from 'dropzone';
import loadImage from 'blueimp-load-image';

const $dropzone = $('button#dropzone');
const $dropzonePreview = $('img#dropzone-preview');
const $avatar = $('[name=avatar]');

new Dropzone('a#dropzone', {
  url: '/dummy',
  autoProcessQueue: false,
  maxFiles: 1,
  acceptedFiles: 'image/*',
  dictDefaultMessage: 'アップロード',
  previewsContainer: document.querySelector('div#dropzone-preview'),
  addedfile: (file) => {
    loadImage.parseMetaData(file, (data) => {
      const options = {
        maxHeight: 240,
        maxWidth: 240,
        canvas: true,
        crop: true
      };
      if (data.exif) {
        options.orientation = data.exif.get('Orientation')
      }
      loadImage(file, (canvas) => {
        const datauri = canvas.toDataURL('image/png');
        $dropzonePreview.attr('src', datauri);
        $avatar.val(datauri);
      }, options);
    });
  }
});