If you're app developers and/frequently work with images coming from mobile devices, you most likely already know about the photo orientation issue: you can see the picture properly oriented from the smartphone or tablet, yet you get it rotated by 90, 180 or 270 degrees when you open it with your desktop viewer, library or component.
The issue
The reason for that is quite trivial: whenever a photo is taken, the device saves the current accelerometer orientation into a corresponding Exif tag within the image metadata, so the viewer apps can retrieve it and rotate the photo accordingly. Problem is, this handy auto-correction feature is often not present in most softwares, libraries and components for desktop computers, meaning that they will basically show you the image as it has been taken, which is often very wrong from your (or your customer) point of view.
The reasons for such behaviour are very simple: almost any image viewing/editing software made for desktop computers is made to support all types of images, not just photos, so they won't fetch any Exif tag to modify your viewing/editing experience unless you tell them to. On top of that, most of them aren't accustomed to digital images coming from accelerometer-enabled devices such as smartphones and tablets, so they won't bother checking for the Orientation tag whatsoever.
The same scenario applies to image SDKs, components tools and libraries, including native solutions such as GDI+ e DirectX: they won't automatically use any Exif tag info to correct anything, including the image orientation. For example, when we do something like the following:
1 |
var bmp = new Bitmap("/path/to/image.jpg"); |
we can be sure that the resulting
Bitmapwill have the exact same orientation issues you can experience with any desktop PC image viewer.
The solution
Luckily enough, the fix for this issue is fairly simple: we only need to write a simple C# helper method that can fetch the Orientation Exif tag (id 0x0122), the one that stores the device orientation status, and rotate the image accordingly. The required code can be summarized into the following helper class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Linq; using Ryadel.Components.IO; using Ryadel.Components.Util; using System.Runtime.InteropServices; using Ryadel.Components.Media.ImageManipulation; using System.Drawing.Text; namespace Ryadel.Components.Media { /// <summary> /// Helper class for manipulating images. /// </summary> public static class ImageHelper { /// <summary> /// Rotate the given image file according to Exif Orientation data /// </summary> /// <param name="sourceFilePath">path of source file</param> /// <param name="targetFilePath">path of target file</param> /// <param name="targetFormat">target format</param> /// <param name="updateExifData">set it to TRUE to update image Exif data after rotation (default is TRUE)</param> /// <returns>The RotateFlipType value corresponding to the applied rotation. If no rotation occurred, RotateFlipType.RotateNoneFlipNone will be returned.</returns> public static RotateFlipType RotateImageByExifOrientationData(string sourceFilePath, string targetFilePath, ImageFormat targetFormat, bool updateExifData = true) { // Rotate the image according to EXIF data var bmp = new Bitmap(sourceFilePath); RotateFlipType fType = RotateImageByExifOrientationData(bmp, updateExifData); if (fType != RotateFlipType.RotateNoneFlipNone) { bmp.Save(targetFilePath, targetFormat); } return fType; } /// <summary> /// Rotate the given bitmap according to Exif Orientation data /// </summary> /// <param name="img">source image</param> /// <param name="updateExifData">set it to TRUE to update image Exif data after rotation (default is TRUE)</param> /// <returns>The RotateFlipType value corresponding to the applied rotation. If no rotation occurred, RotateFlipType.RotateNoneFlipNone will be returned.</returns> public static RotateFlipType RotateImageByExifOrientationData(Image img, bool updateExifData = true) { int orientationId = 0x0112; var fType = RotateFlipType.RotateNoneFlipNone; if (img.PropertyIdList.Contains(orientationId)) { var pItem = img.GetPropertyItem(orientationId); fType = GetRotateFlipTypeByExifOrientationData(pItem.Value[0]); if (fType != RotateFlipType.RotateNoneFlipNone) { img.RotateFlip(fType); if (updateExifData) img.RemovePropertyItem(orientationId); // Remove Exif orientation tag } } return fType; } /// <summary> /// Return the proper System.Drawing.RotateFlipType according to given orientation EXIF metadata /// </summary> /// <param name="orientation">Exif "Orientation"</param> /// <returns>the corresponding System.Drawing.RotateFlipType enum value</returns> public static RotateFlipType GetRotateFlipTypeByExifOrientationData(int orientation) { switch (orientation) { case 1: default: return RotateFlipType.RotateNoneFlipNone; case 2: return RotateFlipType.RotateNoneFlipX; case 3: return RotateFlipType.Rotate180FlipNone; case 4: return RotateFlipType.Rotate180FlipX; case 5: return RotateFlipType.Rotate90FlipX; case 6: return RotateFlipType.Rotate90FlipNone; case 7: return RotateFlipType.Rotate270FlipX; case 8: return RotateFlipType.Rotate270FlipNone; } } } } |
We can see that the helper method includes two different overloads: the former one to support file paths, the latter accepting an existing System.Drawing.Image object.
It's also worth noticing how both methods optionally allow the developer to remove the Orientation Exif tag if the image gets rotated, which is usually a good thing to do: if we fix the issue by physically rotating the image we should also remove any potentially misleading info that might trigger the auto-adjustement feature of any orientation-aware viewer and/or app.
That's all for now: happy orientation!