When working on a project that involves users uploading images, there’s a lot of things to look out for. One unexpected one, is EXIF data. Firstly, if you are hosting images uploaded to you, it’s best to strip EXIF data to prevent the internet at large from being able to see where GPS tagged images were taken (see celebrity twitter stalking). However there is another – orientation. A number of cameras, including the iPhone, store orientation data in the EXIF, and leave the original image the “correct” orientation with regard to the camera sensors. And that approach makes a lot of sense, however some browsers on some devices seem to rotate images in accordance with their EXIF data while others do not. Obviously this can and will result in browsers displaying images in different orientations,which can be a big issue, especially if it’s an image going on a product.
Get up to speed on orientations of images here. Yes, it’s in another language, but it’s the original source for some of the best explanation images, and at the end of the day that’s what you need to know. What do the images mean and how’s the data stored. Ultimately, there’s an exif orientation tag with a value of 1 – 8. Four for each quarter rotation, and four of the same reflected along a vertical axis.
So what’s the best thing to do? I’m focused on python, so that’s the perspective I’ll take. Firstly, I think the best option is to always output an unequivocal “production” version of the image. So the first step is to apply the modification to the image. For completeness in my solution I included the mirrored options that you don’t normally get from cameras (I mean, who knows right?). But this makes the problem worse! PIL, Wand and ImageMagick all retain the EXIF data and as yet I can’t seem to be able to write to this in any of these packages, only read.
So, my code does the following:
Load image or inherit image object from loading function
Check EXIF parameters using built in functions of your image handler (for wand it's imageobj.metadata for instance)
Get the orientation value, which will either be an int or a length one string of an int
If this isn't 1, then you need to apply the rotations
Assuming any changes are applied, then it's advisable to edit or strip the exif
What’s the best way to strip the EXIF?
I borrowed some concepts from here, but the code was from 2004 and non-functional. So I rewrote the code to strip out app1 and set a generic app0 replacing the mmap function (which is different on windows and unix) with the StringIO function. The main differences were changing resize to truncate, and building a move function which operates in the same manner as mmap’s move.
def stringio_move(stringio, destination, source, count):
x = stringio.read(count)
I parse the image into the module as a blob (wand: imageobj.make_blob()) load that into a stringIO and get to work. At the end of the day, you are just stepping through the string looking for the SOI header and then the APP0 and APP1 markers. At the end of the function you write out the StringIO over the initial blob and go on your merry way.