diff --git a/Tests/images/orientation_rectangle.jpg b/Tests/images/orientation_rectangle.jpg new file mode 100644 index 000000000..85cfbd0a8 Binary files /dev/null and b/Tests/images/orientation_rectangle.jpg differ diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index d390f3c1e..e7d04cceb 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -404,6 +404,18 @@ def test_exif_transpose(): assert 0x0112 not in transposed_im.getexif() +def test_exif_transpose_inplace(): + with Image.open("Tests/images/orientation_rectangle.jpg") as im: + assert im.size == (2, 1) + assert im.getexif()[0x0112] == 8 + expected = im.rotate(90, expand=True) + + ImageOps.exif_transpose(im, inPlace=True) + assert im.size == (1, 2) + assert 0x0112 not in im.getexif() + assert_image_equal(im, expected) + + def test_autocontrast_cutoff(): # Test the cutoff argument of autocontrast with Image.open("Tests/images/bw_gradient.png") as img: diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 301c593c7..460a21ce2 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -576,19 +576,20 @@ def solarize(image, threshold=128): return _lut(image, lut) -def exif_transpose(image): +def exif_transpose(image, inPlace=False): """ - If an image has an EXIF Orientation tag, other than 1, return a new image - that is transposed accordingly. The new image will have the orientation - data removed. - - Otherwise, return a copy of the image. + If an image has an EXIF Orientation tag, other than 1, transpose the image + accordingly, and remove the orientation data. :param image: The image to transpose. - :return: An image. + :param inPlace: Boolean. + If ``True``, the original image is modified in-place, and ``None`` is returned. + If ``False`` (default), a new :py:class:`~PIL.Image.Image` object is returned + with the transposition applied. If there is no transposition, a copy of the + image will be returned. """ - exif = image.getexif() - orientation = exif.get(0x0112) + image_exif = image.getexif() + orientation = image_exif.get(0x0112) method = { 2: Image.Transpose.FLIP_LEFT_RIGHT, 3: Image.Transpose.ROTATE_180, @@ -600,22 +601,30 @@ def exif_transpose(image): }.get(orientation) if method is not None: transposed_image = image.transpose(method) - transposed_exif = transposed_image.getexif() - if 0x0112 in transposed_exif: - del transposed_exif[0x0112] - if "exif" in transposed_image.info: - transposed_image.info["exif"] = transposed_exif.tobytes() - elif "Raw profile type exif" in transposed_image.info: - transposed_image.info[ - "Raw profile type exif" - ] = transposed_exif.tobytes().hex() - elif "XML:com.adobe.xmp" in transposed_image.info: + if inPlace: + image.im = transposed_image.im + image.pyaccess = None + image._size = transposed_image._size + exif_image = image if inPlace else transposed_image + + exif = exif_image.getexif() + if 0x0112 in exif: + del exif[0x0112] + if "exif" in exif_image.info: + exif_image.info["exif"] = exif.tobytes() + elif "Raw profile type exif" in exif_image.info: + exif_image.info["Raw profile type exif"] = exif.tobytes().hex() + elif "XML:com.adobe.xmp" in exif_image.info: for pattern in ( r'tiff:Orientation="([0-9])"', r"([0-9])", ): - transposed_image.info["XML:com.adobe.xmp"] = re.sub( - pattern, "", transposed_image.info["XML:com.adobe.xmp"] + exif_image.info["XML:com.adobe.xmp"] = re.sub( + pattern, "", exif_image.info["XML:com.adobe.xmp"] ) + if inPlace: + return return transposed_image + if inPlace: + return return image.copy()