From 53df62647af19f47819380373b7b4abd1ffe79ff Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 4 Apr 2017 01:28:33 +0300 Subject: [PATCH] DPI is a tuple (#2472) * DPI is a tuple * Some EXIF only contains an X resolution for DPI * Refactor * Test with no DPI in EXIF * Handle EXIF with no DPI * Created with: exiftool "-*resolution*"= photoshop-200dpi.jpg * Test when not in EXIF, DPI==72,72 * Use X resolution for Y, default to 72,72 dpi * Created with: exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg * Test for EXIF with dpcm instead of dpi * Convert dpcm to dpi, and default to inches if unit unknown --- PIL/JpegImagePlugin.py | 13 +++++++++++-- Tests/images/exif-200dpcm.jpg | Bin 0 -> 10949 bytes Tests/images/no-dpi-in-exif.jpg | Bin 0 -> 10897 bytes Tests/test_file_jpeg.py | 22 +++++++++++++++++++++- 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 Tests/images/exif-200dpcm.jpg create mode 100644 Tests/images/no-dpi-in-exif.jpg diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index ae420551b..22ac2eaba 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -119,8 +119,17 @@ def APP(self, marker): # If DPI isn't in JPEG header, fetch from EXIF if "dpi" not in self.info and "exif" in self.info: - x_resolution = self._getexif()[0x011A] - self.info["dpi"] = x_resolution[0] / x_resolution[1] + exif = self._getexif() + try: + resolution_unit = exif[0x0128] + x_resolution = exif[0x011A] + dpi = x_resolution[0] / x_resolution[1] + if resolution_unit == 3: # cm + # 1 dpcm = 2.54 dpi + dpi *= 2.54 + self.info["dpi"] = dpi, dpi + except KeyError: + self.info["dpi"] = 72, 72 def COM(self, marker): diff --git a/Tests/images/exif-200dpcm.jpg b/Tests/images/exif-200dpcm.jpg new file mode 100644 index 0000000000000000000000000000000000000000..efa55613b8da191f38b1cc0e0b1f50b234ed1ea2 GIT binary patch literal 10949 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 VB_h`L5NlhB_JIhkE literal 0 HcmV?d00001 diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index f543119f6..21436edc1 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -506,7 +506,27 @@ class TestFileJpeg(PillowTestCase): im = Image.open("Tests/images/photoshop-200dpi.jpg") # Act / Assert - self.assertEqual(im.info.get("dpi"), 200) + self.assertEqual(im.info.get("dpi"), (200, 200)) + + def test_dpi_from_dpcm_exif(self): + # Arrange + # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm: + # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg + im = Image.open("Tests/images/exif-200dpcm.jpg") + + # Act / Assert + self.assertEqual(im.info.get("dpi"), (508, 508)) + + def test_no_dpi_in_exif(self): + # Arrange + # This is photoshop-200dpi.jpg with resolution removed from EXIF: + # exiftool "-*resolution*"= photoshop-200dpi.jpg + im = Image.open("Tests/images/no-dpi-in-exif.jpg") + + # Act / Assert + # "When the image resolution is unknown, 72 [dpi] is designated." + # http://www.exiv2.org/tags.html + self.assertEqual(im.info.get("dpi"), (72, 72)) if __name__ == '__main__':