Two weeks ago I received an interesting support ticket from a user of our ImageBox control, stating that when being used for .png files, the control was absolutely file but when used with .jpg files that had been rotated using Windows Explorer shell context menus, they were displayed in the original orientation.

As soon as I read the ticket I had a hunch what was going to be the ultimate cause, and was able to quickly reproduce the problem and then confirm my hunch.

Introducing exchangeable image file format

JEPG images support extension attributes via the Exchangeable image file format (Exif). These attributes can be used to provide additional information about an image, such as how and where it was captured. It can also store information such as the software used to manipulate the image, artist and copyright information, or camera model details. In other words, it's similar to how you can add tags to an MP3 file with album information, although usually this doesn't cause your MP3 file to play backwards!

The Exif specification is available from the CIPA website, although at 191 pages long it is a hefty read.

Introducing the orientation attribute

I mentioned that MP3 tags don't cause your MP3 files to play backwards (at least I'm not aware of any that do!), but Exif supports an orientation attribute that means the source image has one orientation, but should be displayed in another in order to appear correctly.

The tag supports one of the following 8 values

EXIF Orientation Value Row #0 is: Column #0 is:
1 Top Left side
2* Top Right side
3 Bottom Right side
4* Bottom Left side
5* Left side Top
6 Right side Top
7* Right side Bottom
8 Left side Bottom

The starred values also flip the image in addition to rotating it.

Source: ImpulseAdventure - JPEG / Exif Orientation and Rotation

An example of an uncommon orientation value
An example of an uncommon orientation value

Enter Windows Explorer

Windows Explorer has knowledge of the Exif orientation tag, and automatically rotates thumbnail previews so they appear correct - very helpful!

Windows Explorer showing correctly orientated images
Windows Explorer showing correctly orientated images

However, if you context click an image and make use of the Rotate Left and Rotate Right commands, Explorer doesn't truly rotate the images. Instead, for JEPG images, an appropriate orientation attribute is added (plus a large block of XML, it wouldn't be Microsoft if they weren't adding extra unwanted data in files). This is quite clever in a way, as it means it doesn't touch the original image data at all, but does mean that any application that doesn't understand Exif is going to display the image wrong.

Also somewhat annoyingly, when you view the properties of the image, the Details tab includes lots of information gleaned from Exif - but neglects to mention that a custom orientation is in use.

Enter .NET

The top image is using the Image object directly after loading, the bottom image is the result of manually processing the orientation attribute
The top image is using the Image object directly after loading, the bottom image is the result of manually processing the orientation attribute

When it comes to loading images in .NET, I usually use Image.FromFile or Image.FromStream if I'm just loading common formats. Unfortunately, these return the images "as-is", without doing any post processing. I said unfortunately, but actually this is probably a good thing - most likely you don't want .NET manipulating images for you.

Fixing the problem

The Image class does provide access to Exif properties, albeit in a very obsure way. Image.PropertyIdList returns an int array of defined extension values, and GetPropertyItem will return a PropertyItem instance describing an existing property. You can then use the Value property to get the attribute value, although this may require further conversion to use (e.g. convert raw bytes into a string).

Note: Calling GetPropertyItem will throw an ArgumentException if you request a property that doesn't exist. You should use PropertyIdList to check that the property exists first.

The following extension method (body code from this Stack Overflow answer) will take a source Image, check to see if the orientation attribute is present and if it is rotate/flip the image as appropriate and then remove the attribute.

csharp
public static void NormalizeOrientation(this Image image)
{
  if (Array.IndexOf(image.PropertyIdList, ExifOrientationTagId) > -1)
  {
    int orientation;

    orientation = image.GetPropertyItem(ExifOrientationTagId).Value[0];

    if (orientation >= 1 && orientation <= 8)
    {
      switch (orientation)
      {
        case 2:
          image.RotateFlip(RotateFlipType.RotateNoneFlipX);
          break;
        case 3:
          image.RotateFlip(RotateFlipType.Rotate180FlipNone);
          break;
        case 4:
          image.RotateFlip(RotateFlipType.Rotate180FlipX);
          break;
        case 5:
          image.RotateFlip(RotateFlipType.Rotate90FlipX);
          break;
        case 6:
          image.RotateFlip(RotateFlipType.Rotate90FlipNone);
          break;
        case 7:
          image.RotateFlip(RotateFlipType.Rotate270FlipX);
          break;
        case 8:
          image.RotateFlip(RotateFlipType.Rotate270FlipNone);
          break;
      }

      image.RemovePropertyItem(ExifOrientationTagId);
    }
  }
}

If your application deals with user submitted images, having something like this in your arsenal may be useful; I know I have several applications that now need this "fix".

Demonstration program

The linked source code download includes a demonstration program that features each of 8 supported orientation values and shows how they are displayed both before and after processing the orientation attribute.

Fixing ImageBox

At this time I'm still mulling over if this is something I should be handling within ImageBox, or if it is the responsibility of the program using the control. Given ImageBox typical use cases, I'm leaning towards the idea of adding a new option to automatically handle this but am currently undecided.

Credits

Update History

  • 2019-03-09 - First published
  • 2020-11-22 - Updated formatting

Like what you're reading? Perhaps you like to buy us a coffee?

Donate via Buy Me a Coffee

Donate via PayPal


Files


Comments

# Zeeshanef

Very nice point Greatly explained by Author! I received an image from client which was appearing fine in windows, but when loaded in my C# application so it was appearing rotated 180 degree. Now going to implement this.

Reply

# cb

WTH is ExifOrientationTagId???

Reply

# Richard Moss

I was going to make a brief and sarcastic comment to match the tone of yours, seems netiquette is a lost art these days. Then I realised that the constant was missing from the article and if you view it from the new blog, the download is a direct link unlike a source viewer in the previous blog. So I'll give the benefit of the doubt.

But maybe next time you could spend a little more time searching first.

https://www.cyotek.com/downloads/view/ExifOrientationDemo.zip/ImageExtensions.cs

public const int ExifOrientationTagId = 0x112;

Regards;
Richard Moss

Reply