ブラウザ依存の画像回転の調べ方と対処法
<img>タグで表示しているjpeg画像がブラウザによって回転して表示され見た目が統一されない問題の原因と対処法についてまとめてみました。
事件
チームから、ブラウザによって回転して表示される画像がある!という相談がありました。 ざっくり、以下のような流れでベトナム&セールスがお祭り
ベトQC: 画像が回転している!
ベト開発:フロントエンドは何もしていない、 cloudfront のせいだ!
セールス🇯🇵:じゃ、じゃあfirebaseで回転処理しているか確認してください!!
いやいや、ブラウザ依存で動作が違うのですからそりゃ無理筋でしょう。。。ということで、おじさんが調べてみます。
原因
まず先に原因から。jpegと先に書いたのでピンときてる方、ハイ正解っ!
そうです、exif情報のorientで回転設定がされていました。
しかしブラウザごとに違う表示なのはなぜでしょう?
EXIFとはなんぞ?
exifとはjpegファイルのメタ情報のフォーマットのことで現在はversion2.3xがよく使われているようです。新しい3.0はUTF-8の文字コード対応で日本人には嬉しい仕様ですが対応している機器はまだ少なそうです。今回の問題ファイルも2.3系でした。
このexif (画像の中のメタ情報)にorientaionという回転・反転の設定を追加することが出来る仕様となっています。exif の仕様はゆるく画像を作成する側の自由なのでない場合もあります。
Orientationの実装仕様
exif の詳細の確認方法
問題の回転画像は、mac osの表示やmac/windows他のツールで問題の画像情報を確認をしてもOrientationがあるというツール、無いというツールが混ざりますます謎が深まりました。GUIツールだとこういうときは不便。
また、Orientationは画像ツールなどで見る方法もありますが、すべての情報が確認できるわけではなく、やはりダメ。ということでCLIツールの出番です。ダンプしてみるのは流石に辛いのでexif用の解析ツールを使います。
exiftool
mac ではexiftoolという便利ツールが利用できます。これ最強。
インストールは
brew install exiftool
exifの表示はこんな (下のはサンプルで問題の画像のモノではありません、業務上の制約がありますので。。。)
exiftool -a -G1 -s <filename>
// e.g.
[ExifTool] ExifToolVersion : 12.70
[System] FileName : image2.jpeg
[System] Directory : .
[System] FileSize : 44 kB
[System] FileModifyDate : 2024:01:23 10:55:46+07:00
[System] FileAccessDate : 2024:01:23 11:00:59+07:00
[System] FileInodeChangeDate : 2024:01:23 10:55:46+07:00
[System] FilePermissions : -rw-r--r--
[File] FileType : JPEG
[File] FileTypeExtension : jpg
[File] MIMEType : image/jpeg
[File] ExifByteOrder : Little-endian (Intel, II)
[File] ImageWidth : 640
[File] ImageHeight : 533
[File] EncodingProcess : Progressive DCT, Huffman coding
[File] BitsPerSample : 8
[File] ColorComponents : 3
[File] YCbCrSubSampling : YCbCr4:4:4 (1 1)
[JFIF] JFIFVersion : 1.01
[JFIF] ResolutionUnit : inches
[JFIF] XResolution : 72
[JFIF] YResolution : 72
[IFD0] Compression : JPEG (old-style)
[IFD0] Orientation : Rotate 90 CW
[IFD0] XResolution : 72
[IFD0] YResolution : 72
[IFD0] ResolutionUnit : inches
[IFD0] Software : GIMP 2.10.30
[IFD0] ModifyDate : 2022:10:20 22:06:23
[ExifIFD] ColorSpace : sRGB
....
...
[ICC-chrm] ChromaticityChannels : 3
[ICC-chrm] ChromaticityColorant : Unknown
[ICC-chrm] ChromaticityChannel1 : 0.64 0.33002
[ICC-chrm] ChromaticityChannel2 : 0.3 0.60001
[ICC-chrm] ChromaticityChannel3 : 0.15001 0.06
[Composite] ImageSize : 640x533
[Composite] Megapixels : 0.341
あれやこれや、問題の画像をひっくり返して調べてみましたがOrientationはあるし、ツールによって認識したり、しなかたったりで、ブラウザ同様に調べるほど謎な結果になってしまいました。うーん、なんなのかな? もっとよく調べよう、俺。
真犯人を逮捕! ついに完全解決
ベトナムコーヒーを飲み、集中力をアップしてexiftool の出力をファイルに落としたものをvim でじーーーっとよく見ます。(てか検索しますけど)…と程なく微妙な差を発見。
トラブルのもとになっていたデータは、上記のexif のなかの
[IFD0] Orientation : Rotate 90 CW
ではなく IFD1 、拡張されたアトリビュートチャンクのなかにオリエンテーションん設定が入っていました。具体的は
[IFD1] Orientation : Rotate 90 CW
このため、mac OSのツールやブラウザによってこの値が拾えず、回転されたりされなかったりしていたのでした….
って、皆で苦笑いですがそもそも、この画像は誰が作ったのか???犯人は特捜部の調べによりますと、SONY Xperiaシリーズで撮影した写真でありました。うーん、SONYさんたのむよー。
当初調べたときには全然出てこなかったのに、原因がわかってから調べると簡単に見つかりました、google search。こちら↓に詳しい事が書かれていました。 IFD0 は通常、IFD1はサムネイル画像、なのだそうです。へ〜、知らなかった。
でもさー、その画像がサムネイルかどうかって主観ですよね?
。。。まぁ、いいですけど。
ブラウザ依存じゃない動作はどうするのか
1.CSSで動作を止めるにはCSSで指定する方法があります。
ただしCORS要件があるため、同じドメインの画像でないとCSS設定が有効になりません。違うドメインの場合はheaderの設定が追加位で必要となります。
img {
image-orientation: none;
}
こちらのCSSについては↓が詳しいです
CSSで制御を止めたうえで、以下のJSライブラリを使って制御も可能なようです。(未テスト)
※ただしこのCSS設定はCORS制限があるため、画像は同一ドメインか画像ドメインをヘッダでCORSエラーにならないように設定する必要があります。
Access-Control-Allow-Origin: 画像のドメイン
# apache の例
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
<FilesMatch "\.(avifs?|bmp|cur|gif|ico|jpe?g|jxl|a?png|svgz?|webp)$">
SetEnvIf Origin ":" IS_CORS
Header set Access-Control-Allow-Origin "*" env=IS_CORS
</FilesMatch>
</IfModule>
</IfModule>
exif-js
画像またはファイル入力要素から、ブラウザー内の画像に使用できます。EXIF メタデータと IPTC メタデータの両方が取得されます。このパッケージは、AMD または CommonJS 環境でも使用できます。(EXIF 標準 v2.2)
window.onload=getExif;
function getExif() {
var img1 = document.getElementById("img1");
EXIF.getData(img1, function() {
var make = EXIF.getTag(this, "Make");
var model = EXIF.getTag(this, "Model");
var makeAndModel = document.getElementById("makeAndModel");
makeAndModel.innerHTML = `${make} ${model}`;
});
var img2 = document.getElementById("img2");
EXIF.getData(img2, function() {
var allMetaData = EXIF.getAllTags(this);
var allMetaDataSpan = document.getElementById("allMetaDataSpan");
allMetaDataSpan.innerHTML = JSON.stringify(allMetaData, null, "\t");
});
}
<img src="image1.jpg" id="img1" />
<pre>Make and model: <span id="makeAndModel"></span></pre>
<br/>
<img src="image2.jpg" id="img2" />
<pre id="allMetaDataSpan"></pre>
<br/>
まとめ&対処法
内部ではちょっとした違いでも画像は表示されるとインパクトのある見た目になってしまうので、ちょっと慌てましたがまとめると以下のようになります。
→ exif のチャンク2(IFD1) にOrientation が設定されているとSafari / Chromeで動作に差が出る(safariで回転)
ブラウザ差をなくすには
a ) 事前に画像を修正
b) CSS で exif Orientation 処理を抑制
→ CORS の制限あり (画像URLは同じドメイン、もしくは画像ドメインを header で
→ 回転をさせたい場合は独自でexif 解析、回転処理をjs で行う必要
付録
ちなみに、cssはCORS要件があるくせに、CSSでCORSを突破する方法があるそうです。css のなかにJSONを仕込みロード、JSで解析して読み込むという手法なようです。
CORSってなによ?
Cross-Origin Resource Sharingの略で、日本語ではオリジン間リソース共有。
Webは同一オリジンポリシー(SOP / Same Origin Policy) といって、自分のドメインのファイルだけを読める制約があり安全に動作するようになっています。CORSは自分のドメインの外にあるファイルにアクセスすることを指します。(これ自体に善悪はありませんが、詐欺などで悪用するための手段となります。)
@prathkumさんの解説漫画