From 636ad68f5e4a6f66d3d8987a7565b756c65e3c5e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Mar 2019 10:28:45 +1100 Subject: [PATCH] Read Photoshop resolution data --- Tests/test_file_jpeg.py | 9 +++++++++ src/PIL/IptcImagePlugin.py | 32 +++----------------------------- src/PIL/JpegImagePlugin.py | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index cbe894f34..712a36cd5 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -590,6 +590,15 @@ class TestFileJpeg(PillowTestCase): # Act / Assert self.assertEqual(im._getexif()[306], '2017:03:13 23:03:09') + def test_photoshop(self): + im = Image.open("Tests/images/photoshop-200dpi.jpg") + self.assertEqual(im.info["photoshop"][0x03ed], { + 'XResolution': 200.0, + 'DisplayedUnitsX': 1, + 'YResolution': 200.0, + 'DisplayedUnitsY': 1, + }) + @unittest.skipUnless(sys.platform.startswith('win32'), "Windows only") class TestFileCloseW32(PillowTestCase): diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 5055d2a00..371bb3acf 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -198,35 +198,9 @@ def getiptcinfo(im): elif isinstance(im, JpegImagePlugin.JpegImageFile): # extract the IPTC/NAA resource - try: - app = im.app["APP13"] - if app[:14] == b"Photoshop 3.0\x00": - app = app[14:] - # parse the image resource block - offset = 0 - while app[offset:offset+4] == b"8BIM": - offset += 4 - # resource code - code = i16(app, offset) - offset += 2 - # resource name (usually empty) - name_len = i8(app[offset]) - # name = app[offset+1:offset+1+name_len] - offset = 1 + offset + name_len - if offset & 1: - offset += 1 - # resource data block - size = i32(app, offset) - offset += 4 - if code == 0x0404: - # 0x0404 contains IPTC/NAA data - data = app[offset:offset+size] - break - offset = offset + size - if offset & 1: - offset += 1 - except (AttributeError, KeyError): - pass + photoshop = im.info.get("photoshop") + if photoshop: + data = photoshop.get(0x0404) elif isinstance(im, TiffImagePlugin.TiffImageFile): # get raw data from the IPTC/NAA tag (PhotoShop tags the data diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 2f76e9675..d51e0a831 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -39,7 +39,7 @@ import struct import io import warnings from . import Image, ImageFile, TiffImagePlugin -from ._binary import i8, o8, i16be as i16 +from ._binary import i8, o8, i16be as i16, i32be as i32 from .JpegPresets import presets from ._util import isStringType @@ -104,6 +104,39 @@ def APP(self, marker): # reassemble the profile, rather than assuming that the APP2 # markers appear in the correct sequence. self.icclist.append(s) + elif marker == 0xFFED: + if s[:14] == b"Photoshop 3.0\x00": + blocks = s[14:] + # parse the image resource block + offset = 0 + photoshop = {} + while blocks[offset:offset+4] == b"8BIM": + offset += 4 + # resource code + code = i16(blocks, offset) + offset += 2 + # resource name (usually empty) + name_len = i8(blocks[offset]) + # name = blocks[offset+1:offset+1+name_len] + offset = 1 + offset + name_len + if offset & 1: + offset += 1 + # resource data block + size = i32(blocks, offset) + offset += 4 + data = blocks[offset:offset+size] + if code == 0x03ED: # ResolutionInfo + data = { + 'XResolution': i32(data[:4]) / 65536, + 'DisplayedUnitsX': i16(data[4:8]), + 'YResolution': i32(data[8:12]) / 65536, + 'DisplayedUnitsY': i16(data[12:]), + } + photoshop[code] = data + offset = offset + size + if offset & 1: + offset += 1 + self.info["photoshop"] = photoshop elif marker == 0xFFEE and s[:5] == b"Adobe": self.info["adobe"] = i16(s, 5) # extract Adobe custom properties