Auto rotate Dexterity images based on EXIF info
Although I’ve been using Dexterity by default in the past years, I still need AT types for auto transforming images based on EXIF information.
When uploading an image to Plone using plone.app.contenttypes it is not automatically rotated like the user would expect. So when uploading a photo made sideways, the user views the photo in windows or mac just fine, almost every program rotates images automatically. But in Plone it suddenly is turned upside down, turned to the left, or right.
In AT there was some really cool stuff to read out your EXIF information and automatically transform images in your plone site.
To do this in Dexterity we can use the code below and trigger it when modified or created event is fired!
The simplified code from:
[https://github.com/plone/Products.ATContentTypes/blob/master/Products/ATContentTypes/lib/imagetransform.py][1]
FLIP_LEFT_RIGHT = 0
FLIP_TOP_BOTTOM = 1
ROTATE_90 = 2
ROTATE_180 = 3
ROTATE_270 = 4
TRANSPOSE_MAP = {
FLIP_LEFT_RIGHT: (u'Flip around vertical axis'),
FLIP_TOP_BOTTOM: (u'Flip around horizontal axis'),
ROTATE_270: (u'Rotate 90 clockwise'),
ROTATE_180: (u'Rotate 180'),
ROTATE_90: (u'Rotate 90 counterclockwise'),
}
AUTO_ROTATE_MAP = {
0: None,
90: ROTATE_270,
180: ROTATE_180,
270: ROTATE_90,
}
ROTATION = {'Horizontal (normal)': 1,
'Mirrored horizontal': 2,
'Rotated 180': 3,
'Mirrored vertical': 4,
'Mirrored horizontal then rotated 90 CCW': 5,
'Rotated 90 CW': 6,
'Mirrored horizontal then rotated 90 CW': 7,
'Rotated 90 CCW': 8}
def exifRotation(self, event):
if self.portal_type == 'Image':
if self.image:
mirror, rotation = getEXIFOrientation(self)
transform = None
if rotation:
transform = AUTO_ROTATE_MAP.get(rotation, None)
if transform is not None:
transformImage(self, transform)
def getEXIF(self):
exif_data = exif.process_file(StringIO(self.image.data), debug=False)
# remove some unwanted elements lik thumb nails
for key in ('JPEGThumbnail', 'TIFFThumbnail', 'MakerNote JPEGThumbnail'):
if key in exif_data:
del exif_data[key]
return exif_data
def getEXIFOrientation(self):
"""Get the rotation and mirror orientation from the EXIF data
Some cameras are storing the informations about rotation and mirror in
the exif data. It can be used for autorotation.
"""
exif = getEXIF(self)
mirror = 0
rotation = 0
code = exif.get('Image Orientation', None)
if code is None:
return (mirror, rotation)
try:
code = int(code)
except ValueError:
code = ROTATION[code]
if code in (2, 4, 5, 7):
mirror = 1
if code in (1, 2):
rotation = 0
elif code in (3, 4):
rotation = 180
elif code in (5, 6):
rotation = 90
elif code in (7, 8):
rotation = 270
return (mirror, rotation)
def transformImage(self, method, REQUEST=None):
"""
Transform an Image:
FLIP_LEFT_RIGHT
FLIP_TOP_BOTTOM
ROTATE_90 (rotate counterclockwise)
ROTATE_180
ROTATE_270 (rotate clockwise)
"""
image = self.image.data
image2 = StringIO()
if image is not None:
method = int(method)
img = PIL.Image.open(StringIO(self.image.data))
del image
fmt = img.format
img = img.transpose(method)
img.save(image2, fmt, quality=88)
self.image.data = image2.getvalue()
self.reindexObject()
Now make a simple subscriber action to both modified and objectadded:
<subscriber
for="plone.app.contenttypes.content.Image
zope.lifecycleevent.IObjectModifiedEvent"
handler=".events.exifRotation"
/>
<subscriber
for="plone.app.contenttypes.content.Image
zope.lifecycleevent.IObjectAddedEvent"
handler=".events.exifRotation"
/>
And BAM you’ve got AT powers in you Dexterity based contenttypes and your images are auto rotated just like the user would expect. At the Arnhem sprint I will try and make this a default behavior or at least optional.
More about EXIF information:
[1]: https://github.com/plone/Products.ATContentTypes/blob/master/Products/ATContentTypes/lib/imagetransform.py