Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

802: handle EXIF orientations 3, 6, and 8 when cropping photos #816

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions OpenOversight/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from flask import current_app, url_for
from flask_login import current_user
from PIL import Image as Pimage
from PIL import ExifTags
from PIL.PngImagePlugin import PngImageFile

from .models import (db, Officer, Assignment, Job, Image, Face, User, Unit, Department,
Expand Down Expand Up @@ -473,6 +474,32 @@ def create_description(self, form):
date_updated=datetime.datetime.now())


def conditionally_rotate_image(image):
try:
# NOTE: Pillow >= 6.0 provides `ImageOps.exif_transpose` which acts like the below code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to bump the Pillow dependency version instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll try that, too. I was a bit hesitant to mess with dependencies.


# find the orientation key
for orientation in ExifTags.TAGS.keys():
if ExifTags.TAGS[orientation] == 'Orientation':
break

exif = dict(image._getexif().items())

if exif[orientation] == 3:
current_app.logger.info('image had EXIF orientation 3; rotating 180 degrees')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These loggers might be useless. They don’t really provide any actionable information and they expose a behavior the user would otherwise assume “just works.”

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the detriment to keeping them. It would be interesting for admins to see how often we run across these EXIF rotation issues.

image = image.rotate(180, expand=True)
elif exif[orientation] == 6:
current_app.logger.info('image had EXIF orientation 6; rotating 270 degrees')
image = image.rotate(270, expand=True)
elif exif[orientation] == 8:
current_app.logger.info('image had EXIF orientation 8; rotating 90 degrees')
image = image.rotate(90, expand=True)
return image
except (AttributeError, KeyError, IndexError):
# cases: image don't have getexif
return image


def crop_image(image, crop_data=None, department_id=None):
if 'http' in image.filepath:
with urlopen(image.filepath) as response:
Expand All @@ -488,6 +515,8 @@ def crop_image(image, crop_data=None, department_id=None):
if not crop_data and pimage.size[0] < SIZE[0] and pimage.size[1] < SIZE[1]:
return image

pimage = conditionally_rotate_image(pimage)

if crop_data:
pimage = pimage.crop(crop_data)
if pimage.size[0] > SIZE[0] or pimage.size[1] > SIZE[1]:
Expand Down