Recursive AI

Fix EXIF orientation in C#

All oneimage recipes

Phones often store a photo’s pixels sideways and record how to turn them in the EXIF orientation tag. Viewers and browsers apply the tag, so the photo looks right until your code resizes or re-saves it without applying it. With OneImage, Image.Load applies the tag, so the image is upright from the start and Save writes it upright. The program below uses a public test photo stored sideways.

Terminal
dotnet new console -o ExifOrientation && cd ExifOrientation
dotnet add package RecursiveAI.OneImage --version 1.10.4
repo=https://raw.githubusercontent.com/recurser/exif-orientation-examples
curl -L -o phone.jpg $repo/master/Portrait_6.jpg

OneImage needs the .NET 10 SDK. Put this in Program.cs and run dotnet run:

C#
using OneImage;
 
// Identify reads the headers: the size as stored, and the tag.
ImageInfo stored = Image.Identify("phone.jpg");
Console.WriteLine($"stored:   {stored.Width}x{stored.Height}, "
                + $"orientation {stored.Metadata.Orientation}");
 
// Load applies the tag, so the image is upright from here on.
Image photo = Image.Load("phone.jpg");
Console.WriteLine($"loaded:   {photo.Width}x{photo.Height}, "
                + $"orientation {photo.Metadata.Orientation}");
 
// Save writes upright pixels and orientation 1, so no viewer turns it twice.
photo.Save("upright.jpg");
ImageInfo saved = Image.Identify("upright.jpg");
Console.WriteLine($"saved:    {saved.Width}x{saved.Height}, "
                + $"orientation {saved.Metadata.Orientation}");
Text
stored:   1800x1200, orientation 6
loaded:   1200x1800, orientation 1
saved:    1200x1800, orientation 1

Width, Height, crops and resizes all work in upright coordinates, which is also how a browser shows the photo, so crop rectangles picked in a web page line up. The rest of the EXIF block (camera, date, GPS) is kept. Image.Identify is the exception: it reads only the headers and reports the size as stored.

What the eight values mean

Orientation is EXIF tag 274 (0x0112), defined in the Exif standard (CIPA DC-X008):

ValueTo show the stored pixels upright
1Nothing
2Flip horizontally
3Rotate 180°
4Flip vertically
5Transpose (swap rows and columns)
6Rotate 90° clockwise
7Transverse (swap across the other diagonal)
8Rotate 90° counterclockwise

Values 5 to 8 swap width and height. OneImage handles all eight.

Keep the stored layout

To compare files or pass pixels on untouched, turn auto-orientation off and apply it later yourself:

C#
using OneImage;
 
Image stored = Image.Load("phone.jpg", new LoadOptions { AutoOrient = false });
Console.WriteLine($"{stored.Width}x{stored.Height}, "
                + $"orientation {stored.Metadata.Orientation}");
 
Image upright = stored.AutoOrient();
Console.WriteLine($"{upright.Width}x{upright.Height}, "
                + $"orientation {upright.Metadata.Orientation}");
Text
1800x1200, orientation 6
1200x1800, orientation 1

If the orientation comes from somewhere else, such as a database column, Orient(6) applies a given value.

Limits

  • Re-saving a JPEG re-encodes it. Fixing orientation on disk costs a little quality; skip files whose Image.Identify(path).Metadata.Orientation is already 1.
  • GPS stays. Upright doesn’t mean private: remove the metadata before publishing, or use Image.Thumbnail, which does both.
  • Coming from ImageSharp? Remove your AutoOrient() calls; Load already applies the tag. See the migration guide.

To read the other EXIF fields, see Read EXIF data. Metadata handling is in the guide.