EXIFミラー方向の画像アナライザが返す寸法の誤りを修正
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アナライザでは、文字列の配列から TopRight と BottomLeft を除き、代わりに LeftTop と RightBottom を追加しています。
変更前:
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と新規記録値の間で差異が生じる可能性がある点に注意が必要です。