I took the picture with iPhone 8+, when I posted it in my blog, it is rotated by 180 degree by default.

But if I view the post in Safari mobile, check the original picture in my phone or in Preview, or even open the image in a new Chrome tab, it looks perfectly normal, it just doesn’t work in a blog post in Chrome / Firefox / Safari (desktop).

The side by side comparison of different image rotations.

Root Cause

After some investigation, I found that it’s all because the information hidden in EXIF. Let’s look at the EXIF data of the previous example, the Rotation field is actual noted as “rotated by 180 degree”, which means the photo itself is upside down.

An example of image EXIF data with orientation as 180 rotate.

But why it looks normal in my phone or in Preview?

I guess it’s because the image viewer Preview supports EXIF, it respects EXIF information and shows images in a correct way (rotate them if needed). On the contrary, popular web browsers (e.g. Chrome, Firefox, Safari) don’t support this.

After some more googling, this issue has been discussed back in 2010, here’s the bug filed for Chromium, there is also a plan to support EXIF rotation in <img> tag by adding a property image-orientation, unfortunately, it’s only supported in Firefox at the moment.

How to fix this?

There’s not much I can do to add the support to Chromium, there must be a reason it takes so long to support EXIF. However, since I have the full control of the image, I can rotate the image beforehand according to its EXIF data, so I don’t need to rely on web browsers to correctly show them.

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
filepath = '[FILE_PATH]'
try:
# Read image and its EXIF data.
image = Image.open(filepath)
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break
exif = dict(image._getexif().items())

# Check if it needs to be rotated, and how.
need_rotate = True
if exif[orientation] == 3:
image = image.rotate(180, expand = True)
elif exif[orientation] == 6:
image = image.rotate(270, expand = True)
elif exif[orientation] == 8:
image = image.rotate(90, expand = True)
else:
need_rotate = False

if need_rotate:
image.save(filepath)
image.close()

except (AttributeError, KeyError, IndexError, IOError):
# Handle the exceptions if needed.
print "failed to ..."
pass
  • This is only a sample Python script used as a POC (prove-of-concept), please clean it up if you want to use it in Production.
  • The script above will erase image EXIF data, so please use with caution, back up the image first if needed.
  • The meaning of orientation value is in this table:
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