Coping With EXIF Rotation Problems

At Knowledge Architecture, we may not operate at the scale of Facebook or Flickr, but Synthesis sees enough image uploads that we’ve run into some pretty strange image rotation problems now and then. I’ve spend a lot of time with the issue, and while I’m not naive enough to declare victory over something as devilish as EXIF rotation gremlins, we’ve got an update rolling out this week that will at least avoid the species we’ve seen so far.

First, a little background, and we’ll imagine a portrait-orientation photo of the Sutro Tower as our example image. Digital images are stored in a given rotation, which generally corresponds to the default rotation of the image sensor—so our Sutro Tower photo is probably sideways on disk. If we open the image in any half-decent viewer, though, the tower will be right-side up because most digital cameras annotate image files using the EXIF Orientation tag. That’s used by most image-viewing things to recognize situations where image may be stored on disk like this, but when displayed it should be (say) flipped, and rotated 90° to look like that.

It’s gotten better, but support for that Orientation value isn’t perfect. So when someone uploads an image to Synthesis, we look for the Orientation EXIF field, rotate the image data itself so that it’s stored the way it should be displayed, and then we remove the Orientation field, because its instruction to rotate the image on display is no longer needed.

The problem we’ve run into is that some image editors apparently don’t update EXIF data properly; they rotate the image data itself (so the Sutro Tower is right-side up), but leave the Orientation flag in place. So our uploading process would dutifullly rotate the image one more time, and the tower would end up pointing sideways. It was a frustrating situation.

Luckily, those EXIF-unaware image editors seem to leave all the other EXIF data unchanged, too. So what our solution does is compare the so-called “EXIF Width” and “EXIF Height” field values to the width and height of the image data on disk. If it looks like the image has already been rotated, we simply remove the Orientation field.

So here’s the important part of the code. In short, we:

  • calculate the ratio of actual image width to height, and the ratio of EXIF-reported height to width,
  • round them both to one decimal place (to account for resized images subtly changing that ratio),
  • and check to see if one equals the other. If so, the image has been rotated already.

This is C# but it’s mostly math so it should be easy enough to port to whatever language you’re using. Not shown is fetching the orientation, exifWidth, and exifHeight values; they’re EXIF fields 0x0112, 0xa002 and 0xa003, respectively. I found this list of EXIF tags very helpful during this whole process.

// Useless data check (this check doesn't work on squares)
if (exifWidth > 0 && exifHeight > 0 && exifWidth != exifHeight)
    // To see if the bits were rotated, we calculate a height/width ratio, because 
    // we might be dealing with a resized image. We round, becuase resizing might 
    // slightly change this ratio
    decimal actualRatio = Math.Round(
		((decimal)img.Height / (decimal)img.Width)
		, 1); // height / width
    decimal exifRatio = Math.Round(
		((decimal)exifWidth / (decimal)exifHeight)
		, 1);  // width / height

    if ( orientation >= 5 && actualRatio == exifRatio )
        // SO, if the orientation is rotate90 or rotate270 (with or without flip),
        // and the image seems to have already been rotated 90 or 270 (the width 
	// is now the height), we're going to assume that some bad software rotated 
	// the pixels and never removed the orientation flag. In such a case, we'll
	// trust the lousy software's rotation, and remove the flag.

        orientationPropItem.Value = new byte[1] { 1 };

        // We return true because in our implementation, we just need to know if the 
	// image file we're storing has changed at all, whether that means rotation 
	// or just removing an EXIF value.
        return true;

Leave a comment

Your email address will not be published. Required fields are marked *