Pil Thumbnail Is Rotating My Image

PIL thumbnail is rotating my image?

Please note that there are better answers below.


When a picture is taller than it is wide, it means the camera was rotated. Some cameras can detect this and write that info in the picture's EXIF metadata. Some viewers take note of this metadata and display the image appropriately.

PIL can read the picture's metadata, but it does not write/copy metadata when you save an Image. Consequently, your smart image viewer will not rotate the image as it did before.

Following up on @Ignacio Vazquez-Abrams's comment, you can read the metadata using PIL this way, and rotate if necessary:

import ExifTags
import Image

img = Image.open(filename)
print(img._getexif().items())
exif=dict((ExifTags.TAGS[k], v) for k, v in img._getexif().items() if k in ExifTags.TAGS)
if not exif['Orientation']:
img=img.rotate(90, expand=True)
img.thumbnail((1000,1000), Image.ANTIALIAS)
img.save(output_fname, "JPEG")

But be aware that the above code may not work for all cameras.

The easiest solution maybe to use some other program to make thumbnails.

phatch is a batch photo editor written in Python which can handle/preserve EXIF metadata. You could either use this program to make your thumbnails, or look at its source code to see how to do this in Python. I believe it uses the pyexiv2 to handle the EXIF metadata. pyexiv2 may be able to handle EXIF better than the PIL's ExifTags module.

imagemagick is another possibility for making batch thumbnails.

Compressing the image using PIL without changing the orientation of the image

I suspect that you're experiencing the same issue as PIL thumbnail is rotating my image?

PIL is not rotating the image as such. The image file has a flag noting the orientation of the image, which Pillow is reading, but not saving to your new file.

So I would try -

from PIL import Image, ExifTags

def save(self, **kwargs):
super(Post, self).save()
if self.picture:
mywidth = 1100
image = Image.open(self.picture)

if hasattr(image, '_getexif'):
exif = image._getexif()
if exif:
for tag, label in ExifTags.TAGS.items():
if label == 'Orientation':
orientation = tag
break
if orientation in exif:
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)

wpercent = (mywidth / float(image.size[0]))
hsize = int((float(image.size[1]) * float(wpercent)))
image = image.resize((mywidth, hsize), Image.ANTIALIAS)
image.save(self.picture.path)

python pillow when I open a portrait (height greater than width) image, the image is rotated ccw 90 degrees

A couple of things. Apparently show saves the image as a .png temp file, and that save loses the orientation information that is in the exif data of the jpg image. I also had trouble with rotate. There is some inconsistency in the Pillow image functions. e.g. thumbnail updates the supplied image and return None.
rotate rotates the image and returns the changed image.

fimage.thumbnail((700,700), Image.ANTIALIAS)
simage = fimage.rotate(270)

# update. Transpose will use EXIF data if available to
# rotate the image to the correct orientation

from PIL import ImageOps
img = ImageOps.exif_transpose(img)

Reference: https://pillow.readthedocs.io/en/stable/reference/ImageOps.html#PIL.ImageOps.exif_transpose

How to use PIL to resize and apply rotation EXIF information to the file?

I finally used pyexiv2, but it is a bit tricky to install on other platforms than GNU.

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (C) 2008-2009 Rémy HUBSCHER <natim@users.sf.net> - http://www.trunat.fr/portfolio/python.html

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

# Using :
# - Python Imaging Library PIL http://www.pythonware.com/products/pil/index.htm
# - pyexiv2 http://tilloy.net/dev/pyexiv2/

###
# What is doing this script ?
#
# 1. Take a directory of picture from a Reflex Camera (Nikon D90 for example)
# 2. Use the EXIF Orientation information to turn the image
# 3. Remove the thumbnail from the EXIF Information
# 4. Create 2 image one maxi map in 600x600, one mini map in 200x200
# 5. Add a comment with the name of the Author and his Website
# 6. Copy the EXIF information to the maxi and mini image
# 7. Name the image files with a meanful name (Date of picture)

import os, sys
try:
import Image
except:
print "To use this program, you need to install Python Imaging Library - http://www.pythonware.com/products/pil/"
sys.exit(1)

try:
import pyexiv2
except:
print "To use this program, you need to install pyexiv2 - http://tilloy.net/dev/pyexiv2/"
sys.exit(1)

############# Configuration ##############
size_mini = 200, 200
size_maxi = 1024, 1024

# Information about the Photograph should be in ASCII
COPYRIGHT="Remy Hubscher - http://www.trunat.fr/"
ARTIST="Remy Hubscher"
##########################################

def listJPEG(directory):
"Retourn a list of the JPEG files in the directory"
fileList = [os.path.normcase(f) for f in os.listdir(directory)]
fileList = [f for f in fileList if os.path.splitext(f)[1] in ('.jpg', '.JPG')]
fileList.sort()
return fileList

def _mkdir(newdir):
"""
works the way a good mkdir should :)
- already exists, silently complete
- regular file in the way, raise an exception
- parent directory(ies) does not exist, make them as well
"""
if os.path.isdir(newdir):
pass
elif os.path.isfile(newdir):
raise OSError("a file with the same name as the desired " \
"dir, '%s', already exists." % newdir)
else:
head, tail = os.path.split(newdir)
if head and not os.path.isdir(head):
_mkdir(head)
if tail:
os.mkdir(newdir)

if len(sys.argv) < 3:
print "USAGE : python %s indir outdir [comment]" % sys.argv[0]
exit

indir = sys.argv[1]
outdir = sys.argv[2]

if len(sys.argv) == 4:
comment = sys.argv[1]
else:
comment = COPYRIGHT

agrandie = os.path.join(outdir, 'agrandie')
miniature = os.path.join(outdir, 'miniature')

print agrandie, miniature

_mkdir(agrandie)
_mkdir(miniature)

for infile in listJPEG(indir):
mini = os.path.join(miniature, infile)
grand = os.path.join(agrandie, infile)
file_path = os.path.join(indir, infile)

image = pyexiv2.Image(file_path)
image.readMetadata()

# We clean the file and add some information
image.deleteThumbnail()

image['Exif.Image.Artist'] = ARTIST
image['Exif.Image.Copyright'] = COPYRIGHT

image.setComment(comment)

# I prefer not to modify the input file
# image.writeMetadata()

# We look for a meanful name
if 'Exif.Image.DateTime' in image.exifKeys():
filename = image['Exif.Image.DateTime'].strftime('%Y-%m-%d_%H-%M-%S.jpg')
mini = os.path.join(miniature, filename)
grand = os.path.join(agrandie, filename)
else:
# If no exif information, leave the old name
mini = os.path.join(miniature, infile)
grand = os.path.join(agrandie, infile)

# We create the thumbnail
#try:
im = Image.open(file_path)
im.thumbnail(size_maxi, Image.ANTIALIAS)

# We rotate regarding to the EXIF orientation information
if 'Exif.Image.Orientation' in image.exifKeys():
orientation = image['Exif.Image.Orientation']
if orientation == 1:
# Nothing
mirror = im.copy()
elif orientation == 2:
# Vertical Mirror
mirror = im.transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 3:
# Rotation 180°
mirror = im.transpose(Image.ROTATE_180)
elif orientation == 4:
# Horizontal Mirror
mirror = im.transpose(Image.FLIP_TOP_BOTTOM)
elif orientation == 5:
# Horizontal Mirror + Rotation 90° CCW
mirror = im.transpose(Image.FLIP_TOP_BOTTOM).transpose(Image.ROTATE_90)
elif orientation == 6:
# Rotation 270°
mirror = im.transpose(Image.ROTATE_270)
elif orientation == 7:
# Horizontal Mirror + Rotation 270°
mirror = im.transpose(Image.FLIP_TOP_BOTTOM).transpose(Image.ROTATE_270)
elif orientation == 8:
# Rotation 90°
mirror = im.transpose(Image.ROTATE_90)

# No more Orientation information
image['Exif.Image.Orientation'] = 1
else:
# No EXIF information, the user has to do it
mirror = im.copy()

mirror.save(grand, "JPEG", quality=85)
img_grand = pyexiv2.Image(grand)
img_grand.readMetadata()
image.copyMetadataTo(img_grand)
img_grand.writeMetadata()
print grand

mirror.thumbnail(size_mini, Image.ANTIALIAS)
mirror.save(mini, "JPEG", quality=85)
img_mini = pyexiv2.Image(mini)
img_mini.readMetadata()
image.copyMetadataTo(img_mini)
img_mini.writeMetadata()
print mini

print

If you see something that could be improved (except the fact that it is still for Python 2.5) then please let me know.

Remove Black Bars/Excess from Image rotated with PIL

The proper answer to this is to ensure your image is in the correct mode. Even if you load an image that supports transparency (such as a .png), you will still encounter this error unless you convert the image mode from its default to 'RGBA', like so:

image = Image.open('image.png').convert('RGBA')

Which then allows the rotation to be completely scuff-free!

Sample Image

I decided to post this question/answer combo because I searched for hours to find this answer, and I thought I'd make it more accessible to anybody else who experiences my issue!

Here's where I found the answer, on an old forum from long, long ago:

    rotate image with transparent background?

Rotating PIL Image does not seem to rotate canvas (no TKinter canvas added)

Figured it out. I'm going to leave it up to help others, as this seems to be a subtle yet important difference.

img = img.transpose(Image.ROTATE_270) 

...or...

img = img.transpose(Image.ROTATE_90) 

Docs

Python Image Library: Image is turned by 90 degree?

When you do a portrait picture with your smartphone and a landscape picture, both will be done with the same coordinate system, that is, relative to the orientation of the imaging sensor in your phone.

However, the phone's gyroscope detects the rotation of the device, so it "knows" whether it is a portrait or landscape picture. This information is saved in the JPEG's metadata.

Software to display images typically evaluates this information to rotate the picture accordingly. In image editing software, you should typically be asked if you want to apply the rotation to the picture (e.g. The Gimp does this).

However, image processing libraries typically completely ignore this information. They only access the image pixels, and they access them in the order they are stored. As you can see there is nothing wrong with python imaging, however it would be interested if there is an interface to also read this information, so you can deal with it accordingly.



Related Topics



Leave a reply



Submit