Posted at

iPhoneからアップロードしたJPEG写真が横向きになる問題(EXIF, Orientation)

More than 3 years have passed since last update.

iPhoneで撮影した写真をWebサービスにアップロードすると、思わぬ方向に回転してしまうことがあります。

調べてみたところ、奥の深い問題であることがわかったのでこちらにまとめておきます。

iPhone上で確認した場合
uploadした後で確認した場合


まっすぐだニャ
何するニャ!


原因

iPhoneで撮影した写真には内部的には傾いた状態になっており、傾きはEXIFのOrientation情報として保存されます。ブラウザによってこのEXIFのOrientation情報を反映するか無視するかで対応がまちまちです。

Inpulse Adventureより、EXIFのOrientation情報の図

http://www.impulseadventure.com/


解決策

あなたが写真をアップロードする系のウェブサービスをつくっているとすると、主に二つの解決策があります。


  • ブラウザ側で解決 -> HTML 5 の canvas要素を使う



  • ウェブサービス側で傾きを戻す



    • Cloudinaryなどの外部サービス


    • ImageMagickなどのライブラリを自前サービスに組み込む




問題の詳細


EXIFのOrientation情報

iPhoneで撮影した写真には内部的には傾いた状態になっており、傾きはEXIFのOrientation情報として保存されます。EXIFとはJPEG形式の画像に付随する「メタデータ」のことで、こちらのWeblioにあるように写真に関する様々な情報を保存することができます。

EXIFプロパティ名
プロパティの値

Make
Apple

Model
iPhone 6 Plus

Orientation
right-top

XResolution
72

...
...

「原因」のところで転載した画像のように、EXIFのOrientation情報は画像の傾きを示していて、iPhoneで写真を撮影すると、iPhone本体を縦向きにして写真を撮影にしてもなぜかOrientatation = right-top、すなわち-90度傾いた状態で保存されます。

iPhone内部での保存

EXIFのOrientation情報がright-top (=6)

orient_flag6.gif


ブラウザごとにまちまちな対応

iPhoneの中だけで写真を見ているならこれで問題はないのですが、これをWebサービスにアップロードするとなると問題となります。ブラウザによってこのEXIFのOrientationをどう扱うかがまちまちで、一貫性がないのです。

あなたがまっすぐだと思ってアップロードして、わざわざアップロード後にiPhoneのSafariから確認しても、別の人がChrome見たら傾いているということが起こります。

iPhone「写真」アプリ
iPhoneのSafari


Chrome
Firefox


さらに困ったことにはiPhoneのサファリは<img src="http://...">のように指定するとEXIFのOrientationをもとに「傾きを戻す」のに対して、同じiPhoneのSafariでもCSSのbackground-urlを使って背景画像として読み込むとEXIFのOrientationを考慮せずright-topになっている画像は傾いたまま表示します。

おいSafari!

iPhone Safari
iPhone Safari

<img>タグ
CSS background-url


参考資料


解決策の詳細

ブラウザ毎に対応が違うということは、解決策としては:

ブラウザ側で



  • ブラウザの種類ごとに処理を変える (無理筋、対応が大変になりすぎる)

  • EXIFのOrientation情報をもとに各ブラウザ共通の手法で回転して表示する、

あるいは、ウェブサービス側で


  • 写真自体の回転を戻して、EXIFのOrientation情報も同時に消去する

という方法をとることができます。


ブラウザ側での解決

どうしてもウェブサービス側での解決を行おうとすると、作りこみが必要になってしまいますし、Firebaseなどを使っていてアプリのロジックは全てブラウザ側にありサーバ側では保存ぐらいしかしていないような場合はブラウザ側で対応するしかありません。

注意するべきは「問題の詳細」述べたように、iPhoneのSafariでは勝手に傾きを戻してしまうのでEXIFのOrientationを考慮して回転した後に、Safariがさらに「2重に回転」しないようにしなくてはなりません。

HTML 5の<canvas>要素なら「2重回転」の心配もなくブラウザ間の動作の違いもありません。

canvasへの描画はHTMLだけではできずJavaScriptを使う必要があります。自前でJavaScriptを書いてもいいのですが、上記のStackOverflowでも紹介されているJavascript-Image-Loadをつかうと便利でしょう。


Reactの場合

上記のJavaScript-Image-LoadはAPIはJQuery的な方法でDOMを操作する場合には相性がいいのですが、Reactとはあまり相性が良くありません、というかひと工夫必要です。

以下のようにcanvasを取り囲むReact Componentだけを以下のように切り出せば、「React的でない書き方」は一つのコンポーネントに閉じ込めることができます。


class MyCanvas extends React.Component {
...

injectCanvas(parent){
//JavaScript-Image-Load API
loadImage(
this.props.src,
function (canvas) {
parent.appendChild(canvas); //appendChild(), raw-DOM API
},
{
canvas: true,
orientation: Number(orientation)
}
);
};

shouldComponentUpdate(nextProps){
return this.props.src !== nextProps.src;
}

componentDidMount(){
//findDOMNode() bridges between React and raw-DOM
let parent = ReactDOM.findDOMNode(this.refs.parent);
this.injectCanvas(parent);
}

componentDidUpdate(){
let parent = ReactDOM.findDOMNode(this.refs.parent);
parent.removeChild(parent.firstChild);
this.injectCanvas(parent);
}
};

参考資料


余談 - CSSのtransform: rotate(90deg);ではいけないのか?

わざわざcanvasを使わなくてもCSSのtransform: rotate(90deg);でいいじゃん、と思うかもしれませんが、これはうまくいきません。というのもtransform: rotate(90deg);はいったんウェブページ内での配置およびwidthとheightが決まった後に回転を行うので、右図のように画像が正方形でない限りは妙な空白と隣の要素との重なりが起きてしまうのです。


ウェブサービス側での解決


自前のウェブサービスで対応

ウェブサービスのバックエンド・サーバ側でロジックを実装する余力があるなら、ブラウザ側での対応よりこちらのほうがきれいかもしれません。その場合ブラウザ側ではimgタグを使うだけでよく、ブラウザ側、すなわちHTMLはより簡潔でわかりやすくなります。

対応としては、写真自体をEXIFのOrientationをもとに適宜回転したのちウェブサービス側で保存・さらにEXIFのOrientation情報を削除します。

あたかも最初から傾いていない状態で撮った写真のみが保存されるので、ChromeであろうがSafariであろうが、動作の違いを気にする必要はありません。

これを自前のウェブサービスで実装しようとなると ImageMagickなどのライブラリを自前サービスに組み込むことが多いようです。


外部の写真用ウェブサービスで対応

あるいは写真の保存を外部サービスに頼ってもよいのならCloudinaryなどを使えばAPIを経由して画像の回転を行うことができます。こちらをうまく使えば、自前でImageMagickを組み込むことに比べて、実装の労力を格段に減らすことができそうです。

CloudinaryはAPIがURLによって提供されているので、下記のようなURLで、元画像を回転した状態での画像を用意することができます。

http://res.cloudinary.com/demo/image/upload/w_100/a_-20/sample.jpg


さいごに…

私自身もウェブサービス作っていて、Proof Of Conceptといって簡単に「ランディングページ」と呼ばれるウェブページを作れるものです。

その作成の最中にこの問題に気付きました。もし、Webサービスを作られている方の参考になれば幸いです。