スマホの縦写真がWebで横倒しになる——EXIFを消す前に「向き」をピクセルに焼く
スマホで縦に撮った写真をWebに載せると横倒しになる。特にプライバシーのためEXIF(撮影情報・位置情報を含む)を削除したあとに起きやすい。原因は写真の向きがEXIFのOrientationフラグに入っていて、素朴に消すと回転指示だけ失われるから。消す前にexif_transposeで向きをピクセルに焼く。
スマホで縦に撮った写真をWebサイトに載せたら、90度横に倒れて表示される。しかも、プライバシーのために EXIF(撮影情報。位置情報もここに入る)を消す処理を入れてから、急にこうなった、という状況で踏むことが多い。原因と直し方をまとめる。
事象
スマホ(iPhone等)で縦向きに撮った写真をリサイズしてサイトに置くと、被写体が90度横倒しになる。横向きに撮った写真は正しく出る。縦のものだけ倒れる。
やっかいなのは、これが位置情報を消すために EXIF を削除する処理を入れた「あと」から起きることだ。それまでは正しく表示されていたのに、GPS漏れ対策で EXIF を落としたら縦写真が全部倒れた、というパターン。
原因
多くのスマホ(特にiPhone)のカメラは、撮影の向きに関わらずセンサーの向き(多くは横長)のままピクセルを保存し、「この写真は90度回転して表示せよ」という向きの情報を EXIF の Orientation というフラグに入れる。だから見た目は縦でも、ファイルの中のピクセルは横向きのまま、というズレが常にある。
現代のブラウザは <img> で表示するとき、この Orientation を読んで自動で回転してくれる(CSS の image-orientation が既定で from-image=EXIF の向きを尊重する)。ただしこれがブラウザ横断で確実に効くようになったのは2020年ごろ以降で、それより前は尊重しない時代もあった。さらに <img> の表示は回っても、Canvas に描いたり画像処理ライブラリで読むと、素のピクセルの向き(横)で出てくることがある。表示と処理でズレるわけだ。
ここで EXIF を素朴に全部消すと、位置情報(GPS)と一緒にこの Orientation フラグも消える。すると、ピクセルは横向きのまま、それを回転させる指示だけが失われる。結果、どこで見ても横倒しになる。
Orientation の値は、1 が回転不要、6 が「(ビューアが)時計回りに90度回して表示せよ」、8 が「反時計回りに90度」、という意味だ。iPhoneを普通に縦持ちで撮ると 6 が入っていることが多い。
解決策
EXIF を消す前に、Orientation を「ピクセルに適用」して物理的に回転させておく。回転させてしまえば、もう向きフラグは要らないので、そのあと安心して EXIF ごと消せる。
Python(Pillow)なら ImageOps.exif_transpose 一発だ。
from PIL import Image, ImageOps
img = Image.open("in.jpg")
img = ImageOps.exif_transpose(img) # Orientationを見てピクセルを回転(縦写真がここで正立する)
img = img.convert("RGB") # JPEG保存でモード不一致(RGBA/P)を避ける
# リサイズ等はこのあとで
img.save("out.jpg", "JPEG", quality=85) # exif= を渡さない=GPSごとEXIFが落ちる
Node.js の sharp なら、引数なしの .rotate() が EXIF の Orientation を見て自動回転してくれる。
await sharp("in.jpg").rotate().toFile("out.jpg"); // 引数なし=EXIFを見て自動回転
sharp は最近だと .autoOrient() という明示メソッドも用意されている(引数なし .rotate() は後方互換のためのエイリアス)。意図を明確にしたいなら .autoOrient() の方がいい。なお .rotate(90) のように角度を渡すと EXIF を見ずに固定で90度回すので、自動回転したいときは引数を渡してはいけない。
順番が肝心だ。「向きを焼く → リサイズ等 → EXIFなしで保存」。先に EXIF を落としてしまうと、もう向きの情報が無いので、後からピクセルを正しく回せない。
見分け方
- 「縦写真だけ横倒し」「EXIF/GPSを消す処理を入れてから起きた」の2点が揃ったら、まずこれ
- 元画像の Orientation を確認する。Pillow なら
Image.open(p).getexif().get(274)(274が Orientation のタグ番号)。1以外(特に6/8)が入っていて、出力側でNoneになっているなら、フラグを消しただけで回転していない証拠だ
位置情報を消すのは正しい。ただ、向きの情報まで巻き添えで消していないかを一度確認するといい。