EXIFミラー方向の画像アナライザが返す寸法の誤りを修正

rails/rails

Active Storageの画像アナライザにおいて、EXIFミラー方向(orientations 2, 4, 5, 7)を持つ画像のblob.metadataに記録される幅と高さが誤っていた問題を修正しました。rotated_image?が判定する向きのセットを、実際に90°または270°の回転を伴う4方向(5, 6, 7, 8)のみに絞ることで、縦横の寸法が正しく記録されるようになります。

背景

rotated_image? は、VipsアナライザとImageMagickアナライザの両方に存在するメソッドで、EXIFのOrientationタグに基づいて幅と高さを入れ替えるべきかを判定します。この判定に使われる向きのセットに誤りがあり、ミラー変換を含む画像の寸法が正反対に記録される問題が発生していました。

この問題の根源は #42005 まで遡ります。#41940 でミラー向きへの対応が未実装と報告されたことを受け、#42005 で TopRight(2)と BottomLeft(4)が追加されましたが、これら2つはフリップのみで90°回転を伴わないため、寸法の入れ替えは不要です。一方で、フリップと90°回転を組み合わせた LeftTop(5)と RightBottom(7)は追加されず、こちらは寸法の入れ替えが必要なのに行われないままでした。

結果として、EXIF orientation 2または4を持つ景色向き(landscape)の画像は縦向き(portrait)として、orientation 5または7を持つ縦向きの画像は横向きとしてblob.metadataに記録されるという、逆転した誤りが生じていました。

技術的な変更

rotated_image? が参照する向きのセットを、90°または270°の回転を実際に伴う4方向のみに修正しました。サムネイルやVariantの生成は別のパスを通るため影響を受けず、blob.metadata[:width]blob.metadata[:height] の記録のみが対象です。

各EXIFオリエンテーションと寸法スワップの関係は以下の通りです:

EXIF 変換内容 スワップ要否 修正前 修正後
2 Top-right 水平フリップ 不要 スワップ ✗ そのまま ✓
4 Bottom-left 垂直フリップ 不要 スワップ ✗ そのまま ✓
5 Left-top フリップ + 270° CW回転 必要 そのまま ✗ スワップ ✓
6 Right-top 90° CW回転 必要 スワップ ✓ スワップ ✓
7 Right-bottom フリップ + 90° CW回転 必要 そのまま ✗ スワップ ✓
8 Left-bottom 270° CW回転 必要 スワップ ✓ スワップ ✓

ImageMagickアナライザでは、文字列の配列から TopRightBottomLeft を除き、代わりに LeftTopRightBottom を追加しています。

変更前:

def rotated_image?(image)
  %w[ RightTop LeftBottom TopRight BottomLeft ].include?(image["%[orientation]"])
end

変更後:

def rotated_image?(image)
  %w[ LeftTop RightTop RightBottom LeftBottom ].include?(image["%[orientation]"])
end

Vipsアナライザでは、ROTATIONS 定数の正規表現を同様に更新しています。

変更前:

ROTATIONS = /Right-top|Left-bottom|Top-right|Bottom-left/

変更後:

ROTATIONS = /Left-top|Right-top|Right-bottom|Left-bottom/

テストとして racecar_mirrored.jpg(orientation 2: フリップのみ、4104×2736)と racecar_mirrored_rotated.jpg(orientation 5: フリップ+回転、2736×4104)の2つのフィクスチャ画像が追加され、VipsとImageMagickの両アナライザに対して寸法を検証するテストケースが追加されています。

設計判断

修正範囲をメタデータ記録のみに限定するアプローチが採られています。PR内で明示されている通り、サムネイルやVariant生成においてはVipsの auto_rot が8つ全てのEXIF方向を正しく処理するため、その部分に変更は加えていません。バグはblob.metadataへの記録処理に局所化されており、修正もその範囲に留まっています。

コード変更は各アナライザで1行ずつの修正であり、影響範囲は最小限です。VipsとImageMagickは向きの表記形式が異なる(Vipsはハイフン区切り Left-top、ImageMagickはキャメルケース LeftTop)ため、それぞれの記法に合わせた修正が行われています。

まとめ

この修正は、誤って追加された2方向と未追加だった2方向を入れ替えるだけのシンプルな変更でありながら、ミラーEXIF方向を持つ画像の寸法が逆転して記録されていたという実害のあるバグを解消します。画像のメタデータ精度に依存するアプリケーション(向きに基づいたレイアウト調整や、縦横比の条件分岐など)では、アップグレード後に既存のblob.metadataと新規記録値の間で差異が生じる可能性がある点に注意が必要です。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
1ea87940

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)、背景、技術的な変更、設計判断、まとめ(結論)の3部構成が明確に適用されており、非常に分かりやすい構成です。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックのシンタックスハイライトは正しく使用されていますが、本文中で言及されているPR番号(#42005)とIssue番号(#41940)がリンク化されていません。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

EXIF、Vips、ImageMagickといった用語を前提としており、専門知識を持つエンジニアという対象読者に適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落もトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られていて読みやすいです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

Diffで示されたコード変更(ImageMagickとVipsのアナライザ)を正確に引用し、ファイルパスも正しく記載されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「EXIF orientation」「フリップ」「スワップ」などの技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「rotated_image?」の挙動、影響範囲がメタデータ記録に限定される点、EXIFオリエンテーションごとのスワップ要否など、すべての説明が技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(問題の根本原因が#42005にあること、テストが追加されたこと等)は、PRのDescriptionやDiffで裏付けられており、ハルシネーションは見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#57290)、関連PR/Issue番号(#42005, #41940)、EXIF orientation番号、テストコード内の画像寸法など、すべての数値・固有名詞が正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRのタイトル(Fix image analyzer reporting wrong dimensions for mirrored EXIF orientations)の内容を正確に反映しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PRで提供された情報のみに基づいており、サポート状況やリリース予定といったPR外の知識の不適切な追加はありません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「#42005 まで遡ります」「追加されず...行われないままでした」など、過去の経緯に関する時間表現がPR情報と一致しており正確です。