oneimage

Fix EXIF orientation in C#

Why phone photos come out sideways in C#, what the EXIF orientation values mean, and how to load photos upright, read EXIF data and remove GPS in pure .NET.

Phone photos come out sideways in C# because cameras often store the pixels in the sensor’s layout and record how to turn them in the EXIF orientation tag. Browsers and photo viewers apply that tag, so the photo looks right everywhere until your code resizes or re-saves it without applying it. The fix is to apply the orientation to the pixels before you do anything else, and to write orientation 1 (“upright”) afterwards. OneImage does both by default when it loads a file. This page explains the tag, then shows how to load photos upright, read EXIF data, keep or remove it, and fix a folder of photos. Every sample was run against OneImage 1.6.0 on .NET 10.

TerminalNuGet
dotnet add package RecursiveAI.OneImage

What the EXIF orientation values mean

Orientation is EXIF tag 274 (0x0112). The Exif 2.32 standard (CIPA DC-X008-2019) defines eight values by where the stored image’s first row and first column belong when the picture is viewed, with 1 as the default. In terms of what a program has to do to show the stored pixels upright:

ValueStored image, relative to uprightTo show it upright
1Already uprightNothing
2Mirrored left to rightFlip horizontally
3Upside downRotate 180°
4Mirrored top to bottomFlip vertically
5Mirrored and turnedTranspose (swap rows and columns)
6Turned 90° counterclockwiseRotate 90° clockwise
7Mirrored and turnedTransverse (swap across the other diagonal)
8Turned 90° clockwiseRotate 90° counterclockwise

Values 5 to 8 swap width and height, so a photo stored as 4032×3024 with orientation 6 is shown 3024×4032. Web pages show it upright too: the CSS image-orientation property defaults to from-image, and MDN lists it as available across browsers since April 2020. That is why the bug shows up only after server-side processing.

Load photos upright

Program.cs.NET 10
using OneImage;

// Identify reads the headers: the size as stored, and the orientation tag.
ImageInfo stored = Image.Identify("phone.jpg");
Console.WriteLine($"stored:  {stored.Width}x{stored.Height}, orientation {stored.Metadata.Orientation}");

// Load applies the orientation, 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}");

photo.Save("phone-upright.jpg");

Our test photo is stored as 1200×800 with orientation 6. It prints:

Output
stored:  1200x800, orientation 6
loaded:  800x1200, orientation 1

Image.Load applies the orientation lazily, so Width, Height, crops and resizes all work in upright coordinates. Saving writes upright pixels and sets the orientation tag to 1. The file is then shown correctly by any viewer, whether or not it reads EXIF, and never turned twice. The EXIF block’s pixel dimensions are corrected to the saved size as well. Image.Identify is the exception: it reads only the headers and reports the size as stored, which is what you want when checking a file without decoding it.

We checked all eight values. We generated a 600×400 test picture, stored it eight ways with tags 1 to 8, and loaded each with OneImage. Every one came out 600×400 with orientation 1 and matched the original within JPEG noise.

Read EXIF data in C#

Program.cs.NET 10
using OneImage;

ExifData? exif = Image.Identify("phone.jpg").Metadata.ReadExif();

if (exif is null)
{
    Console.WriteLine("No EXIF block.");
    return;
}

Console.WriteLine($"Orientation: {exif.Orientation}");
Console.WriteLine($"Camera:      {exif.Make} {exif.Model}");
Console.WriteLine($"Taken:       {exif.CaptureTime:yyyy-MM-dd HH:mm:ss}");

if (exif.Gps is GpsPosition gps)
{
    Console.WriteLine($"GPS:         {gps.Latitude:F5}, {gps.Longitude:F5}");
}
Output
Orientation: 6
Camera:      ExampleCam X100
Taken:       2026-07-04 18:32:10
GPS:         40.44611, -79.98222

ReadExif() parses the common tags: orientation, make, model, software, capture time (DateTimeOriginal, falling back to the digitized and file times), the recorded pixel size, and a GPS position in signed decimal degrees, with altitude when present. It returns null when there is no EXIF block. Reading is bounds-checked, so a malformed block throws an ImageFormatException instead of crashing or hanging, and TryReadExif returns false instead. Calling it on Identify’s result reads only the headers. The raw EXIF, XMP, IPTC and ICC bytes are on Metadata if you need other tags.

Keep the stored layout

Some tools need the pixels exactly as stored, for example to compare files or to pass them on untouched. Turn auto-orientation off at load:

Program.cs.NET 10
using OneImage;

// Keep the stored pixel layout and the original tag.
Image stored = Image.Load("phone.jpg", new LoadOptions { AutoOrient = false });
Console.WriteLine($"{stored.Width}x{stored.Height}, orientation {stored.Metadata.Orientation}");

// Apply it later, when you choose to.
Image upright = stored.AutoOrient();
Console.WriteLine($"{upright.Width}x{upright.Height}, orientation {upright.Metadata.Orientation}");
Output
1200x800, orientation 6
800x1200, orientation 1

If the orientation comes from somewhere other than the file, such as a database column, Orient(6) applies a given value to the pixels and leaves the metadata alone.

Remove GPS and camera data before publishing

EXIF often carries the GPS position where a photo was taken. A plain load and save keeps the EXIF block, since metadata is preserved by default. Remove it when the image goes public:

Program.cs.NET 10
using OneImage;

// Upright pixels, and no EXIF, XMP or IPTC: no GPS position, no camera serial.
Image.Load("phone.jpg").StripMetadata().Save("phone-public.jpg");

// The same in one call, resized for the web.
Image.Thumbnail("phone.jpg", 600).Save("phone-600.jpg");

Both outputs are upright (800×1200 and 600×900) and have no EXIF block at all, so no GPS position, camera or capture time. StripMetadata() also drops the ICC color profile; StripMetadata(keepIcc: true) keeps it. Image.Thumbnail keeps the profile, converted to sRGB, removes EXIF, XMP and IPTC, and never enlarges. Our guide to resizing images in C# covers it in full, including an ASP.NET Core upload endpoint.

Fix the orientation of a folder of photos

To rewrite files on disk upright, re-encode only the ones that need it. JPEG re-encoding is lossy, so leave upright files alone:

Program.cs.NET 10
using OneImage;

var jpeg = new SaveOptions { EncoderOptions = new JpegEncoderOptions { Quality = 90 } };

foreach (string path in Directory.EnumerateFiles("photos", "*.jpg"))
{
    // Identify reads only the headers, so upright files cost almost nothing.
    if (Image.Identify(path).Metadata.Orientation == 1)
    {
        continue;
    }

    Image upright = Image.Load(path);
    upright.Save(Path.ChangeExtension(path, ".upright.jpg"), jpeg);
    Console.WriteLine($"{Path.GetFileName(path)}: rotated to {upright.Width}x{upright.Height}");
}

On a folder holding our eight test files and the phone photo, it skipped the upright file and wrote eight .upright.jpg files: seven at 600×400 and the phone photo at 800×1200. The EXIF block is kept, GPS included, with the orientation set to 1. Add StripMetadata() before Save if the files are going public.

EXIF orientation in ImageSharp and System.Drawing

Cropping is where orientation bites hardest, because coordinates from a browser refer to the upright picture. Our guide to cropping images in C# has an upload example that relies on it. For OneImage’s other formats and operations, see the overview.