From bb9672095bf85b320557272c02577d9065b4e93d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 28 Sep 2013 21:31:32 -0700 Subject: [PATCH 01/49] initial working version of saving arbitrary tags from the img.tags directory --- PIL/TiffImagePlugin.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 25ad3578e..eec187985 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -293,6 +293,7 @@ class ImageFileDirectory(collections.MutableMapping): def __setitem__(self, tag, value): if not isinstance(value, tuple): + # UNDONE -- this should probably be type, value value = (value,) self.tags[tag] = value @@ -446,7 +447,10 @@ class ImageFileDirectory(collections.MutableMapping): if typ == 1: # byte data - data = value + if isinstance(value, tuple): + data = value = value[-1] + else: + data = value elif typ == 7: # untyped data data = value = b"".join(value) @@ -493,6 +497,7 @@ class ImageFileDirectory(collections.MutableMapping): count = len(value) if typ == 5: count = count // 2 # adjust for rational data field + append((tag, typ, count, o32(offset), data)) offset = offset + len(data) if offset & 1: @@ -931,6 +936,13 @@ def _save(im, fp, filename): for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION): if key in im.tag.tagdata: ifd[key] = im.tag.tagdata.get(key) + + info = im.encoderinfo.get("tiffinfo",[]) + print("info %s "% info) + for key in info: + if key in im.tag: + ifd[key] = im.tag.get(key) + print ("added %s" %key) # preserve some more tags from original TIFF image file # -- 2008-06-06 Florian Hoech ifd.tagtype = im.tag.tagtype @@ -941,6 +953,7 @@ def _save(im, fp, filename): # which support profiles as TIFF) -- 2008-06-06 Florian Hoech if "icc_profile" in im.info: ifd[ICCPROFILE] = im.info["icc_profile"] + if "description" in im.encoderinfo: ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] if "resolution" in im.encoderinfo: From dbf47837ce997800b74a539500d7a8b1b02f97ae Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 2 Oct 2013 22:06:17 -0700 Subject: [PATCH 02/49] Add arbitrary tags to tiff images using an ImageFileDirectory --- PIL/TiffImagePlugin.py | 29 +++++++++++++++++++++-------- Tests/test_file_tiff_metadata.py | 27 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 Tests/test_file_tiff_metadata.py diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index eec187985..ffb1e81bd 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -217,7 +217,7 @@ class ImageFileDirectory(collections.MutableMapping): # represents a TIFF tag directory. to speed things up, # we don't decode tags unless they're asked for. - def __init__(self, prefix): + def __init__(self, prefix=II): self.prefix = prefix[:2] if self.prefix == MM: self.i16, self.i32 = ib16, ib32 @@ -456,8 +456,15 @@ class ImageFileDirectory(collections.MutableMapping): data = value = b"".join(value) elif isinstance(value[0], str): # string data + if isinstance(value, tuple): + value = value[-1] typ = 2 - data = value = b"\0".join(value.encode('ascii', 'replace')) + b"\0" + # was b'\0'.join(str), which led to \x00a\x00b sorts + # of strings which I don't see in in the wild tiffs + # and doesn't match the tiff spec: 8-bit byte that + # contains a 7-bit ASCII code; the last byte must be + # NUL (binary zero). + data = value = b"".join(value.encode('ascii', 'replace')) + b"\0" else: # integer data if tag == STRIPOFFSETS: @@ -929,6 +936,18 @@ def _save(im, fp, filename): ifd[IMAGEWIDTH] = im.size[0] ifd[IMAGELENGTH] = im.size[1] + # write any arbitrary tags passed in as an ImageFileDirectory + info = im.encoderinfo.get("tiffinfo",{}) + if Image.DEBUG: + print ("Tiffinfo Keys: %s"% info.keys) + for key in info.keys(): + ifd[key] = info.get(key) + try: + ifd.tagtype[key] = info.tagtype[key] + except: + pass # might not be an IFD, Might not have populated type + + # additions written by Greg Couch, gregc@cgl.ucsf.edu # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com if hasattr(im, 'tag'): @@ -937,12 +956,6 @@ def _save(im, fp, filename): if key in im.tag.tagdata: ifd[key] = im.tag.tagdata.get(key) - info = im.encoderinfo.get("tiffinfo",[]) - print("info %s "% info) - for key in info: - if key in im.tag: - ifd[key] = im.tag.get(key) - print ("added %s" %key) # preserve some more tags from original TIFF image file # -- 2008-06-06 Florian Hoech ifd.tagtype = im.tag.tagtype diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py new file mode 100644 index 000000000..8c38a1cb6 --- /dev/null +++ b/Tests/test_file_tiff_metadata.py @@ -0,0 +1,27 @@ +from tester import * +from PIL import Image, TiffImagePlugin, TiffTags + +tag_ids = dict(zip(TiffTags.TAGS.values(), TiffTags.TAGS.keys())) + +# Test writing arbitray metadata into the tiff image directory +# Use case is ImageJ private tags, one numeric, one arbitrary data. +# https://github.com/python-imaging/Pillow/issues/291 + +def test_rt_metadata(): + img = lena() + + textdata = "This is some arbitrary metadata for a text field" + info = TiffImagePlugin.ImageFileDirectory() + + info[tag_ids['ImageJMetaDataByteCounts']] = len(textdata) + info[tag_ids['ImageJMetaData']] = textdata + + f = tempfile("temp.tif") + + img.save(f, tiffinfo=info) + + loaded = Image.open(f) + + assert_equal(loaded.tag[50838], (len(textdata),)) + assert_equal(loaded.tag[50839], textdata) + From c4f9fa7a40dbaebdb07880785e415b0a9900fc59 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 2 Oct 2013 22:23:27 -0700 Subject: [PATCH 03/49] Py3 -- can't join a string --- PIL/TiffImagePlugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index ffb1e81bd..2e2c772ae 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -463,8 +463,9 @@ class ImageFileDirectory(collections.MutableMapping): # of strings which I don't see in in the wild tiffs # and doesn't match the tiff spec: 8-bit byte that # contains a 7-bit ASCII code; the last byte must be - # NUL (binary zero). - data = value = b"".join(value.encode('ascii', 'replace')) + b"\0" + # NUL (binary zero). Also, I don't think this was well + # excersized before. + data = value = b"" + value.encode('ascii', 'replace') + b"\0" else: # integer data if tag == STRIPOFFSETS: From ce0e8b6abfe5f2127af3c130d8c55b1918c89593 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Oct 2013 16:59:37 -0700 Subject: [PATCH 04/49] streamlining internal representation of types in ImageFileDirectory --- PIL/TiffImagePlugin.py | 47 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 2e2c772ae..d7e940365 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -213,11 +213,45 @@ def _accept(prefix): # Wrapper for TIFF IFDs. class ImageFileDirectory(collections.MutableMapping): + """ This class represents a TIFF tag directory. To speed things + up, we don't decode tags unless they're asked for. - # represents a TIFF tag directory. to speed things up, - # we don't decode tags unless they're asked for. + Exposes a dictionary interface of the tags in the directory + ImageFileDirectory[key] = value + value = ImageFileDirectory[key] + + Also contains a dictionary of tag types as read from the tiff + image file, 'ImageFileDirectory.tagtype' + + + Data Structures: + 'public' + * self.tagtype = {} Key: numerical tiff tag number + Value: integer corresponding to the data type from + `TiffTags.TYPES` + + 'internal' + * self.tags = {} Key: numerical tiff tag number + Value: Decoded data, Generally a tuple. + * If set from __setval__ -- always a tuple + * Numeric types -- always a tuple + * String type -- not a tuple, returned as string + * Undefined data -- not a tuple, returned as bytes + * Byte -- not a tuple, returned as byte. + * self.tagdata = {} Key: numerical tiff tag number + Value: undecoded byte string from file + + + Tags will be found in either self.tags or self.tagdata, but + not both. The union of the two should contain all the tags + from the Tiff image file. External classes shouldn't + reference these unless they're really sure what they're doing. + """ def __init__(self, prefix=II): + """ + :prefix: 'II'|'MM' tiff endianness + """ self.prefix = prefix[:2] if self.prefix == MM: self.i16, self.i32 = ib16, ib32 @@ -263,7 +297,8 @@ class ImageFileDirectory(collections.MutableMapping): try: return self.tags[tag] except KeyError: - type, data = self.tagdata[tag] # unpack on the fly + data = self.tagdata[tag] # unpack on the fly + type = self.tagtype[tag] size, handler = self.load_dispatch[type] self.tags[tag] = data = handler(self, data) del self.tagdata[tag] @@ -292,8 +327,10 @@ class ImageFileDirectory(collections.MutableMapping): return tag in self def __setitem__(self, tag, value): + # tags are tuples for integers + # tags are not tuples for byte, string, and undefined data. + # see load_* if not isinstance(value, tuple): - # UNDONE -- this should probably be type, value value = (value,) self.tags[tag] = value @@ -407,7 +444,7 @@ class ImageFileDirectory(collections.MutableMapping): warnings.warn("Possibly corrupt EXIF data. Expecting to read %d bytes but only got %d. Skipping tag %s" % (size, len(data), tag)) continue - self.tagdata[tag] = typ, data + self.tagdata[tag] = data self.tagtype[tag] = typ if Image.DEBUG: From 951a5d4ce8047849752fa68a969f4f655da69060 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Oct 2013 17:00:20 -0700 Subject: [PATCH 05/49] Using the public interface rather than the raw, undecoded interface --- PIL/TiffImagePlugin.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index d7e940365..cfabcb7ee 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -990,16 +990,12 @@ def _save(im, fp, filename): # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com if hasattr(im, 'tag'): # preserve tags from original TIFF image file - for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION): - if key in im.tag.tagdata: - ifd[key] = im.tag.tagdata.get(key) - - # preserve some more tags from original TIFF image file - # -- 2008-06-06 Florian Hoech - ifd.tagtype = im.tag.tagtype - for key in (IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): + for key in (RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION, + IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): if key in im.tag: ifd[key] = im.tag[key] + ifd.tagtypes[key] = im.tag.tagtype.get(key, None) + # preserve ICC profile (should also work when saving other formats # which support profiles as TIFF) -- 2008-06-06 Florian Hoech if "icc_profile" in im.info: From a3d267b96c43a2d8eddcba8aa036cc9a93cb0e4e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Oct 2013 17:00:54 -0700 Subject: [PATCH 06/49] Changes in the internal representation of ImageFileDirectory --- PIL/IptcImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/IptcImagePlugin.py b/PIL/IptcImagePlugin.py index 02e3360ff..157b73509 100644 --- a/PIL/IptcImagePlugin.py +++ b/PIL/IptcImagePlugin.py @@ -262,7 +262,7 @@ def getiptcinfo(im): # get raw data from the IPTC/NAA tag (PhotoShop tags the data # as 4-byte integers, so we cannot use the get method...) try: - type, data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK] + data = im.tag.tagdata[TiffImagePlugin.IPTC_NAA_CHUNK] except (AttributeError, KeyError): pass From 350cc702f1e9499d621472f05898a03b493ce52a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Oct 2013 23:01:15 -0700 Subject: [PATCH 07/49] Additional tests for reading and writing Tiff ImageFileDirectories --- Tests/test_file_libtiff.py | 21 ++++++++++- Tests/test_file_tiff_metadata.py | 61 +++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 037fc022d..976f97f34 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -92,7 +92,6 @@ def test_g4_write(): assert_equal(reread.size,(500,500)) _assert_noerr(reread) assert_image_equal(reread, rot) - assert_false(orig.tobytes() == reread.tobytes()) def test_adobe_deflate_tiff(): @@ -105,3 +104,23 @@ def test_adobe_deflate_tiff(): assert_no_exception(lambda: im.load()) +def test_write_metadata(): + """ Test metadata writing through libtiff """ + img = Image.open('Tests/images/lena_g4.tif') + f = tempfile('temp.tiff') + img.save(f, tiffinfo = img.tag) + + loaded = Image.open(f) + + original = img.tag.named() + reloaded = loaded.tag.named() + + ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber'] + + for tag, value in reloaded.items(): + if tag not in ignored: + assert_equal(original[tag], value, "%s didn't roundtrip" % tag) + + for tag, value in original.items(): + if tag not in ignored: + assert_equal(value, reloaded[tag], "%s didn't roundtrip" % tag) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 8c38a1cb6..9a050e1e3 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -3,11 +3,12 @@ from PIL import Image, TiffImagePlugin, TiffTags tag_ids = dict(zip(TiffTags.TAGS.values(), TiffTags.TAGS.keys())) -# Test writing arbitray metadata into the tiff image directory -# Use case is ImageJ private tags, one numeric, one arbitrary data. -# https://github.com/python-imaging/Pillow/issues/291 - def test_rt_metadata(): + """ Test writing arbitray metadata into the tiff image directory + Use case is ImageJ private tags, one numeric, one arbitrary + data. https://github.com/python-imaging/Pillow/issues/291 + """ + img = lena() textdata = "This is some arbitrary metadata for a text field" @@ -25,3 +26,55 @@ def test_rt_metadata(): assert_equal(loaded.tag[50838], (len(textdata),)) assert_equal(loaded.tag[50839], textdata) +def test_read_metadata(): + img = Image.open('Tests/images/lena_g4.tif') + + known = {'YResolution': ((1207959552, 16777216),), + 'PlanarConfiguration': (1,), + 'BitsPerSample': (1,), + 'ImageLength': (128,), + 'Compression': (4,), + 'FillOrder': (1,), + 'DocumentName': u'lena.g4.tif', + 'RowsPerStrip': (128,), + 'ResolutionUnit': (1,), + 'PhotometricInterpretation': (0,), + 'PageNumber': (0, 1), + 'XResolution': ((1207959552, 16777216),), + 'ImageWidth': (128,), + 'Orientation': (1,), + 'StripByteCounts': (1796,), + 'SamplesPerPixel': (1,), + 'StripOffsets': (8,), + 'Software': u'ImageMagick 6.5.7-8 2012-08-17 Q16 http://www.imagemagick.org'} + + # assert_equal is equivalent, but less helpful in telling what's wrong. + named = img.tag.named() + for tag, value in named.items(): + assert_equal(known[tag], value) + + for tag, value in known.items(): + assert_equal(value, named[tag]) + + +def test_write_metadata(): + """ Test metadata writing through the python code """ + img = Image.open('Tests/images/lena.tif') + + f = tempfile('temp.tiff') + img.save(f, tiffinfo = img.tag) + + loaded = Image.open(f) + + original = img.tag.named() + reloaded = loaded.tag.named() + + ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets'] + + for tag, value in reloaded.items(): + if tag not in ignored: + assert_equal(original[tag], value, "%s didn't roundtrip" % tag) + + for tag, value in original.items(): + if tag not in ignored: + assert_equal(value, reloaded[tag], "%s didn't roundtrip" % tag) From e6c8e5abecfe06e6ed090eb39e1b29f3d46a4804 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Oct 2013 23:02:59 -0700 Subject: [PATCH 08/49] better handling of unicode, rational tuples --- PIL/TiffImagePlugin.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index cfabcb7ee..34f307305 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -481,7 +481,10 @@ class ImageFileDirectory(collections.MutableMapping): if tag in self.tagtype: typ = self.tagtype[tag] - + + if Image.DEBUG: + print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) + if typ == 1: # byte data if isinstance(value, tuple): @@ -491,7 +494,7 @@ class ImageFileDirectory(collections.MutableMapping): elif typ == 7: # untyped data data = value = b"".join(value) - elif isinstance(value[0], str): + elif type(value[0]) in (str, unicode): # string data if isinstance(value, tuple): value = value[-1] @@ -508,9 +511,12 @@ class ImageFileDirectory(collections.MutableMapping): if tag == STRIPOFFSETS: stripoffsets = len(directory) typ = 4 # to avoid catch-22 - elif tag in (X_RESOLUTION, Y_RESOLUTION): + elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ==5: # identify rational data fields typ = 5 + # UNDONE -- could be multiple rational tuples. + if isinstance(value[0], tuple): + value = value[-1] elif not typ: typ = 3 for v in value: From 2188cf2bafbd16184f5b8eb2a1a9fabd3dd1f06e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Oct 2013 23:03:13 -0700 Subject: [PATCH 09/49] typo --- PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 34f307305..06a712b55 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1000,7 +1000,7 @@ def _save(im, fp, filename): IPTC_NAA_CHUNK, PHOTOSHOP_CHUNK, XMP): if key in im.tag: ifd[key] = im.tag[key] - ifd.tagtypes[key] = im.tag.tagtype.get(key, None) + ifd.tagtype[key] = im.tag.tagtype.get(key, None) # preserve ICC profile (should also work when saving other formats # which support profiles as TIFF) -- 2008-06-06 Florian Hoech From 0204733fd6935eb01680a332222167977aa697de Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Oct 2013 23:03:50 -0700 Subject: [PATCH 10/49] Proper handling of both IFDs for libtiff usage --- PIL/TiffImagePlugin.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 06a712b55..342dba36c 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1071,11 +1071,13 @@ def _save(im, fp, filename): _fp = os.dup(fp.fileno()) blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. - atts = dict([(k,v) for (k,(v,)) in ifd.items() if k not in blocklist]) + + items = itertools.chain(getattr(im, 'ifd', {}).items(), ifd.items()) + atts = {} try: # pull in more bits from the original file, e.g x,y resolution # so that we can save(load('')) == original file. - for k,v in im.ifd.items(): + for k,v in items: if k not in atts and k not in blocklist: if type(v[0]) == tuple and len(v) > 1: # A tuple of more than one rational tuples @@ -1094,9 +1096,15 @@ def _save(im, fp, filename): if type(v) == str: atts[k] = v continue + if type(v) == unicode: + atts[k] = v.encode('ascii', errors='ignore') + continue - except: + except Exception, msg: # if we don't have an ifd here, just punt. + if Image.DEBUG: + print (msg) + #raise msg pass if Image.DEBUG: print (atts) From e35ed87be60c1dc1b0421233368d2e86795c4853 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Oct 2013 23:09:41 -0700 Subject: [PATCH 11/49] Writing multiple rational items --- PIL/TiffImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 342dba36c..8c1ed8381 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -514,9 +514,9 @@ class ImageFileDirectory(collections.MutableMapping): elif tag in (X_RESOLUTION, Y_RESOLUTION) or typ==5: # identify rational data fields typ = 5 - # UNDONE -- could be multiple rational tuples. if isinstance(value[0], tuple): - value = value[-1] + # long name for flatten + value = tuple(itertools.chain.from_iterable(value)) elif not typ: typ = 3 for v in value: From 0bded743f57dfab8c7d707eb4a0695c00c63d5cc Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 7 Oct 2013 23:14:46 -0700 Subject: [PATCH 12/49] Additional test image --- Tests/images/lena.tif | Bin 0 -> 49470 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/lena.tif diff --git a/Tests/images/lena.tif b/Tests/images/lena.tif new file mode 100644 index 0000000000000000000000000000000000000000..fead980d47364ed40b478fa1c85102800061a5b1 GIT binary patch literal 49470 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
  • c}~ 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 Date: Mon, 7 Oct 2013 23:30:55 -0700 Subject: [PATCH 13/49] Python3 fixes --- PIL/TiffImagePlugin.py | 15 ++++++++------- Tests/test_file_tiff_metadata.py | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 8c1ed8381..1564c8f5e 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -46,6 +46,7 @@ __version__ = "1.3.5" from PIL import Image, ImageFile from PIL import ImagePalette from PIL import _binary +from PIL._util import isStringType import warnings import array, sys @@ -53,6 +54,8 @@ import collections import itertools import os + + II = b"II" # little-endian (intel-style) MM = b"MM" # big-endian (motorola-style) @@ -494,7 +497,7 @@ class ImageFileDirectory(collections.MutableMapping): elif typ == 7: # untyped data data = value = b"".join(value) - elif type(value[0]) in (str, unicode): + elif isStringType(value[0]): # string data if isinstance(value, tuple): value = value[-1] @@ -984,7 +987,8 @@ def _save(im, fp, filename): info = im.encoderinfo.get("tiffinfo",{}) if Image.DEBUG: print ("Tiffinfo Keys: %s"% info.keys) - for key in info.keys(): + keys = list(info.keys()) + for key in keys: ifd[key] = info.get(key) try: ifd.tagtype[key] = info.tagtype[key] @@ -1093,14 +1097,11 @@ def _save(im, fp, filename): # int or similar atts[k] = v[0] continue - if type(v) == str: - atts[k] = v - continue - if type(v) == unicode: + if isStringType(v): atts[k] = v.encode('ascii', errors='ignore') continue - except Exception, msg: + except (Exception, msg): # if we don't have an ifd here, just punt. if Image.DEBUG: print (msg) diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 9a050e1e3..354eb972f 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -35,7 +35,7 @@ def test_read_metadata(): 'ImageLength': (128,), 'Compression': (4,), 'FillOrder': (1,), - 'DocumentName': u'lena.g4.tif', + 'DocumentName': 'lena.g4.tif', 'RowsPerStrip': (128,), 'ResolutionUnit': (1,), 'PhotometricInterpretation': (0,), @@ -46,7 +46,7 @@ def test_read_metadata(): 'StripByteCounts': (1796,), 'SamplesPerPixel': (1,), 'StripOffsets': (8,), - 'Software': u'ImageMagick 6.5.7-8 2012-08-17 Q16 http://www.imagemagick.org'} + 'Software': 'ImageMagick 6.5.7-8 2012-08-17 Q16 http://www.imagemagick.org'} # assert_equal is equivalent, but less helpful in telling what's wrong. named = img.tag.named() From e9b0b09b72c39439f068962d389591dfebd7d480 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 8 Oct 2013 10:43:55 -0700 Subject: [PATCH 14/49] Python 2.6 compatibility --- PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 1564c8f5e..6f9c0f52a 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1101,7 +1101,7 @@ def _save(im, fp, filename): atts[k] = v.encode('ascii', errors='ignore') continue - except (Exception, msg): + except Exception as msg: # if we don't have an ifd here, just punt. if Image.DEBUG: print (msg) From 08347569b5b3b229f8f3ffcc93aa8be47a0756f2 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 8 Oct 2013 11:32:51 -0700 Subject: [PATCH 15/49] Another python 2.6 detail --- PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 6f9c0f52a..220812ffe 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1098,7 +1098,7 @@ def _save(im, fp, filename): atts[k] = v[0] continue if isStringType(v): - atts[k] = v.encode('ascii', errors='ignore') + atts[k] = v.encode('ascii', 'ignore') continue except Exception as msg: From 043dda21d24ecc305c2f4ab54397d5ee26fc93b3 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 8 Oct 2013 22:41:44 -0700 Subject: [PATCH 16/49] Added tiff save option documentation --- docs/handbook/image-file-formats.rst | 51 ++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 8789e115a..6f74622af 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -265,6 +265,57 @@ dictionary of decoded TIFF fields. Values are stored as either strings or tuples. Note that only short, long and ASCII tags are correctly unpacked by this release. +Saving Tiff Images +~~~~~~~~~~~~~~~~~~ + +The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments: + +**tiffinfo** + A :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory` object or dict + object containing tiff tags and values. The TIFF field type is + autodetected for Numeric and string values, any other types + require using an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory` + object and setting the type in + :py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory.tagtype` with + the appropriate numerical value from + ``TiffTags.TYPES``. + + .. versionadded:: 2.3.0 + +**compression** + A string containing the desired compression method for the + file. (valid only with libtiff installed) Valid compression + methods are: ``[None, "tiff_ccitt", "group3", "group4", + "tiff_jpeg", "tiff_adobe_deflate", "tiff_thunderscan", + "tiff_deflate", "tiff_sgilog", "tiff_sgilog24", "tiff_raw_16"]`` + +These arguments to set the tiff header fields are an alternative to using the general tags available through tiffinfo. + +**description** + +**software** + +**date time** + +**artist** + +**copyright** + Strings + +**resolution unit** + A string of "inch", "centimeter" or "cm" + +**resolution** + +**x resolution** + +**y resolution** + +**dpi** + Either a Float, Integer, or 2 tuple of (numerator, + denominator). Resolution implies an equal x and y resolution, dpi + also implies a unit of inches. + WebP ^^^^ From 388c25b7818063829a27c11ab9d396b3b78af3ee Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 23 Oct 2013 20:50:01 -0700 Subject: [PATCH 17/49] PhotometricInterpretation is set from SAVE_INFO, not the original image, so spurious test failure. --- Tests/test_file_libtiff.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index cb81282c2..51ee79ab7 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -111,6 +111,7 @@ def test_write_metadata(): """ Test metadata writing through libtiff """ img = Image.open('Tests/images/lena_g4.tif') f = tempfile('temp.tiff') + img.save(f, tiffinfo = img.tag) loaded = Image.open(f) @@ -118,8 +119,9 @@ def test_write_metadata(): original = img.tag.named() reloaded = loaded.tag.named() - ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber'] - + # PhotometricInterpretation is set from SAVE_INFO, not the original image. + ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'PhotometricInterpretation'] + for tag, value in reloaded.items(): if tag not in ignored: assert_equal(original[tag], value, "%s didn't roundtrip" % tag) From 4da7c475ec5402cbbbe64d0290f765f716a116fd Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Wed, 30 Oct 2013 19:29:15 -0600 Subject: [PATCH 18/49] Quote filenames and title before using on command line This commit quotes title and filename paramaters that are passed to the command line when showing an image. --- PIL/ImageShow.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/PIL/ImageShow.py b/PIL/ImageShow.py index 7e3d63ba3..10a860107 100644 --- a/PIL/ImageShow.py +++ b/PIL/ImageShow.py @@ -17,6 +17,11 @@ from __future__ import print_function from PIL import Image import os, sys +if(sys.version_info >= (3, 3)): + from shlex import quote +else: + from pipes import quote + _viewers = [] def register(viewer, order=1): @@ -99,7 +104,7 @@ if sys.platform == "win32": format = "BMP" def get_command(self, file, **options): return ("start /wait %s && ping -n 2 127.0.0.1 >NUL " - "&& del /f %s" % (file, file)) + "&& del /f %s" % (quote(file), quote(file))) register(WindowsViewer) @@ -111,7 +116,7 @@ elif sys.platform == "darwin": # on darwin open returns immediately resulting in the temp # file removal while app is opening command = "open -a /Applications/Preview.app" - command = "(%s %s; sleep 20; rm -f %s)&" % (command, file, file) + command = "(%s %s; sleep 20; rm -f %s)&" % (command, quote(file), quote(file)) return command register(MacViewer) @@ -134,7 +139,7 @@ else: class UnixViewer(Viewer): def show_file(self, file, **options): command, executable = self.get_command_ex(file, **options) - command = "(%s %s; rm -f %s)&" % (command, file, file) + command = "(%s %s; rm -f %s)&" % (command, quote(file), quote(file)) os.system(command) return 1 @@ -154,8 +159,7 @@ else: # imagemagick's display command instead. command = executable = "xv" if title: - # FIXME: do full escaping - command = command + " -name \"%s\"" % title + command = command + " -name %s" % quote(title) return command, executable if which("xv"): From 840c7b9acbcc29080b3d003ff9d5b825b44c52c3 Mon Sep 17 00:00:00 2001 From: Thayne McCombs Date: Wed, 30 Oct 2013 19:40:21 -0600 Subject: [PATCH 19/49] Closes #397. Fixed Viewer.show to return properly. Viewer.show did not return a value, however ImageShow.show expected Viewer.show to return a non-falsey value if successful. Therefor ImageShow.show would continue to call multiple viewers. --- PIL/ImageShow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/ImageShow.py b/PIL/ImageShow.py index 7e3d63ba3..78bc210f3 100644 --- a/PIL/ImageShow.py +++ b/PIL/ImageShow.py @@ -65,7 +65,7 @@ class Viewer: if base != image.mode and image.mode != "1": image = image.convert(base) - self.show_image(image, **options) + return self.show_image(image, **options) # hook methods From 18ced74c06f2c73a2c43b2c74e7a9f1b5bc3a331 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 12 Nov 2013 22:40:36 -0800 Subject: [PATCH 20/49] Image.convert mode 1 documentation update. Fixes #407 --- PIL/Image.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index e1d88fe59..4357d3aa7 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -675,15 +675,18 @@ class Image: L = R * 299/1000 + G * 587/1000 + B * 114/1000 - When translating a greyscale image into a bilevel image (mode - "1"), all non-zero values are set to 255 (white). To use other - thresholds, use the :py:meth:`~PIL.Image.Image.point` method. + The default method of converting a greyscale ("L") or "RGB" + image into a bilevel (mode "1") image uses Floyd-Steinberg + dither to approximate the original image luminosity levels. If + dither is NONE, all non-zero values are set to 255 (white). To + use other thresholds, use the :py:meth:`~PIL.Image.Image.point` + method. :param mode: The requested mode. :param matrix: An optional conversion matrix. If given, this should be 4- or 16-tuple containing floating point values. :param dither: Dithering method, used when converting from - mode "RGB" to "P". + mode "RGB" to "P" or from "RGB" or "L" to "1". Available methods are NONE or FLOYDSTEINBERG (default). :param palette: Palette to use when converting from mode "RGB" to "P". Available palettes are WEB or ADAPTIVE. From 16dac778472917099a92dffd06a717bcc928d1d5 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 12 Nov 2013 23:08:47 -0800 Subject: [PATCH 21/49] Docs: Noted import change for Image Plugins. Fixes #404, plugin not found --- docs/handbook/writing-your-own-file-decoder.rst | 11 +++++++---- docs/porting-pil-to-pillow.rst | 6 ++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index caf9dc07e..10833a53e 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -1,10 +1,13 @@ Writing your own file decoder ============================= -The Python Imaging Library uses a plug-in model which allows you to add your -own decoders to the library, without any changes to the library itself. Such -plug-ins have names like :file:`XxxImagePlugin.py`, where ``Xxx`` is a unique -format name (usually an abbreviation). +The Python Imaging Library uses a plug-in model which allows you to +add your own decoders to the library, without any changes to the +library itself. Such plug-ins usually have names like +:file:`XxxImagePlugin.py`, where ``Xxx`` is a unique format name +(usually an abbreviation). + +.. warning:: Pillow >= 2.1.0 no longer automatically imports any file in the Python path with a name ending in :file:`ImagePlugin.py`. You will need to import your decoder manually. A decoder plug-in should contain a decoder class, based on the :py:class:`PIL.ImageFile.ImageFile` base class. This class should provide an diff --git a/docs/porting-pil-to-pillow.rst b/docs/porting-pil-to-pillow.rst index 93bc672af..a58baac39 100644 --- a/docs/porting-pil-to-pillow.rst +++ b/docs/porting-pil-to-pillow.rst @@ -15,3 +15,9 @@ to this:: The :py:mod:`_imaging` module has been moved. You can now import it like this:: from PIL.Image import core as _imaging + +The image plugin loading mechanisim has changed. Pillow no longer +automatically imports any file in the Python path with a name ending +in :file:`ImagePlugin.py`. You will need to import your image plugin +manually. + From 4ab7c2a7828c3d1d23b135a27dbb7e5269735633 Mon Sep 17 00:00:00 2001 From: Alexander Nordlund Date: Thu, 14 Nov 2013 12:10:19 +0000 Subject: [PATCH 22/49] Adds directories for NetBSD. --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 6c677bbd3..2bc5ca3f6 100644 --- a/setup.py +++ b/setup.py @@ -211,6 +211,10 @@ class pil_build_ext(build_ext): # work ;-) self.add_multiarch_paths() + elif sys.platform.startswith("netbsd"): + _add_directory(library_dirs, "/usr/pkg/lib") + _add_directory(include_dirs, "/usr/pkg/include") + _add_directory(library_dirs, "/usr/local/lib") # FIXME: check /opt/stuff directories here? From f44f4efb681ef4bfb5e79f153bbff8dfb414acc7 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 14 Nov 2013 08:08:50 -0500 Subject: [PATCH 23/49] Add history --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9a5006c03..f6a166922 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ +- Adds directories for NetBSD. + [deepy] + - Support RGBA TIFF with missing ExtraSamples tag [cgohlke] From 7989378bfdf44457f0a997ca932441a99f3d950e Mon Sep 17 00:00:00 2001 From: Esteban Santana Santana Date: Sun, 17 Nov 2013 01:26:44 -0600 Subject: [PATCH 24/49] Added a way to specify the render size for EPS files. There is now a scale parameter that you can pass in to the EpsImageFile.load() function. This parameter is used to specify at what scale Ghostscript renders the EPS internally. Scale needs to be an integer, and all of the internal structures (image size and bounding box) are scaled based on that parameter. --- PIL/EpsImagePlugin.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py index bc0ed43c5..1c9c4b6a1 100644 --- a/PIL/EpsImagePlugin.py +++ b/PIL/EpsImagePlugin.py @@ -50,13 +50,21 @@ if sys.platform.startswith('win'): else: gs_windows_binary = False -def Ghostscript(tile, size, fp): +def Ghostscript(tile, size, fp, scale=1): """Render an image using Ghostscript""" # Unpack decoder tile decoder, tile, offset, data = tile[0] length, bbox = data + #Hack to support hi-res rendering + scale = int(scale) or 1 + orig_size = size + orig_bbox = bbox + size = (size[0] * scale, size[1] * scale) + bbox = [bbox[0], bbox[1], bbox[2] * scale, bbox[3] * scale] + #print("Ghostscript", scale, size, orig_size, bbox, orig_bbox) + import tempfile, os file = tempfile.mktemp() @@ -65,6 +73,7 @@ def Ghostscript(tile, size, fp): command = ["gs", "-q", # quite mode "-g%dx%d" % size, # set output geometry (pixels) + "-r%d" % (72*scale), # set input DPI (dots per inch) "-dNOPAUSE -dSAFER", # don't pause between pages, safe mode "-sDEVICE=ppmraw", # ppm driver "-sOutputFile=%s" % file,# output file @@ -304,11 +313,11 @@ class EpsImageFile(ImageFile.ImageFile): if not box: raise IOError("cannot determine EPS bounding box") - def load(self): + def load(self, scale=1): # Load EPS via Ghostscript if not self.tile: return - self.im = Ghostscript(self.tile, self.size, self.fp) + self.im = Ghostscript(self.tile, self.size, self.fp, scale) self.mode = self.im.mode self.size = self.im.size self.tile = [] From 582d54d05534ee3454061f4cddde23185031b2f6 Mon Sep 17 00:00:00 2001 From: Esteban Santana Santana Date: Wed, 20 Nov 2013 00:43:10 -0600 Subject: [PATCH 25/49] Test harness for EPS files. We now have a test harness for EPS files. Two variants were created one for the default scale=1 and one where scale=2. These two tests are run against two different EPS files, one with zero for the start of the bounding box and one where this is not the case. PNG test renders are used to make sure the output match what we expect. Lastly the sample EPS files were generated using the included create_eps.gnuplot file and the gnuplot program on a Mac. --- Tests/images/create_eps.gnuplot | 30 ++++++++++ Tests/images/non_zero_bb.eps | Bin 0 -> 26367 bytes Tests/images/non_zero_bb.png | Bin 0 -> 2544 bytes Tests/images/non_zero_bb_scale2.png | Bin 0 -> 5489 bytes Tests/images/zero_bb.eps | Bin 0 -> 26359 bytes Tests/images/zero_bb.png | Bin 0 -> 2717 bytes Tests/images/zero_bb_scale2.png | Bin 0 -> 5894 bytes Tests/test_file_eps.py | 82 ++++++++++++++++++++++++++++ 8 files changed, 112 insertions(+) create mode 100644 Tests/images/create_eps.gnuplot create mode 100644 Tests/images/non_zero_bb.eps create mode 100644 Tests/images/non_zero_bb.png create mode 100644 Tests/images/non_zero_bb_scale2.png create mode 100644 Tests/images/zero_bb.eps create mode 100644 Tests/images/zero_bb.png create mode 100644 Tests/images/zero_bb_scale2.png create mode 100644 Tests/test_file_eps.py diff --git a/Tests/images/create_eps.gnuplot b/Tests/images/create_eps.gnuplot new file mode 100644 index 000000000..4d7e29877 --- /dev/null +++ b/Tests/images/create_eps.gnuplot @@ -0,0 +1,30 @@ +#!/usr/bin/gnuplot + +#This is the script that was used to create our sample EPS files +#We used the following version of the gnuplot program +#G N U P L O T +#Version 4.6 patchlevel 3 last modified 2013-04-12 +#Build System: Darwin x86_64 + +#This file will generate the non_zero_bb.eps variant, in order to get the +#zero_bb.eps variant you will need to edit line6 in the result file to +#be "%%BoundingBox: 0 0 460 352" instead of "%%BoundingBox: 50 50 410 302" + +set t postscript eps color +set o "sample.eps" +set dummy u,v +set key bmargin center horizontal Right noreverse enhanced autotitles nobox +set parametric +set view 50, 30, 1, 1 +set isosamples 10, 10 +set hidden3d back offset 1 trianglepattern 3 undefined 1 altdiagonal bentover +set ticslevel 0 +set title "Interlocking Tori" + +set style line 1 lt 1 lw 1 pt 3 lc rgb "red" +set style line 2 lt 1 lw 1 pt 3 lc rgb "blue" + +set urange [ -3.14159 : 3.14159 ] noreverse nowriteback +set vrange [ -3.14159 : 3.14159 ] noreverse nowriteback +splot cos(u)+.5*cos(u)*cos(v),sin(u)+.5*sin(u)*cos(v),.5*sin(v) ls 1,\ + 1+cos(u)+.5*cos(u)*cos(v),.5*sin(v),sin(u)+.5*sin(u)*cos(v) ls 2 diff --git a/Tests/images/non_zero_bb.eps b/Tests/images/non_zero_bb.eps new file mode 100644 index 0000000000000000000000000000000000000000..750a44b38a1a89b8cbd014ccc8abc25bd9b000b2 GIT binary patch literal 26367 zcmcItdvDvumjB!Q6th?uaQ0Rq#=bUqqjyg$U9y^VRZnk^uZ)(Mk_MixrrU>TnY>vollZkb!1(3t6#w#(Ukd3U_}`dajT`Zokv;48fR zc{w{-JrEobbR6BymlvDWVs+;ofvDMhnh4b&2)>v_w_f`2hDLh(-oCiLkHqb2u~>Zp z4v}oO(b4T>5#!-vauF~1eFD4uVk*`b+YyPPLtF<(aF z%Y2sH3mSE|nLNhR$s!W-hsj+e?j{cp6ER(FHqkVJOn7qOS`mgRCcfkOyrUCvuwhz& z=GHqpUQDK+lIUw9G->?sYPFcp><6cl7=mBk0lU<2XP^QEticSk#BgV%8)-keBng8% zYtYGJzP?^rqPJgOZP)8f6vr2*Z_X#1PxhfsgUa(;c|vHRZ9=lFhpn}}WrJ0NM! z!2Odag!D0=CA5d_Lf}8VNd@vGrdud$ndG$jhr>RN67lv%%(iRc3+VSrtXH@^^riSl z#z8+kO#slT9iW@cX3nhl>*W7LAP@VRr_b&!-U=SU4CAHX0h;~Q6v}0i%33D52muYj zjqi=OJ*+-Q$%>s0zDJ@kcW}Y<7B8&3>0%W}Gy~5@=x|}lg#7NUi5c2m#rtRzlS@zt zIk<|}N%U|NZ74V>1OadVsAzQLsi}s~#giuTo4A?7@tDsu@uX?BA6|;5O?l{hJS~s? zRXnAFD1-d3c*?l65uAvpI7&8mH&gOq`^{~JMBL673;Wd_15voJpT3RnS6}c3KiQO^ zn0;}s;}IfHbPG+)!~%hGBj!tyP#~Mb7@?gQ9=h5j;@$O|-UuGBP83;yP;9y^9+-zB z8?m3AX^h6`*Q@z55pl9veTu|64Ba@=rSSIhi1c(9Pd-O@3$}>^!E@XpJ|Hzj-%|SL zw-JJ>af!<&JmgeBifF9E1bNmny34S?6#2>bBJU~WkIxzY7jysaLOce9 zvrMH;K!|jfW{-i@Sd1IfwdH}5ER=eCS(ufCiC+90CWqu@?vS~NLc`Ec^;462UNZGN zOidbb5)Z4uWg=6?9*ve_k!XoY79@%i4KrJ0YBRK7)Y1L|s)NH3G5vtT1i=fx5L8d2 z+sPJGok5l;G!TJe@969VQAC1Vkjbupc5)-0P=N_QdoU?J@Cn2oft=}MjvNG8jpoQF z4KzspAk-lFgTAICpJPx&JLEYI0}IaxW02;|f12U~r`M0`h#0*FqY(9nkJ^TGK-DZ? zgJl>eUJ>`FXDR2`;2f6qL5eNZM*36f)khuF$Ta{oAz|ln-OLIxX@qAds3r1QAuW0y z3Mw`bKNgA*OM3|9M?>hP6hS&f*+U>v8iHyLLpay#c>;S7q)dZQ+@z?$eRfS~Lwh)M zK*I^4L2HI*B=qg!&`?e#Gs5ghLHL-dfju1h%PH2w8weZPpwM$QCL61LO~PdgM)@=s z$XICrg08dJCUlmvLkp)gU%XEEtP_83RsQ|^xM<~^d0yw=p`T6ESRfgvJp>49b_Jm zRIa=?)#Nq{?YU~xH7wf=Nva0-Uo9h-iqmQVSRR|{i`}PI3+6?gx-7*u>kE=O?IM}K zfEr6O2xSdKR9SB3#2n_j2Qdrj1(wZ?>fneBK_}EQpj8Y)5_!+LKt>xXu~{YgLuA%X zv_`uL0L9ROK8s##*r5~HYk1xPUSc!DYW(kHzL`=dpgv_7qrysg;ETy-nkJq+;OjYR z=qB0Zu$H7zd6?2BPj|-T&C;E`aE!JYR_VZNuQ5@QG{bs(miPEH3sIh{favh{2tiI; z_SV6KdPf&vHB>s7fJcs=j|+uUcU&(+1!L`wig;4P=ktZ)tvj+*TbW?BHDtFOo4XR^ zs1ikCcQT;Lm#~bAvOBJqp+W`P9ToAEI&x<4+}Y{o4&(1dX1w=VzK5l09c$%C(ZrcE z+Oeb;daV4;KvWkU>IrOOt4sVsp;srv8pnExdC2b!VKsrdUK}LA7ZLbffe++$Lgs`S zSs(6d6xQAUE~9DQ-VL#)#aAP&|8=WdqMtg8z9OZWwlJ=^iZ-|29+gD-+syxM@~{5f z;yZ|LECHMS>NqyK)n8&h>moH{zK==FC#>Y($ZvoU5EF=z2%U z7KJt+t@G8JdgXk&-$@FrrLf*MD`#XKbm6?-AdRCHCh_)OhOa)+HqQ$(lxUPteOmpF znVkgCW;ONMm4rqHQxIOI<<_T7dzNSG^p1FrBj4%-G$6o zykS3I=K2D`-K-wd6|HSSr!b%vc(gb5*Sbo0SVIfEYJ^9aZh;RP;i114_^=TkqHlrk zH^L*Bw!jY>;Sn-h;D?Ry2!Ji{_@L9EKZL#(_;DjVg}jytFrG-9Ya@IuxcC)!^Kv z-B)2Iqpv~z*BE^b5vN^<*qOyw5kcl&L&USq-N94Ukgq{lvUO)7W2i8iH`M1hHczW5 z{(Q_<%|%{n?Wtu)%XpqV^3J*=0-Z(6Y$FIzn$MBx9s)d}jh@HZ5Waou-d<+6X85H5 z%%w=Drwt3~HK|W>@}RGBdiJ+8{B1U*ZTK+_KkhV~$>7Nuq`Wx-^1~Y<&KBXtYVkF73~xzjap5VvZY?tZJHeAYQEE?sBTwAF7GK{Qc}DUvS{jEuHUfeP$={Mws*orA_bqUcBAA7P^}$X~9G9x-CNS;cYh zo_1(sPvw!}mMBi;e{}I?B0h+5+7SE5I|HyO0grug7bSe9_zHV5u5X7C&Kwq1Jh=Z|ePNfH(V4jT>hB(0FGNIR;EP zdr0nc@g}zDr5#^1DyJ%aLK2Ao#BNLNcC;z#oQL&U5av53&1P66KwBuZuPQ|rG7Je; zyYlT~1^rDw&%M1TLapBuA%w+excA>k$sn0-C)k-odV=9Td3(>DS^+(GM2V9a6Q?+M z7q2L<5{P%0)B-lTg?H!!0AKE-O=NZzI&Yvg>gg8W zZ&feN7~sXeMKe>ut2GgdjwH}dbR7%;Fvnhf>@|j)V9yxshF6X!ct_XRMmze7^%`6R zdthmM8LT$-7rvE(Mq!g`dUuR?Cq9t!M`H9*9Q}TkL~C3h{g1u?6Zzice@4+7!4aB$ zDr5!mAw?sOPNUEBX>|Ga_#@fol(+M(CsXXA&1U-h--RL8BlV}U z=NVxQbg387MY=lk_OwK_EJ%T8Y$@Lw&<4*$+Y5n_#EuIfhIcC5;)kV%2DIVx(Q3^D z6Qc!sMoVHh+HXMHe=b_Z+=ec8(L=f+cF6Vtj*vZ(mB-ju-NMiU* zBET~#8pv4htZ=?11>la<0b}T91*2yYf%7rO+L#CsLxKTzB7#XH%j`(RI42?vSZEHJ zIC^}CQY4RYc*@z?1QaWLrJ(2v)q9BcHU78~Sdvqp`D%VnZ zO`kJnkyUsyFLdJQWtClqT3l^!;$)s|v6qahUe4`E+-p1k6Adv+Z5&S8$HF2zp?8}N zPY=(`W7N&)h@qI7;_x3%`k8V;8eQnZIjf^#|Nf!E9xJnmskU=uW}H;39|C)(GiFUg zV7ET@onoK1UM*E1f))CQ0gkHG4}nej*q_=o1Qy~Sjw)x#h5joJDJ9r|$0AQ<2mPng zH%jMQh)DmoBnb23XLb>m+8T-c>ha-bwdnnJvzSv0%G@JGFK4$nD@xnreQ_uULzJvY zCLg(Wp*J4Y+2%kb9{}Y;pLD=Lf89TN^UJA-{>%=&fDo!CvjZ(Kp$XP@nekRZG3yQp4~ps8Ol$~)t4pP8#x3W zJ8|FJFxY1}*(585qf&ppO>o!{Ck{0#&E^MM!tkkGagd+deML{ivyf@;d8t6 zgWs4Fz4@EsiC!M{(>A#We+P8zcCuL08W(5uXvMTy&9+mla?_dh<9|kvUsju0EDmxc z=18zOs5=tOLr;G@`Q_I)zX>BH5AtVD>BJG=@CjgZ;&*@lg4g9ZDp5G+s~b!+KKSUt zweWAg{=%tE-0PowTGz{(m!3KQ=NFsH#`|)ARX^zTR^5^qdP8~O~r}H z1S?##oZ>rfI`S2yh<_={JQd+i&=IFIt@O`O`Jj*J4*7T9T4A;8f$z?|v+Em=j<7%Vp?$!sti@{jiOxG;uQu~nf{(;`W||AAk`Gb~wvGRk=?j|Q$NVgw zZ2n+09*CeHO3dApekg(wwzungt{Px4>H`30N6$U!55;iUJYJ1)sy65cMEqzh1&;AH z&aY&sM4<5F+#B}M$pB0`1EIhI0Vq1((|P_7@m95r zmxBOQ5BgXscmYnzhuCRYK)~fxMvu+09E0URDq7eyD4`RG%XQEnjIk-VPkQL#OJGl8 z8*_oK0)zxDatwumk?|PiCx9CvARa&r6zXB90(u9^0zkp~df+1z1$Jh)G%FN2EIB^a4$;T-9I%6Cc706^Lb5&RkfY@4XTP*gFy@qlcihp1YLVBb01m3`>LyObP~J*W{nab@kg z^FvuM(V--3=t(qh3*7PwDvuT|qYHD7NnHx(m_W<`pn?*;6et_^k zg3}|l!7b&u#Z;9-s8eHhGf7e#w*?o{vK;#yE3#nOC|g@na)?d@{U|VAlr4&BD3Hxt zL9np)&HxgcRH$~F0dxjM_J{Tc)DbH2?Ha8~xlI7B^0X5LFcNK15(P?uBo{#Wph5X4 z1wc~hBT2vk8zJmG&0)U8gDX~KDHJk3>;fbWVS@0dVbPUbqEPmLsA0(2Y9x>%3KY2* zke@QeA;WdZx1j_X6mKZGr5@F6POLhKNWl5f;A;fwD@YYVirfN%>LXh>vSBAgMfosn z39vzkDnJcksn8t?vR=^Q+B&HZ*qq2R$`rYJz}W#fHWeg5Adz0EvMKOTfUrC}pooSD z+6WN%a2h$r)*_U-kQeRGZv?Q@L{B1)^hPam1s(9s5ljkP!1PeTl?3Z@flv+z>PDP< z12ivNY9%}K=H5}T4HtuUJH@tP+^!`$1!I(KSa6U}1abtg2oVkoNL;^Ab`tAiP@xrO zF+IGts0$YXTP&(uqKPfM6fj+`ZDsd{Pz4qgjmoy8lN^#cwb-An3J6QIS(6Hf$V5~c zkPAWKSl=|0WZMQT2^qD`=|WV(D9Ff(Br-M%@kX8lpNwzNCz4RWiuT0=Ez3e)Zcsc1 z6NUL*rG-}Tg9BlWj)iFf!d081)q?6X2fYB^fQ3Xegv~=Q0DUmqW)zklRDcqvG~Q*x zj;-k7(}KD%zdJr0A;JU{kZG|&nHS|;0iwY47`B6W&&yFV?vmPE3;}5v4$akQfR#)S z8Z2NaKv;1}(K-={r~u%SWG8phG^)VH@ycRB`j9Hsm<0%zB-=LV4_l$LQNmGxppccY5DPge=hsJjHMycAN8!atpt|Zigka+Q}^K_5aL9#6c*uYfcTNM(HS7omSWzeb#eJiT5dT0r>r*>WW>F})-VlgbS$c2l; zHYkpMSj`sLb^(Gu!bKoMUY2I1Y{5cYNjYT}RVYMS0f`I2+Q8bES5VzNm1|y;xB>)! zS&qp?Msn%OKKpj5N*mjH@9qaMW)n8G;MqvhpLfdyJWs3EAK8kxd=i3t~jt!wt9wqQUN zvY`ko0$E-w7v{IJbzWvR8mvu{4hC&2Sto9GD=l+P_eSssg%Y-48!jJQEE>LRip7E^ ztW?zF?1jR#fT93l=`KLw*9+QmHdn|d-BE=s!Fw|b>TCuST+w)i2w<`;0f!f)sXl%3C`gK$F8Cxtm41lJUf9;19D~pDYR= zv5vCuZ*8C7ihu1DnG_vh)VTQaHC@Gj9Blu1^|!}`6TzXb?C z#K>2I|9++WS&TlO(MLq{Lw~(<9`uKZ(V7za7(!LIEerfLW9PG5@N!u&@A@;VTQDd7;-yupcM6%QD|EJQ2yv?L5#6l zmbB%f5g;3N?v2q#8My78GJ>2AYNeovD11jMLl6e6yQsZ`IDw0y60H~%+t3bEumk)fC>^tM>ysKj zc6up`OAavti+C!a4tm&R^KuH%oQF{92W6~Q3Y0{_BWZyG3WUD{`~zBzNd7KO8gL4> zvII~((&B}Cb-=$1383zUP<6+;gAJg1>WeYgum?iETmV6cuXnW4=;Q*ZLa>!_pvAuv z7eGG`?HM53N6GhqMLfSny zW)=3xQ8ZCljip4rT&wtsh!yRSwc3d!+HHY$8h^U0qG=~_0n1rhf5Cve397GXJbj0! z@tLGD_3a{B;#({A^|%F(q8vhKP8jHBfTjWW>oUn&CbgRg+8?72Do0paSdy}_>>x}! zwFsFl^`jUE)@5)G`pdyPutZ7&rXcAd%2NG`zR>^|nFE&U;)*Bp#v$ln&La8%Tu z&%uD*Jp2i)%msVLPPkB1K0)Ut9AxXe!9ZJdsxgqhcY|ZhyHF~)fZgr{gDHutFnk{u m7wLK_JvGYvL+Nk*udyo>|8p2@GRLv54Xz6RtC?(V>HR-T908^P literal 0 HcmV?d00001 diff --git a/Tests/images/non_zero_bb.png b/Tests/images/non_zero_bb.png new file mode 100644 index 0000000000000000000000000000000000000000..156c9a091ec1e758906761dd85071cba04770a41 GIT binary patch literal 2544 zcmbW3`8yN}7sp2~CPau~7#XsZBHcm8CHqhr4Oz05b?lNQOQMY346=@G7MHmck`gf! zDZ7k@Y-7pg4jNKsY{~1sKfHgy`#hiX{hsrj-@ZSb=cM02B2EZ`1pxrS2`fu8TL6Fu zeboQ_jrT|?*OUZ~O!UU}o90Ij03043)sY?^GGkf}0D$9VR%XUG@4K^^JlI9GlYAqi zwHg;k%ia$0MJ)i7Df^ZFeBHmqPr{FXU>6b)pNvZiE2vZveMq*7q6$=5d3gtKpP#&*?U2zcoEs;fY?PR|el8SwB zkx!vVwrSRUw_{|QOmph=^|Itq`Z`TGAP+37wHU;QeQ0E!7@?$iMo9-=r~;UUH|iRP zB4!gdc*+0-#Va^&>Lg`OKTBQgja>r*{ukInVhZX|$J2f9UG+l1JO&k)#w0`Rx~ew= zZpSAdSa!0C#iO#NBxC!6`myn?UPio3k?Ha*m8I=fVehBr2Kq){g=q^_>7?@6zxV@C z#A(UrT-S)bKW7^F-A@Hmxau@@g;7KPD!Dccf zDvj3lk~Ork!&ZiMeZZts8jRz#_l2|-Bzp_{;O&br*EH@j5d6-Wqu!k8)H|^*dG|9h z``8K2vN(AADs-Z7syb1Oqm7Q}T8yTh9ofAh417}yBvoMRA0ySRsb1ju8xh6#-sc!I zK*Ww?*)mN+BI%Av4QZ)>$vbw1FDeHi4t-E94*9yuON4Jz!lj zKRl3`ppBQ~t_L6tK#&+V1)`uIm#N+{9QQz;a~y3;RGISjf^a+{F^ja|PnJ6O=O@x* z!TiGg}32)CZg7Zd$P@e;YXMj zRj~e1U^AOFG(-7t>DIalFAI6iIlZ);ONcece)~|scs5-yk`5m%m7jGahGyF+Fy0xA z*McI$BXdqSvPohak#s)Fc^sJ)v;I5TYKtm|u03}OuU+M%0X5Y2Ok_ORoI=Gr9AUCP@o$`g^63Qj-Q43aS~XJZ-YLrK8Ez1ZVUzw1dvfitNPaA(G7n~TE>`KyQlQ9Kpe#AAc z?7X9yT6uq=6e+Yhh(m6PP?sBrjZ@{}FZ@ODSzLs>!BgweNz3?@lvHF~K!)<3&1tvP zcJ}t@1K8;DrwaU+SKY-eNHd%)>)SCrYW;alwbRe_bk+~u(wFs*HNGSWSlD|P?cI&C9}0MI&pgkZA{gq2!n_J_OG;^7 zC#7MP-98KH`JFX+HD!_7F=sSg<123{hGKye!Xza`Yzf;cPL5-LLZ&XaUHK+46I5#4 zyLz>GI$CEv*tOgQM?#Se3CtVeRPv!}(r46l+Sr z!|1A*6d^TW65EC)I&zc2yR6ILgWtrgXQb#`?{y0DIWy}(#IjAG@fO)fDB^7kwZz6q zXft(n@5^uF96tQ7@E(X zmOG`@ZHjJ>shw%VVtg+9)2|h!HD#$>h<{e1tnyyzFr3o6mzo~k@-QK@B2K+Rfh)?6Ww7zQG0wfZRd zkEzoamsC!L7`BCsA%aTc6*(M}%0|pXkqiA+$-&4hWHfOWEj-@%m(saPTizl~GxrCyQGzg7{x zW7-`bw7YY+9a{T`%!TvUtIn4}dfd&diZ5#@iz}j*gW#{uUpG-C#kY4VUXmFaG|3yG zr*u$&LjD$+Xxj|II|KbyxB7ZyWjY`O*NxOO^Ooc78;f3B4Hs+rD2vs34)h;S7cI8? z`-!U^j*>3prHNn5ozKtjKHeJ}WM6#76sPMHqXP!HKL^bOHJ*A1bZPf~FPhKN{=UP> z-B$bk9|*tfva5XJCq%`!IwhN1CBZio3g|29A3?+( zDc+0ZL%Fq$qx1IJ!oA)!-1{@PXcju`E#40ix8}VVss-54{(f<0 lwNqucL;L%`$^4JKhtjv3krrcRJ^y}gR^~{vdXw9a{sX*_;w1n8 literal 0 HcmV?d00001 diff --git a/Tests/images/non_zero_bb_scale2.png b/Tests/images/non_zero_bb_scale2.png new file mode 100644 index 0000000000000000000000000000000000000000..2600580b3035e333bab01284fc5f6a50e5ba66a0 GIT binary patch literal 5489 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 literal 0 HcmV?d00001 diff --git a/Tests/images/zero_bb.eps b/Tests/images/zero_bb.eps new file mode 100644 index 0000000000000000000000000000000000000000..e931bf8336301d10382d64ae2389c114ad9357d3 GIT binary patch literal 26359 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 Date: Wed, 20 Nov 2013 01:14:51 -0600 Subject: [PATCH 26/49] =?UTF-8?q?Added=20documentation=20for=20the=20newly?= =?UTF-8?q?=20added=20scale=20argument=20to=20EPS=E2=80=99s=20load()=20fun?= =?UTF-8?q?ction.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/handbook/image-file-formats.rst | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 93edebf4e..d0595711e 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -36,6 +36,20 @@ PIL identifies EPS files containing image data, and can read files that contain embedded raster images (ImageData descriptors). If Ghostscript is available, other EPS files can be read as well. The EPS driver can also write EPS images. +If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load` +method with the following parameter to affect how Ghostscript renders the EPS + +**scale** + Affects the scale of the resultant rasterized image. If the EPS suggests + that the image be rendered at 100px x 100px, setting this parameter to + 2 will make the Ghostscript render a 200px x 200px image instead. The + relative position of the bounding box is maintained:: + + im = Image.open(...) + im.size #(100,100) + im.load(scale=2) + im.size #(200,200) + GIF ^^^ @@ -273,19 +287,19 @@ format are currently undocumented. The :py:meth:`~PIL.Image.Image.save` method supports the following options: -**lossless** +**lossless** If present, instructs the WEBP writer to use lossless compression. -**quality** +**quality** Integer, 1-100, Defaults to 80. Sets the quality level for lossy compression. -**icc_procfile** +**icc_procfile** The ICC Profile to include in the saved file. Only supported if the system webp library was built with webpmux support. -**exif** +**exif** The exif data to include in the saved file. Only supported if the system webp library was built with webpmux support. From 9cd654917df36214ff36fb38ac4d109b6e66f319 Mon Sep 17 00:00:00 2001 From: Esteban Santana Santana Date: Wed, 20 Nov 2013 01:32:06 -0600 Subject: [PATCH 27/49] Changed the Ghostscript() function inside of EpsImagePlugin to use subprocess.Popen() instead of the deprecated os.popen(). --- PIL/EpsImagePlugin.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py index 1c9c4b6a1..4acd72f33 100644 --- a/PIL/EpsImagePlugin.py +++ b/PIL/EpsImagePlugin.py @@ -65,7 +65,7 @@ def Ghostscript(tile, size, fp, scale=1): bbox = [bbox[0], bbox[1], bbox[2] * scale, bbox[3] * scale] #print("Ghostscript", scale, size, orig_size, bbox, orig_bbox) - import tempfile, os + import tempfile, os, subprocess file = tempfile.mktemp() @@ -77,30 +77,28 @@ def Ghostscript(tile, size, fp, scale=1): "-dNOPAUSE -dSAFER", # don't pause between pages, safe mode "-sDEVICE=ppmraw", # ppm driver "-sOutputFile=%s" % file,# output file - "- >/dev/null 2>/dev/null"] + ] if gs_windows_binary is not None: if gs_windows_binary is False: raise WindowsError('Unable to locate Ghostscript on paths') command[0] = gs_windows_binary - command[-1] = '- >nul 2>nul' - - command = " ".join(command) # push data through ghostscript try: - gs = os.popen(command, "w") + gs = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) # adjust for image origin if bbox[0] != 0 or bbox[1] != 0: - gs.write("%d %d translate\n" % (-bbox[0], -bbox[1])) + gs.stdin.write("%d %d translate\n" % (-bbox[0], -bbox[1])) fp.seek(offset) while length > 0: s = fp.read(8192) if not s: break length = length - len(s) - gs.write(s) - status = gs.close() + gs.stdin.write(s) + gs.stdin.close() + status = gs.wait() if status: raise IOError("gs failed (status %d)" % status) im = Image.core.open_ppm(file) From c6c32804d0e42307907e620120f7035b8249f5fd Mon Sep 17 00:00:00 2001 From: Esteban Santana Santana Date: Wed, 20 Nov 2013 01:44:38 -0600 Subject: [PATCH 28/49] Added ghostscript dependency. Travis needs a list of packages to install and we need the ghostscript package to successfully run our EPS test suite. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 88118b1b2..fdc920ac2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ python: - 3.2 - 3.3 -install: "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev" +install: "sudo apt-get -qq install libfreetype6-dev liblcms2-dev libwebp-dev ghostscript" script: - python setup.py clean From 7797633d7a99351a87e6ac29457375da243067bb Mon Sep 17 00:00:00 2001 From: Esteban Santana Santana Date: Wed, 20 Nov 2013 02:14:29 -0600 Subject: [PATCH 29/49] Loosened testing rigor for EPS files. Due to differences in rendering between platforms (mainly antialiasing), we had to lower the rigor with which we test our EPS handling. Instead of making the test fail if the rendered image does not exactly match, we now only fail if the images are grossly divergent. --- Tests/test_file_eps.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 8d9b67132..51972a7a8 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -47,18 +47,18 @@ def test_render_scale1(): skip("zip/deflate support not available") #Zero bounding box - image1 = Image.open(file1) - image1.load() - image1_compare = Image.open(file1_compare).convert("RGB") - image1_compare.load() - assert_image_equal(image1, image1_compare) + image1_scale1 = Image.open(file1) + image1_scale1.load() + image1_scale1_compare = Image.open(file1_compare).convert("RGB") + image1_scale1_compare.load() + assert_image_similar(image1_scale1, image1_scale1_compare, 5) #Non-Zero bounding box - image2 = Image.open(file2) - image2.load() - image2_compare = Image.open(file2_compare).convert("RGB") - image2_compare.load() - assert_image_equal(image2, image2_compare) + image2_scale1 = Image.open(file2) + image2_scale1.load() + image2_scale1_compare = Image.open(file2_compare).convert("RGB") + image2_scale1_compare.load() + assert_image_similar(image2_scale1, image2_scale1_compare, 10) def test_render_scale2(): #We need png support for these render test @@ -67,16 +67,16 @@ def test_render_scale2(): skip("zip/deflate support not available") #Zero bounding box - image1 = Image.open(file1) - image1.load(scale=2) - image1_compare = Image.open(file1_compare_scale2).convert("RGB") - image1_compare.load() - assert_image_equal(image1, image1_compare) + image1_scale2 = Image.open(file1) + image1_scale2.load(scale=2) + image1_scale2_compare = Image.open(file1_compare).convert("RGB") + image1_scale2_compare.load() + assert_image_similar(image1_scale2, image1_scale2_compare, 5) #Non-Zero bounding box - image2 = Image.open(file2) - image2.load(scale=2) - image2_compare = Image.open(file2_compare_scale2).convert("RGB") - image2_compare.load() - assert_image_equal(image2, image2_compare) + image2_scale2 = Image.open(file2) + image2_scale2.load(scale=2) + image2_scale2_compare = Image.open(file2_compare).convert("RGB") + image2_scale2_compare.load() + assert_image_similar(image2_scale2, image2_scale2_compare, 10) From 155239cbc39458a8f01942829c2fb7f19ff4bd1a Mon Sep 17 00:00:00 2001 From: Esteban Santana Santana Date: Wed, 20 Nov 2013 02:22:04 -0600 Subject: [PATCH 30/49] Copy paste issue on that last commit. --- Tests/test_file_eps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 51972a7a8..c1777a12a 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -69,14 +69,14 @@ def test_render_scale2(): #Zero bounding box image1_scale2 = Image.open(file1) image1_scale2.load(scale=2) - image1_scale2_compare = Image.open(file1_compare).convert("RGB") + image1_scale2_compare = Image.open(file1_compare_scale2).convert("RGB") image1_scale2_compare.load() assert_image_similar(image1_scale2, image1_scale2_compare, 5) #Non-Zero bounding box image2_scale2 = Image.open(file2) image2_scale2.load(scale=2) - image2_scale2_compare = Image.open(file2_compare).convert("RGB") + image2_scale2_compare = Image.open(file1_compare_scale2).convert("RGB") image2_scale2_compare.load() assert_image_similar(image2_scale2, image2_scale2_compare, 10) From 64e53de5c9f8c26e07863f943546b147a767ac7a Mon Sep 17 00:00:00 2001 From: Esteban Santana Santana Date: Wed, 20 Nov 2013 02:25:50 -0600 Subject: [PATCH 31/49] Another typo. --- Tests/test_file_eps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index c1777a12a..75570f944 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -76,7 +76,7 @@ def test_render_scale2(): #Non-Zero bounding box image2_scale2 = Image.open(file2) image2_scale2.load(scale=2) - image2_scale2_compare = Image.open(file1_compare_scale2).convert("RGB") + image2_scale2_compare = Image.open(file2_compare_scale2).convert("RGB") image2_scale2_compare.load() assert_image_similar(image2_scale2, image2_scale2_compare, 10) From 3d1cd63afdc5460ed9029b5c2e0bd9aae67fbfc9 Mon Sep 17 00:00:00 2001 From: Esteban Santana Santana Date: Wed, 20 Nov 2013 02:41:35 -0600 Subject: [PATCH 32/49] We need to write bytes not strings. Python3 compatibility issue, we need to write bytes to buffers and pipes, not strings. This should still work in python2.6+. --- PIL/EpsImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py index 4acd72f33..5e53fc387 100644 --- a/PIL/EpsImagePlugin.py +++ b/PIL/EpsImagePlugin.py @@ -89,7 +89,7 @@ def Ghostscript(tile, size, fp, scale=1): gs = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) # adjust for image origin if bbox[0] != 0 or bbox[1] != 0: - gs.stdin.write("%d %d translate\n" % (-bbox[0], -bbox[1])) + gs.stdin.write(b"%d %d translate\n" % (-bbox[0], -bbox[1])) fp.seek(offset) while length > 0: s = fp.read(8192) From 8d27167fc7169dbefb904d22016d155a58be01f9 Mon Sep 17 00:00:00 2001 From: Esteban Santana Santana Date: Wed, 20 Nov 2013 02:54:31 -0600 Subject: [PATCH 33/49] More python3 fixes. Changed the way the translate command that is passed to ghostscript is generated. This should now work on both python2.6+ and python3. --- PIL/EpsImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/EpsImagePlugin.py b/PIL/EpsImagePlugin.py index 5e53fc387..a8706b05f 100644 --- a/PIL/EpsImagePlugin.py +++ b/PIL/EpsImagePlugin.py @@ -89,7 +89,7 @@ def Ghostscript(tile, size, fp, scale=1): gs = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) # adjust for image origin if bbox[0] != 0 or bbox[1] != 0: - gs.stdin.write(b"%d %d translate\n" % (-bbox[0], -bbox[1])) + gs.stdin.write(("%d %d translate\n" % (-bbox[0], -bbox[1])).encode('ascii')) fp.seek(offset) while length > 0: s = fp.read(8192) From c6040f618d8f2706a7b46d1cdf37d1a587f9701f Mon Sep 17 00:00:00 2001 From: Andrew Stromnov Date: Thu, 28 Nov 2013 16:58:43 +0400 Subject: [PATCH 34/49] fix compiling with FreeType 2.5.1 --- _imagingft.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/_imagingft.c b/_imagingft.c index 47d50bdca..f19555be2 100644 --- a/_imagingft.c +++ b/_imagingft.c @@ -59,7 +59,11 @@ struct { const char* message; } ft_errors[] = +#if defined(USE_FREETYPE_2_1) +#include FT_ERRORS_H +#else #include +#endif /* -------------------------------------------------------------------- */ /* font objects */ From e3ee9882ae6e4cf9759c21688fd27ed53933a390 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 28 Nov 2013 08:26:52 -0500 Subject: [PATCH 35/49] Add history --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f6a166922..c9d8fec1d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ +- Fix compiling with FreeType 2.5.1 + [stromnov] + - Adds directories for NetBSD. [deepy] From 2729401cc341668410f7d7b49971ce2fe6600e31 Mon Sep 17 00:00:00 2001 From: Cezar Sa Espinola Date: Fri, 29 Nov 2013 14:33:57 -0200 Subject: [PATCH 36/49] Fixed memory leak saving images as webp when webpmux is available --- _webp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_webp.c b/_webp.c index 2dab29fde..6381e1a56 100644 --- a/_webp.c +++ b/_webp.c @@ -113,11 +113,11 @@ PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args) WebPMuxAssemble(mux, &output_data); WebPMuxDelete(mux); + free(output); - output = (uint8_t*)output_data.bytes; ret_size = output_data.size; if (ret_size > 0) { - PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size); + PyObject *ret = PyBytes_FromStringAndSize((char*)output_data.bytes, ret_size); WebPDataClear(&output_data); return ret; } From 417af4f142bce678184432d861d3c8dce07db1f7 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 29 Nov 2013 18:08:53 -0500 Subject: [PATCH 37/49] Add history --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c9d8fec1d..806c01ec4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ +- Fixed memory leak saving images as webp when webpmux is available + [cezarsa] + - Fix compiling with FreeType 2.5.1 [stromnov] From 258e7fe841310648ae333f075e5e8026c13fea42 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 29 Nov 2013 18:16:43 -0500 Subject: [PATCH 38/49] Add history --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 806c01ec4..8b14668c5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ +- Documentation fixes + [wiredfool] + - Fixed memory leak saving images as webp when webpmux is available [cezarsa] From 613b82463027b82efb5571656a217de4b6d11163 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 29 Nov 2013 19:03:12 -0500 Subject: [PATCH 39/49] Add history --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8b14668c5..780305eaa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ +- Fixed Viewer.show to return properly + [tmccombs] + - Documentation fixes [wiredfool] From 0365344fe61bcac7f357d768d13cf10f1910baad Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 29 Nov 2013 19:07:47 -0500 Subject: [PATCH 40/49] Add history --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 780305eaa..cab4dffad 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ +- Quote filenames and title before using on command line + [tmccombs] + - Fixed Viewer.show to return properly [tmccombs] From 6d9f34914021951bba42ffe5b6cd80147e7f538f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 30 Nov 2013 10:06:21 -0800 Subject: [PATCH 41/49] replacing lcms1 with lcms2 --- docs/installation.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index b28c74b56..4c035a071 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -60,9 +60,13 @@ Many of Pillow's features require external libraries: * **littlecms** provides color management + * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and + above uses liblcms2. Tested with **1.19** and **2.2**. + * **libwebp** provides the Webp format. - * Pillow has been tested with version **0.1.3**, which does not read transparent webp files. Version **0.3.0** supports transparency. + * Pillow has been tested with version **0.1.3**, which does not read + transparent webp files. Version **0.3.0** supports transparency. * **tcl/tk** provides support for tkinter bitmap and photo images. @@ -101,13 +105,13 @@ Or for Python 3:: Prerequisites are installed on **Ubuntu 10.04 LTS** with:: $ sudo apt-get install libtiff4-dev libjpeg62-dev zlib1g-dev \ - libfreetype6-dev liblcms1-dev tcl8.5-dev tk8.5-dev + libfreetype6-dev tcl8.5-dev tk8.5-dev Prerequisites are installed with on **Ubuntu 12.04 LTS** or **Raspian Wheezy 7.0** with:: $ sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \ - libfreetype6-dev liblcms1-dev libwebp-dev tcl8.5-dev tk8.5-dev + libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev Mac OS X installation --------------------- From 9b96aa4748e80851444569299a615b67e438a25e Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 30 Nov 2013 15:44:20 -0500 Subject: [PATCH 42/49] Add history --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index cab4dffad..acea664b1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ +- Save arbitrary tags in Tiff image files + [wiredfool] + - Quote filenames and title before using on command line [tmccombs] From 28f22fc908c85a6d6cd8d950c4ffc64cb2d5c01e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 4 Dec 2013 20:59:33 -0800 Subject: [PATCH 43/49] Return positive size for > 2gpx images, fixes #436 --- Tests/large_memory_test.py | 27 +++++++++++++++++++++++++++ _imaging.c | 3 ++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Tests/large_memory_test.py diff --git a/Tests/large_memory_test.py b/Tests/large_memory_test.py new file mode 100644 index 000000000..148841ec2 --- /dev/null +++ b/Tests/large_memory_test.py @@ -0,0 +1,27 @@ +from tester import * + +# This test is not run automatically. +# +# It requires > 2gb memory for the >2 gigapixel image generated in the +# second test. Running this automatically would amount to a denial of +# service on our testing infrastructure. I expect this test to fail +# on any 32 bit machine, as well as any smallish things (like +# raspberrypis). It does succeed on a 3gb Ubuntu 12.04x64 VM on python +# 2.7 an 3.2 + +from PIL import Image +ydim = 32769 +xdim = 48000 +f = tempfile('temp.png') + +def _write_png(xdim,ydim): + im = Image.new('L',(xdim,ydim),(0)) + im.save(f) + success() + +def test_large(): + """ succeeded prepatch""" + _write_png(xdim,ydim) +def test_2gpx(): + """failed prepatch""" + _write_png(xdim,xdim) diff --git a/_imaging.c b/_imaging.c index e792ebfa5..3510e1568 100644 --- a/_imaging.c +++ b/_imaging.c @@ -1250,6 +1250,7 @@ _putdata(ImagingObject* self, PyObject* args) image = self->image; + // UNDONE Py_ssize_t 2Gpix image issue n = PyObject_Length(data); if (n > (int) (image->xsize * image->ysize)) { PyErr_SetString(PyExc_TypeError, "too many data entries"); @@ -3073,7 +3074,7 @@ image_length(ImagingObject *self) { Imaging im = self->image; - return im->xsize * im->ysize; + return (Py_ssize_t) im->xsize * im->ysize; } static PyObject * From f987d7469564e33e2a331f586928f61d54d9845b Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 4 Dec 2013 22:07:11 -0800 Subject: [PATCH 44/49] More Py_ssize_t changes --- _imaging.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/_imaging.c b/_imaging.c index 3510e1568..82c53a40d 100644 --- a/_imaging.c +++ b/_imaging.c @@ -1234,7 +1234,8 @@ static PyObject* _putdata(ImagingObject* self, PyObject* args) { Imaging image; - int n, i, x, y; + // i & n are # pixels, require py_ssize_t. x can be as large as n. y, just because. + Py_ssize_t n, i, x, y; PyObject* data; double scale = 1.0; @@ -1244,17 +1245,16 @@ _putdata(ImagingObject* self, PyObject* args) return NULL; if (!PySequence_Check(data)) { - PyErr_SetString(PyExc_TypeError, must_be_sequence); - return NULL; + PyErr_SetString(PyExc_TypeError, must_be_sequence); + return NULL; } image = self->image; - // UNDONE Py_ssize_t 2Gpix image issue n = PyObject_Length(data); - if (n > (int) (image->xsize * image->ysize)) { - PyErr_SetString(PyExc_TypeError, "too many data entries"); - return NULL; + if (n > (Py_ssize_t) (image->xsize * image->ysize)) { + PyErr_SetString(PyExc_TypeError, "too many data entries"); + return NULL; } if (image->image8) { @@ -1649,7 +1649,7 @@ _stretch(ImagingObject* self, PyObject* args) imIn = self->image; /* two-pass resize: minimize size of intermediate image */ - if (imIn->xsize * ysize < xsize * imIn->ysize) + if ((Py_ssize_t) imIn->xsize * ysize < (Py_ssize_t) xsize * imIn->ysize) imTemp = ImagingNew(imIn->mode, imIn->xsize, ysize); else imTemp = ImagingNew(imIn->mode, xsize, imIn->ysize); From 679f281f01136f888da2ac862779d27a1ab29e61 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 5 Dec 2013 05:10:06 -0500 Subject: [PATCH 45/49] Add history --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index acea664b1..86ed026cd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.3.0 (2014-01-01) ------------------ +- 2gigapix image fixes + [wiredfool] + - Save arbitrary tags in Tiff image files [wiredfool] From 18fe7ee519c31c4721d29d1cd8ad4d8dfb7fd327 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 5 Dec 2013 05:15:58 -0500 Subject: [PATCH 46/49] Prepare for 2.3.0 release in < 30 days --- _imaging.c | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_imaging.c b/_imaging.c index 82c53a40d..dcb063081 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.2.1" +#define PILLOW_VERSION "2.3.0" #include "Python.h" diff --git a/setup.py b/setup.py index 2bc5ca3f6..b74904014 100644 --- a/setup.py +++ b/setup.py @@ -82,7 +82,7 @@ except ImportError: NAME = 'Pillow' -VERSION = '2.2.1' +VERSION = '2.3.0' TCL_ROOT = None JPEG_ROOT = None ZLIB_ROOT = None From 506817a4c3aec94fa0843cc48503669ef28fd595 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 5 Dec 2013 05:24:05 -0500 Subject: [PATCH 47/49] Fix manifest --- MANIFEST.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index bfad0f9ad..3769b645e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -27,6 +27,8 @@ recursive-include Sane README recursive-include Scripts *.py recursive-include Scripts README recursive-include Tests *.bin +recursive-include Tests *.eps +recursive-include Tests *.gnuplot recursive-include Tests *.icm recursive-include Tests *.jpg recursive-include Tests *.pcf @@ -42,8 +44,11 @@ recursive-include Tk *.c recursive-include Tk *.txt recursive-include docs *.bat recursive-include docs *.gitignore +recursive-include docs *.html recursive-include docs *.py recursive-include docs *.rst +recursive-include docs *.txt +recursive-include docs Guardfile recursive-include docs Makefile recursive-include docs BUILDME recursive-include docs COPYING From 79603af5285289dc0c382be3510fe80e4484cd13 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 5 Dec 2013 05:29:18 -0500 Subject: [PATCH 48/49] Too many contributors to effectively list This list was outdated so I'm removing it. At some point I'll do another pass --- CONTRIBUTORS.rst | 36 ------------------------------------ setup.py | 3 +-- 2 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 CONTRIBUTORS.rst diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst deleted file mode 100644 index 56efba356..000000000 --- a/CONTRIBUTORS.rst +++ /dev/null @@ -1,36 +0,0 @@ -Contributors (Pillow) -===================== - -.. Note:: Contributors: please add your name here - -- Alex Po -- Anton Vlasenko -- Brian J. Crowell -- Bryant Mairs -- Christoph Gohlke -- Corey Richardson -- Daniel Hahler -- David Schmidt -- Eliot -- etienne -- Jannis Leidel -- Kyle MacFarlane -- Lars Yencken -- Liu Qishuai -- Manuel Ebert -- Marc Abramowitz -- Matti Picus -- Mikhail Korobov -- OCHIAI, Gouji -- Oliver Tonnhofer -- Phil Elson -- Sandro Mani -- Simon Law -- Stéphane Klein -- Steve Johnson -- Takeshi KOMIYA -- Tom Gross -- Tom Payne -- Tyler Garner -- tdesvenain -- wiredfool diff --git a/setup.py b/setup.py index b74904014..5f3d22e56 100644 --- a/setup.py +++ b/setup.py @@ -587,8 +587,7 @@ setup( description='Python Imaging Library (Fork)', long_description=( _read('README.rst') + b'\n' + - _read('CHANGES.rst') + b'\n' + - _read('CONTRIBUTORS.rst')).decode('utf-8'), + _read('CHANGES.rst')), author='Alex Clark (fork author)', author_email='aclark@aclark.net', url='http://python-imaging.github.io/', From 404b245d891e7c89ac2c7fce1506ca78110b1a1d Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 5 Dec 2013 05:32:33 -0500 Subject: [PATCH 49/49] Fix travis --- PIL/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/__init__.py b/PIL/__init__.py index 6ebe41ff6..18bd42a5f 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.2.1' # Pillow +PILLOW_VERSION = '2.3.0' # Pillow _plugins = ['ArgImagePlugin', 'BmpImagePlugin',