diff --git a/Tests/images/xmp_tags_orientation.png b/Tests/images/xmp_tags_orientation.png new file mode 100644 index 000000000..c1be1665f Binary files /dev/null and b/Tests/images/xmp_tags_orientation.png differ diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index a2535b97f..a44bdecf8 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -591,18 +591,27 @@ class TestFilePng: with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} - @pytest.mark.parametrize( - "test_file", - [ - "Tests/images/exif.png", # With an EXIF chunk - "Tests/images/exif_imagemagick.png", # With an ImageMagick zTXt chunk - ], - ) - def test_exif(self, test_file): - with Image.open(test_file) as im: + def test_exif(self): + # With an EXIF chunk + with Image.open("Tests/images/exif.png") as im: exif = im._getexif() assert exif[274] == 1 + # With an ImageMagick zTXt chunk + with Image.open("Tests/images/exif_imagemagick.png") as im: + exif = im._getexif() + assert exif[274] == 1 + + # Assert that info still can be extracted + # when the image is no longer a PngImageFile instance + exif = im.copy().getexif() + assert exif[274] == 1 + + # With XMP tags + with Image.open("Tests/images/xmp_tags_orientation.png") as im: + exif = im.getexif() + assert exif[274] == 3 + def test_exif_save(self, tmp_path): with Image.open("Tests/images/exif.png") as im: test_file = str(tmp_path / "temp.png") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 8c5fff8ed..9804f3258 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -35,6 +35,7 @@ import struct import sys import tempfile import warnings +import xml.etree.ElementTree from collections.abc import Callable, MutableMapping from pathlib import Path @@ -1300,7 +1301,28 @@ class Image: def getexif(self): if self._exif is None: self._exif = Exif() - self._exif.load(self.info.get("exif")) + + exif_info = self.info.get("exif") + if exif_info is None and "Raw profile type exif" in self.info: + exif_info = bytes.fromhex( + "".join(self.info["Raw profile type exif"].split("\n")[3:]) + ) + self._exif.load(exif_info) + + # XMP tags + if 0x0112 not in self._exif: + xmp_tags = self.info.get("XML:com.adobe.xmp") + if xmp_tags: + root = xml.etree.ElementTree.fromstring(xmp_tags) + for elem in root.iter(): + if elem.tag.endswith("}Description"): + orientation = elem.attrib.get( + "{http://ns.adobe.com/tiff/1.0/}Orientation" + ) + if orientation: + self._exif[0x0112] = int(orientation) + break + return self._exif def getim(self): diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index ee9d52b4c..f62bf8542 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -931,16 +931,7 @@ class PngImageFile(ImageFile.ImageFile): if "exif" not in self.info: self.load() - if self._exif is None: - self._exif = Image.Exif() - - exif_info = self.info.get("exif") - if exif_info is None and "Raw profile type exif" in self.info: - exif_info = bytes.fromhex( - "".join(self.info["Raw profile type exif"].split("\n")[3:]) - ) - self._exif.load(exif_info) - return self._exif + return super().getexif() def _close__fp(self): try: