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の使い方
使い方はREADME.mdを読むとよく分かります。
今回の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); }); } });