しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

System.Drawing が画像の方向を無視するので対応した話

最近は仕事で画像をいろいろと弄ることをやっているのですが、スマートフォンで撮影された画像を System.Drawing で処理させると向きがおかしくなるんですよね。原因は Exif の orientation 情報を正しく処理していないからなんですが、これがまあまあ面倒なのでメモしておきます。

Exif では画像の向きを持つタグは 0x0112 という ID が割り振られています。そして値は仕様書を読むと unsighed short みたいなので、そのあたり考慮しないといけない感じです。

http://www.media.mit.edu/pia/Research/deepview/exif.html

とまあ、色々と検索していたら値は 1-8 取ることが分かったので、適当に enum を定義しておきます。

public enum ExifOrientation : ushort
{
    TopLeft = 1,
    TopRight = 2,
    BottomRight = 3,
    BottomLeft = 4,
    LeftTop = 5,
    RightTop = 6,
    RightBottom = 7,
    LeftBottom = 8
}

ぶっちゃけ、これだけ見てもどうなってるのかさっぱりわかりませんね。

実際に orientation の値からどのように回転させるかですが、酢酸先生が既にまとめてくれていたので、これを元に RotateFlipType にマッピングさせるだけのコードを書きました。

JPEGの回転角度を取得する - 酢ろぐ!

一応 System.Drawing.Bitmap クラスでは PropertyItems という配列に Exif とかのメタデータ周りが格納されるので、それを使って向きを保持するタグを取得できます。GetPropertyItem メソッドに ID を指定すると簡単に取れますが、こいつはタグが見つからなかった時に例外を投げるので LINQ 使ってフィルタリングします。

var bitmap = new Bitmap(@"C:\Users\shibayan\Documents\photo.jpg");

// 0x0112 = Orientation を保持するタグ ID
var property = bitmap.PropertyItems.FirstOrDefault(p => p.Id == 0x0112);

if (property != null)
{
    var rotation = RotateFlipType.RotateNoneFlipNone;

    var orientation = (ExifOrientation)BitConverter.ToUInt16(property.Value, 0);

    // Exif 情報に従って画像を回転させる
    switch (orientation)
    {
        case ExifOrientation.TopLeft:
            break;
        case ExifOrientation.TopRight:
            rotation = RotateFlipType.RotateNoneFlipX;
            break;
        case ExifOrientation.BottomRight:
            rotation = RotateFlipType.Rotate180FlipNone;
            break;
        case ExifOrientation.BottomLeft:
            rotation = RotateFlipType.RotateNoneFlipY;
            break;
        case ExifOrientation.LeftTop:
            rotation = RotateFlipType.Rotate270FlipY;
            break;
        case ExifOrientation.RightTop:
            rotation = RotateFlipType.Rotate90FlipNone;
            break;
        case ExifOrientation.RightBottom:
            rotation = RotateFlipType.Rotate90FlipY;
            break;
        case ExifOrientation.LeftBottom:
            rotation = RotateFlipType.Rotate270FlipNone;
            break;
    }

    bitmap.RotateFlip(rotation);

    property.Value = BitConverter.GetBytes((ushort)ExifOrientation.TopLeft);
    bitmap.SetPropertyItem(property);
}

やってることは極めてシンプルで、Exif の orientation 情報に従って RotateFlip メソッドを使い画像を回転させます。そして、元々の Exif が残っていると書き出した時にさらに回転してしまうので、TopLeft で上書きをしておきます。

これで正しく向きが処理された Bitmap を取得できるので、書き出すなり描画したりと色々と処理が出来るようになります。