diff --git a/Tests/images/hopper_orientation_2.jpg b/Tests/images/hopper_orientation_2.jpg new file mode 100644 index 000000000..02b4f392e Binary files /dev/null and b/Tests/images/hopper_orientation_2.jpg differ diff --git a/Tests/images/hopper_orientation_2.webp b/Tests/images/hopper_orientation_2.webp new file mode 100644 index 000000000..43381d2ba Binary files /dev/null and b/Tests/images/hopper_orientation_2.webp differ diff --git a/Tests/images/hopper_orientation_3.jpg b/Tests/images/hopper_orientation_3.jpg new file mode 100644 index 000000000..01717d980 Binary files /dev/null and b/Tests/images/hopper_orientation_3.jpg differ diff --git a/Tests/images/hopper_orientation_3.webp b/Tests/images/hopper_orientation_3.webp new file mode 100644 index 000000000..9537ff68e Binary files /dev/null and b/Tests/images/hopper_orientation_3.webp differ diff --git a/Tests/images/hopper_orientation_4.jpg b/Tests/images/hopper_orientation_4.jpg new file mode 100644 index 000000000..3e0bb4e1a Binary files /dev/null and b/Tests/images/hopper_orientation_4.jpg differ diff --git a/Tests/images/hopper_orientation_4.webp b/Tests/images/hopper_orientation_4.webp new file mode 100644 index 000000000..ca7b8cd30 Binary files /dev/null and b/Tests/images/hopper_orientation_4.webp differ diff --git a/Tests/images/hopper_orientation_5.jpg b/Tests/images/hopper_orientation_5.jpg new file mode 100644 index 000000000..fd32afc27 Binary files /dev/null and b/Tests/images/hopper_orientation_5.jpg differ diff --git a/Tests/images/hopper_orientation_5.webp b/Tests/images/hopper_orientation_5.webp new file mode 100644 index 000000000..a3164a90d Binary files /dev/null and b/Tests/images/hopper_orientation_5.webp differ diff --git a/Tests/images/hopper_orientation_6.jpg b/Tests/images/hopper_orientation_6.jpg new file mode 100644 index 000000000..22a096198 Binary files /dev/null and b/Tests/images/hopper_orientation_6.jpg differ diff --git a/Tests/images/hopper_orientation_6.webp b/Tests/images/hopper_orientation_6.webp new file mode 100644 index 000000000..3e24c5bcb Binary files /dev/null and b/Tests/images/hopper_orientation_6.webp differ diff --git a/Tests/images/hopper_orientation_7.jpg b/Tests/images/hopper_orientation_7.jpg new file mode 100644 index 000000000..a7c45146a Binary files /dev/null and b/Tests/images/hopper_orientation_7.jpg differ diff --git a/Tests/images/hopper_orientation_7.webp b/Tests/images/hopper_orientation_7.webp new file mode 100644 index 000000000..f78163aed Binary files /dev/null and b/Tests/images/hopper_orientation_7.webp differ diff --git a/Tests/images/hopper_orientation_8.jpg b/Tests/images/hopper_orientation_8.jpg new file mode 100644 index 000000000..e6b8c2c1c Binary files /dev/null and b/Tests/images/hopper_orientation_8.jpg differ diff --git a/Tests/images/hopper_orientation_8.webp b/Tests/images/hopper_orientation_8.webp new file mode 100644 index 000000000..3cce80a47 Binary files /dev/null and b/Tests/images/hopper_orientation_8.webp differ diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index c5e48c431..c0ac12f6c 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -3,6 +3,12 @@ from .helper import PillowTestCase, hopper from PIL import ImageOps from PIL import Image +try: + from PIL import _webp + HAVE_WEBP = True +except ImportError: + HAVE_WEBP = False + class TestImageOps(PillowTestCase): @@ -62,6 +68,9 @@ class TestImageOps(PillowTestCase): ImageOps.solarize(hopper("L")) ImageOps.solarize(hopper("RGB")) + ImageOps.exif_transpose(hopper("L")) + ImageOps.exif_transpose(hopper("RGB")) + def test_1pxfit(self): # Division by zero in equalize if image is 1 pixel high newimg = ImageOps.fit(hopper("RGB").resize((1, 1)), (35, 35)) @@ -218,3 +227,22 @@ class TestImageOps(PillowTestCase): (0, 127, 0), threshold=1, msg='white test pixel incorrect') + + def test_exif_transpose(self): + exts = [".jpg"] + if HAVE_WEBP and _webp.HAVE_WEBPANIM: + exts.append(".webp") + for ext in exts: + base_im = Image.open("Tests/images/hopper"+ext) + + orientations = [base_im] + for i in range(2, 9): + im = Image.open("Tests/images/hopper_orientation_"+str(i)+ext) + orientations.append(im) + for im in orientations: + transposed_im = ImageOps.exif_transpose(im) + self.assert_image_similar(base_im, transposed_im, 17) + + # Repeat the operation, to test that it does not keep transposing + transposed_im2 = ImageOps.exif_transpose(transposed_im) + self.assert_image_equal(transposed_im2, transposed_im) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index b93f6f35d..afbe3e2c0 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -522,3 +522,31 @@ def solarize(image, threshold=128): else: lut.append(255-i) return _lut(image, lut) + + +def exif_transpose(image): + """ + If an image has an EXIF Orientation tag, return a new image that is + transposed accordingly. Otherwise, return a copy of the image. + + :param image: The image to transpose. + :return: An image. + """ + if not hasattr(image, '_exif_transposed') and hasattr(image, '_getexif'): + exif = image._getexif() + if exif: + orientation = exif.get(0x0112) + method = { + 2: Image.FLIP_LEFT_RIGHT, + 3: Image.ROTATE_180, + 4: Image.FLIP_TOP_BOTTOM, + 5: Image.TRANSPOSE, + 6: Image.ROTATE_270, + 7: Image.TRANSVERSE, + 8: Image.ROTATE_90 + }.get(orientation) + if method is not None: + transposed_image = image.transpose(method) + transposed_image._exif_transposed = True + return transposed_image + return image.copy()