From 1b338998dca28e7a170367c8d9014d6cf23f1379 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 28 Sep 2015 22:24:45 +0300 Subject: [PATCH 01/46] Test a png file with iTXt chunks --- Tests/images/itxt_chunks.png | Bin 0 -> 69498 bytes Tests/test_pickle.py | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 Tests/images/itxt_chunks.png diff --git a/Tests/images/itxt_chunks.png b/Tests/images/itxt_chunks.png new file mode 100644 index 0000000000000000000000000000000000000000..ca098440c15ba6b77aa4b656936dad05d01f1306 GIT binary patch literal 69498 zcmeI5du$X{6vk(_TRYtr8K|YJ0U3D+YAGxJVevtywAdDbRzpfLeQbfMBs?0r0jZeo z)B+(Itfai8yc9_k1r-H?h)ZoWAfYH?Q88{~M3Jf+35Zg_+1<|BdEA{CQ;pz9LO5Nz zGqb-r_dAb!=kBtiNqN1TH#s?u>s64S`!L5PdN?lOC~sF@Ir!z-KFU9d)%jCvI4&tA z^w-9{(CkuPOz;*=9GCD-Vrue@x2$u?Ta=du%$!`}&6zuUcGVoO@*~IP%&V&OR?QF> z%=A7k<`qmVnvqeJz;Pm1kefaE30vn^2PWn`B;-6Ky)7)eye(&ZpX~Iqu|GF=KKk2J zUGs~_RP1P9I?!f!@Pa7$IF@m@WB;7irWMTp>wcSDXN%jsAjbQ{zY}Qm$-$y8UhEs| zEXr@Wh1xia>u_A3=;C{?9C17ABGvcBZN3|qL3a~0v@Q)Apb<;u;2ZD__y!`ZHn#u` z(1?{x!Z+X>@QqNRfvQcdJwZd8LxKiqpxYB_tD*O$t*k%;G^jyoFyI^T4TDQi5QT5R zH{cuL>9c>OS-oiBq1%7Fm+u;L^Dn`*`=1X_|DEi6Vd0p$@vBxDCj%S%1$(!6-)re( zF}+t}i|eS}6MLS~!EcFAZa7`LcyO%3T6ymq_Ul^CRa>1VKgq=(JLk+J;V>H>YN?Mt7rIF0m( zUs|($N@gtA51s7}k679}&FVC7`($_7j%sbCYP?g$ereCm8CIv6X^X4eTi#*#2)fh0 z=98=MFSR<&r^*e4qv#3!bB^->*z< zCoXUlIEtc|NNly$dUr{H3P@KhgbBe>;3%T>&s9LEdeBob7B$FGkfR_+F^-BzE{_}q zIf~tZE-iKO$v}>x?^=h!z){FQ$bh53QIxU+6NHQ`6BC5ymMI(sjv|Oya=<7^u;joz z_xN`mrKxu0X>~1tZona5LB4`~#a#A>UO~Qse8px*80xt&gwTT}lEzFA6_9RJL^??b zJqSGrJ^IxVa@P5uPPFbC?0j+fz#E>aFG_b7zB!y9de^9R-`4%SWZJ^r>t8v2{M4!N z^8xZqn_T_J!ZOQ6{HX%JV`&OI_lvAg!BNbyKQws?F5p4}?Z-GWEHsL=6u?EV->T4p z3%IC703r!fIuK#mLj+vFMU#0UlCa$YT)+hp7PUQx zXV7Y25l^`!HrGu{8=^^rbZiw0L5HK|u@0+^Mg04GQs$M`jC@+RYBmCid_!A{<<24t zl_u-uWo3=V3(#DfKSkg-E*@!h8_y~qx+~>fk?AUtn<@_z;V=yU7T_>3>Ss6%d&t3I z;4mc52uXxOZy+SGGbT8U-cN`U9EMQn3^)uNM&l5ulGr6As-zetBpl`%ahR?<>vHr> z3;CPj!QU#bT>9|+Q-x~xL<0pxH_C!~{eS`}3IXIS<{6)!f)ZGh7D)#EFU4;^d*z3V{qrJu z-oBi`#f$sC-CSM}&HE%VkWlH%(7a+w5;ygA3&p?gWea#DFSXm^a1cX^4kl)$0~0Wj zpeIrJRmB!ez{Ku=JVG8JkA^%dA ziC);i(jzQA!qTH2U3!G&M*sbN3>I9RKOv4wZj~l{*ZYr;2kmwpC@6m^Jm)8m_e&)Q z&scpWC+|pZurYDw_dMuO4l6o|l!)fIn-u+zhy%TUdVJJN3dCPsj63%4Wb1SFE$RSbvGK88D#e zAp%O&o5^Maj7#V;9D3OUA^lKRjBcK3^LIC|*hDrho fp!{E(*cV>iHe~go4Fe`~%AbO9lXAC@_0<0foeHn% literal 0 HcmV?d00001 diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index bdfd3582d..f24cff612 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -67,7 +67,8 @@ class TestPickle(PillowTestCase): "Tests/images/non_zero_bb.png", "Tests/images/non_zero_bb_scale2.png", "Tests/images/p_trns_single.png", - "Tests/images/pil123p.png" + "Tests/images/pil123p.png", + "Tests/images/itxt_chunks.png" ]: self.helper_pickle_string(pickle, test_file=test_file) From 9dbaf92f3e2673fb9ad30643d7e0acb383ec67ca Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 29 Sep 2015 08:36:07 +0300 Subject: [PATCH 02/46] itxt_chunks.png fails with protocol v2 --- Tests/test_pickle.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index f24cff612..b6f5a5ae9 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -70,7 +70,10 @@ class TestPickle(PillowTestCase): "Tests/images/pil123p.png", "Tests/images/itxt_chunks.png" ]: - self.helper_pickle_string(pickle, test_file=test_file) + for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): + self.helper_pickle_string(pickle, + protocol=protocol, + test_file=test_file) def test_pickle_l_mode(self): # Arrange From f8df6d1687c2eba376b03b60a59b63c390c7d810 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 2 Jan 2019 18:56:54 +1100 Subject: [PATCH 03/46] Fixed pickling of iTXt class with protocol > 1 --- src/PIL/PngImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 04161a56c..57d2f8341 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -192,7 +192,7 @@ class iTXt(str): """ @staticmethod - def __new__(cls, text, lang, tkey): + def __new__(cls, text, lang=None, tkey=None): """ :param cls: the class to use when creating the instance :param text: value for this key From c0f4382af275a808d88f7de0100ec1ab32c59e3c Mon Sep 17 00:00:00 2001 From: cgohlke Date: Sun, 6 Jan 2019 19:49:00 -0800 Subject: [PATCH 04/46] Add TIFF compression codecs: LZMA, Zstd, WebP --- src/PIL/TiffImagePlugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a39591937..665920b0e 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -133,6 +133,9 @@ COMPRESSION_INFO = { 32946: "tiff_deflate", 34676: "tiff_sgilog", 34677: "tiff_sgilog24", + 34925: "lzma", + 50000: "zstd", + 50001: "webp", } COMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()} From 7acaf3d6a692acdd350ee36cd0881f5f822d02f0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Jan 2019 07:26:52 +1100 Subject: [PATCH 05/46] Added support for I;16 modes for more transpose operations --- Tests/test_image_transpose.py | 13 ++++++------- src/libImaging/Geometry.c | 12 ++++++++++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py index a6b1191db..01501bc18 100644 --- a/Tests/test_image_transpose.py +++ b/Tests/test_image_transpose.py @@ -7,10 +7,9 @@ from PIL.Image import (FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180, class TestImageTranspose(PillowTestCase): - hopper = { - 'L': helper.hopper('L').crop((0, 0, 121, 127)).copy(), - 'RGB': helper.hopper('RGB').crop((0, 0, 121, 127)).copy(), - } + hopper = {mode: helper.hopper(mode).crop((0, 0, 121, 127)).copy() for mode in [ + 'L', 'RGB', 'I;16', 'I;16L', 'I;16B' + ]} def test_flip_left_right(self): def transpose(mode): @@ -25,7 +24,7 @@ class TestImageTranspose(PillowTestCase): self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, y-2))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, y-2))) - for mode in ("L", "RGB"): + for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"): transpose(mode) def test_flip_top_bottom(self): @@ -41,7 +40,7 @@ class TestImageTranspose(PillowTestCase): self.assertEqual(im.getpixel((1, y-2)), out.getpixel((1, 1))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((x-2, 1))) - for mode in ("L", "RGB"): + for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"): transpose(mode) def test_rotate_90(self): @@ -73,7 +72,7 @@ class TestImageTranspose(PillowTestCase): self.assertEqual(im.getpixel((1, y-2)), out.getpixel((x-2, 1))) self.assertEqual(im.getpixel((x-2, y-2)), out.getpixel((1, 1))) - for mode in ("L", "RGB"): + for mode in ("L", "RGB", "I;16", "I;16L", "I;16B"): transpose(mode) def test_rotate_270(self): diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c index 1d08728da..5358f6eeb 100644 --- a/src/libImaging/Geometry.c +++ b/src/libImaging/Geometry.c @@ -39,7 +39,11 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) ImagingSectionEnter(&cookie); if (imIn->image8) { - FLIP_LEFT_RIGHT(UINT8, image8) + if (strncmp(imIn->mode, "I;16", 4) == 0) { + FLIP_LEFT_RIGHT(UINT16, image8) + } else { + FLIP_LEFT_RIGHT(UINT8, image8) + } } else { FLIP_LEFT_RIGHT(INT32, image32) } @@ -253,7 +257,11 @@ ImagingRotate180(Imaging imOut, Imaging imIn) yr = imIn->ysize-1; if (imIn->image8) { - ROTATE_180(UINT8, image8) + if (strncmp(imIn->mode, "I;16", 4) == 0) { + ROTATE_180(UINT16, image8) + } else { + ROTATE_180(UINT8, image8) + } } else { ROTATE_180(INT32, image32) } From a08bfa6e9f07d07f15d8765c683b3224b282699d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 10 Jan 2019 18:19:21 -0800 Subject: [PATCH 06/46] Make ContainerIO.isatty() return a bool, not int Better follows the interface of IOBase.isatty: https://docs.python.org/3/library/io.html#io.IOBase.isatty --- Tests/test_file_container.py | 2 +- src/PIL/ContainerIO.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 55228be0c..19fce637f 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -16,7 +16,7 @@ class TestFileContainer(PillowTestCase): im = hopper() container = ContainerIO.ContainerIO(im, 0, 0) - self.assertEqual(container.isatty(), 0) + self.assertFalse(container.isatty()) def test_seek_mode_0(self): # Arrange diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 682ad9031..e2a89502f 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -39,7 +39,7 @@ class ContainerIO(object): # Always false. def isatty(self): - return 0 + return False def seek(self, offset, mode=0): """ From a00fc33c045bab0ff8b4a7cf289a70937c63e96c Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 12 Jan 2019 18:05:46 -0800 Subject: [PATCH 07/46] Replace .seek() magic numbers with io.SEEK_* constants A bit more readable. https://docs.python.org/3/library/io.html#io.IOBase.seek --- src/PIL/ContainerIO.py | 4 +++- src/PIL/EpsImagePlugin.py | 6 +++--- src/PIL/IcnsImagePlugin.py | 2 +- src/PIL/ImageFile.py | 4 ++-- src/PIL/Jpeg2KImagePlugin.py | 4 ++-- src/PIL/PcfFontFile.py | 3 ++- src/PIL/PcxImagePlugin.py | 3 ++- src/PIL/PsdImagePlugin.py | 7 ++++--- src/PIL/TarIO.py | 3 ++- src/PIL/TiffImagePlugin.py | 2 +- 10 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 682ad9031..c060b9960 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -18,6 +18,8 @@ # A file object that provides read access to a part of an existing # file (for example a TAR file). +import io + class ContainerIO(object): @@ -41,7 +43,7 @@ class ContainerIO(object): def isatty(self): return 0 - def seek(self, offset, mode=0): + def seek(self, offset, mode=io.SEEK_SET): """ Move file pointer. diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index cc2c1b1f8..37fe5d65c 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -102,7 +102,7 @@ def Ghostscript(tile, size, fp, scale=1): # Copy whole file to read in Ghostscript with open(infile_temp, 'wb') as f: # fetch length of fp - fp.seek(0, 2) + fp.seek(0, io.SEEK_END) fsize = fp.tell() # ensure start position # go back @@ -167,7 +167,7 @@ class PSFile(object): self.fp = fp self.char = None - def seek(self, offset, whence=0): + def seek(self, offset, whence=io.SEEK_SET): self.char = None self.fp.seek(offset, whence) @@ -310,7 +310,7 @@ class EpsImageFile(ImageFile.ImageFile): if s[:4] == b"%!PS": # for HEAD without binary preview - fp.seek(0, 2) + fp.seek(0, io.SEEK_END) length = fp.tell() offset = 0 elif i32(s[0:4]) == 0xC6D3D0C5: diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 2ea66675f..4a10b24b8 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -195,7 +195,7 @@ class IcnsFile(object): i += HEADERSIZE blocksize -= HEADERSIZE dct[sig] = (i, blocksize) - fobj.seek(blocksize, 1) + fobj.seek(blocksize, io.SEEK_CUR) i += blocksize def itersizes(self): diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index bcc910853..7487082af 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -491,7 +491,7 @@ def _save(im, fp, tile, bufsize=0): for e, b, o, a in tile: e = Image._getencoder(im.mode, e, a, im.encoderconfig) if o > 0: - fp.seek(o, 0) + fp.seek(o) e.setimage(im.im, b) if e.pushes_fd: e.setfd(fp) @@ -510,7 +510,7 @@ def _save(im, fp, tile, bufsize=0): for e, b, o, a in tile: e = Image._getencoder(im.mode, e, a, im.encoderconfig) if o > 0: - fp.seek(o, 0) + fp.seek(o) e.setimage(im.im, b) if e.pushes_fd: e.setfd(fp) diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 090337252..f5b2be332 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -191,9 +191,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile): fd = -1 try: pos = self.fp.tell() - self.fp.seek(0, 2) + self.fp.seek(0, io.SEEK_END) length = self.fp.tell() - self.fp.seek(pos, 0) + self.fp.seek(pos) except Exception: length = -1 diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index 471d6647a..b50fe72da 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -16,6 +16,7 @@ # See the README file for information on usage and redistribution. # +import io from . import Image, FontFile from ._binary import i8, i16le as l16, i32le as l32, i16be as b16, i32be as b32 @@ -117,7 +118,7 @@ class PcfFontFile(FontFile.FontFile): for i in range(nprops): p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4)))) if nprops & 3: - fp.seek(4 - (nprops & 3), 1) # pad + fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad data = fp.read(i32(fp.read(4))) diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index daa58b3f3..ba1e8611e 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -25,6 +25,7 @@ # See the README file for information on usage and redistribution. # +import io import logging from . import Image, ImageFile, ImagePalette from ._binary import i8, i16le as i16, o8, o16le as o16 @@ -80,7 +81,7 @@ class PcxImageFile(ImageFile.ImageFile): elif version == 5 and bits == 8 and planes == 1: mode = rawmode = "L" # FIXME: hey, this doesn't work with the incremental loader !!! - self.fp.seek(-769, 2) + self.fp.seek(-769, io.SEEK_END) s = self.fp.read(769) if len(s) == 769 and i8(s[0]) == 12: # check if the palette is linear greyscale diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 765895244..6039cd6da 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -18,6 +18,7 @@ __version__ = "0.4" +import io from . import Image, ImageFile, ImagePalette from ._binary import i8, i16be as i16, i32be as i32 @@ -214,12 +215,12 @@ def _layerinfo(file): if size: length = i32(read(4)) if length: - file.seek(length - 16, 1) + file.seek(length - 16, io.SEEK_CUR) combined += length + 4 length = i32(read(4)) if length: - file.seek(length, 1) + file.seek(length, io.SEEK_CUR) combined += length + 4 length = i8(read(1)) @@ -229,7 +230,7 @@ def _layerinfo(file): name = read(length).decode('latin-1', 'replace') combined += length + 1 - file.seek(size - combined, 1) + file.seek(size - combined, io.SEEK_CUR) layers.append((name, mode, (x0, y0, x1, y1))) # get tiles diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py index 89957fba4..a421b12a5 100644 --- a/src/PIL/TarIO.py +++ b/src/PIL/TarIO.py @@ -14,6 +14,7 @@ # See the README file for information on usage and redistribution. # +import io import sys from . import ContainerIO @@ -51,7 +52,7 @@ class TarIO(ContainerIO.ContainerIO): if file == name: break - self.fh.seek((size + 511) & (~511), 1) + self.fh.seek((size + 511) & (~511), io.SEEK_CUR) # Open region ContainerIO.ContainerIO.__init__(self, self.fh, self.fh.tell(), size) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a39591937..151f908fb 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1678,7 +1678,7 @@ class AppendingTiffWriter: def tell(self): return self.f.tell() - self.offsetOfNewPage - def seek(self, offset, whence): + def seek(self, offset, whence=io.SEEK_SET): if whence == os.SEEK_SET: offset += self.offsetOfNewPage From c41ec5b115142faceafef68d5a523f5e1d1ada17 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 13 Jan 2019 09:17:51 -0800 Subject: [PATCH 08/46] Fix 'BytesWarning: Comparison between bytes and string' in PdfDict When bytes warnings are enabled with the '-b' argument, the PdfDict class would emit a warning. https://docs.python.org/3/using/cmdline.html#miscellaneous-options > -b > > Issue a warning when comparing bytes or bytearray with str or bytes > with int. Object attributes are always type str, so can safely encode them without a type check. Observe: $ python3 >>> o = object() >>> setattr(o, b'foo', b'bar') Traceback (most recent call last): File "", line 1, in TypeError: attribute name must be string, not 'bytes' --- src/PIL/PdfParser.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 7216e5b95..ca08bc864 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -269,18 +269,13 @@ class PdfDict(UserDict): else: self.__dict__[key] = value else: - if isinstance(key, str): - key = key.encode("us-ascii") - self[key] = value + self[key.encode("us-ascii")] = value def __getattr__(self, key): try: - value = self[key] + value = self[key.encode("us-ascii")] except KeyError: - try: - value = self[key.encode("us-ascii")] - except KeyError: - raise AttributeError(key) + raise AttributeError(key) if isinstance(value, bytes): value = decode_text(value) if key.endswith("Date"): From adae7ecc6a3c61c0850dbf62ae5807bba3c9df32 Mon Sep 17 00:00:00 2001 From: Will Badart Date: Mon, 28 Jan 2019 12:14:42 -0500 Subject: [PATCH 09/46] _util.isPath returns True for pathlib.Path objects Now, for functions which accept either a path or file object, the predicate will pass on Paths and not attempt to call .read on them before opening. The pathlib module was added in 3.4 but os.path functions did not start accepting path-like objects until 3.6, so that is the version after which this implementation is defined. Added a unit test to make sure isPath accepts Path objects. The unit test is skipped if python version is not 3.6 or later. --- Tests/test_util.py | 16 ++++++++++++++++ src/PIL/_util.py | 11 +++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index 2316d3d65..dc18ba1e7 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,7 +1,11 @@ +import sys + from helper import unittest, PillowTestCase from PIL import _util +py36 = sys.version_info.major >= 3 and sys.version_info.minor >= 6 + class TestUtil(PillowTestCase): @@ -35,6 +39,18 @@ class TestUtil(PillowTestCase): # Assert self.assertTrue(it_is) + @unittest.skipIf(not py36, 'os.path support for Paths added in 3.6') + def test_path_obj_is_path(self): + # Arrange + from pathlib import Path + fp = Path('filename.ext') + + # Act + it_is = _util.isPath(fp) + + # Assert + self.assertTrue(it_is) + def test_is_not_path(self): # Arrange filename = self.tempfile("temp.ext") diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 5828c2c24..d2062cb2f 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -2,13 +2,20 @@ import os import sys py3 = sys.version_info.major >= 3 +py36 = py3 and sys.version_info.minor >= 6 if py3: def isStringType(t): return isinstance(t, str) - def isPath(f): - return isinstance(f, (bytes, str)) + if py36: + from pathlib import Path + + def isPath(f): + return isinstance(f, (bytes, str, Path)) + else: + def isPath(f): + return isinstance(f, (bytes, str)) else: def isStringType(t): return isinstance(t, basestring) # noqa: F821 From 07bff3e9b8a34c85c8326525bd56b02d819a3352 Mon Sep 17 00:00:00 2001 From: Will Badart Date: Mon, 28 Jan 2019 19:45:53 -0500 Subject: [PATCH 10/46] Implement @hugovk's comments The `py36` flag now uses a tuple comparison to correctly handle future major version. The unit test file also now uses `py36` as exported by the _util module, rather than re-testing `sys.version_info`. --- Tests/test_util.py | 4 +--- src/PIL/_util.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index dc18ba1e7..6d7bfeeb0 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -4,8 +4,6 @@ from helper import unittest, PillowTestCase from PIL import _util -py36 = sys.version_info.major >= 3 and sys.version_info.minor >= 6 - class TestUtil(PillowTestCase): @@ -39,7 +37,7 @@ class TestUtil(PillowTestCase): # Assert self.assertTrue(it_is) - @unittest.skipIf(not py36, 'os.path support for Paths added in 3.6') + @unittest.skipIf(not _util.py36, 'os.path support for Paths added in 3.6') def test_path_obj_is_path(self): # Arrange from pathlib import Path diff --git a/src/PIL/_util.py b/src/PIL/_util.py index d2062cb2f..cb307050c 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -2,7 +2,7 @@ import os import sys py3 = sys.version_info.major >= 3 -py36 = py3 and sys.version_info.minor >= 6 +py36 = sys.version_info[0:2] >= (3, 6) if py3: def isStringType(t): From c328ecace3b28bc25c287401b4415ad2ad718b69 Mon Sep 17 00:00:00 2001 From: Will Badart Date: Mon, 28 Jan 2019 20:08:25 -0500 Subject: [PATCH 11/46] Fix lint error Removed missing import in test_util.py. Stopped needing it after I started reusing the py36 test from the _util module. --- Tests/test_util.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index 6d7bfeeb0..ea4a2f962 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,5 +1,3 @@ -import sys - from helper import unittest, PillowTestCase from PIL import _util From 158d99b8b0ca4205f1b89fbc6485528eedc84b25 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 29 Jan 2019 19:10:52 +0200 Subject: [PATCH 12/46] Remove deprecated VERSION --- Tests/test_000_sanity.py | 2 -- src/PIL/Image.py | 9 ++++----- src/PIL/__init__.py | 5 ++--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Tests/test_000_sanity.py b/Tests/test_000_sanity.py index 62a9b5870..5dbed0abd 100644 --- a/Tests/test_000_sanity.py +++ b/Tests/test_000_sanity.py @@ -11,8 +11,6 @@ class TestSanity(PillowTestCase): # Make sure we have the binary extension PIL.Image.core.new("L", (100, 100)) - self.assertEqual(PIL.Image.VERSION[:3], '1.1') - # Create an image and do stuff with it. im = PIL.Image.new("1", (100, 100)) self.assertEqual((im.mode, im.size), ('1', (100, 100))) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index a67d3288f..21a029508 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -24,10 +24,10 @@ # See the README file for information on usage and redistribution. # -# VERSION is deprecated and will be removed in Pillow 6.0.0. -# PILLOW_VERSION is deprecated and will be removed after that. +# VERSION was removed in Pillow 6.0.0. +# PILLOW_VERSION is deprecated and will be removed in Pillow 7.0.0. # Use __version__ instead. -from . import VERSION, PILLOW_VERSION, __version__, _plugins +from . import PILLOW_VERSION, __version__, _plugins from ._util import py3 import logging @@ -60,8 +60,7 @@ except ImportError: from collections import Callable -# Silence warnings -assert VERSION +# Silence warning assert PILLOW_VERSION logger = logging.getLogger(__name__) diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index bc8cfed8c..ec0611b68 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -16,10 +16,9 @@ PIL.VERSION is the old PIL version and will be removed in the future. from . import _version -# VERSION is deprecated and will be removed in Pillow 6.0.0. -# PILLOW_VERSION is deprecated and will be removed after that. +# VERSION was removed in Pillow 6.0.0. +# PILLOW_VERSION is deprecated and will be removed in Pillow 7.0.0. # Use __version__ instead. -VERSION = '1.1.7' # PIL Version PILLOW_VERSION = __version__ = _version.__version__ del _version From 3513c82a737c1f668e7518968f7643c9cb018b47 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 30 Jan 2019 09:20:49 +0200 Subject: [PATCH 13/46] Use Pillow version instead of forked PIL version --- src/PIL/ImageCms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 29959a83a..2cd6f33b5 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -950,5 +950,5 @@ def versions(): return ( VERSION, core.littlecms_version, - sys.version.split()[0], Image.VERSION + sys.version.split()[0], Image.__version__ ) From dd1e7ccc4aacde8972f7b2573a2b5029e2cb222d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 3 Feb 2019 07:13:28 -0800 Subject: [PATCH 14/46] Slightly simplify Image.__eq__ Two identical types can be compared using the `is` operator. Object identity is slightly faster than a string comparison as well. --- src/PIL/Image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ef4a7a88a..62e5051fc 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -655,8 +655,7 @@ class Image(object): return filename def __eq__(self, other): - return (isinstance(other, Image) and - self.__class__.__name__ == other.__class__.__name__ and + return (self.__class__ is other.__class__ and self.mode == other.mode and self.size == other.size and self.info == other.info and From aed56efa5050d032af3afe293142483cb53e125a Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 4 Feb 2019 10:36:34 -0500 Subject: [PATCH 15/46] Apply suggestions from code review Rename `fp` to `test_path` in the new `test_is_path` test. ^ Wow, what a sentence... Co-Authored-By: wbadart --- Tests/test_util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index ea4a2f962..86999c89f 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -39,10 +39,10 @@ class TestUtil(PillowTestCase): def test_path_obj_is_path(self): # Arrange from pathlib import Path - fp = Path('filename.ext') + test_path = Path('filename.ext') # Act - it_is = _util.isPath(fp) + it_is = _util.isPath(test_path) # Assert self.assertTrue(it_is) From 1162b4cf83fb71c69288d3f98a74f6a7ce19e751 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 5 Feb 2019 06:15:50 +1100 Subject: [PATCH 16/46] Do not resize if already the destination size --- Tests/test_image_thumbnail.py | 12 ++++++++++++ src/PIL/Image.py | 9 +++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index d03ea4141..fbadf50cf 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,4 +1,5 @@ from .helper import PillowTestCase, hopper +from PIL import Image class TestImageThumbnail(PillowTestCase): @@ -35,3 +36,14 @@ class TestImageThumbnail(PillowTestCase): im = hopper().resize((128, 128)) im.thumbnail((100, 100)) self.assert_image(im, im.mode, (100, 100)) + + def test_no_resize(self): + # Check that draft() can resize the image to the destination size + im = Image.open("Tests/images/hopper.jpg") + im.draft(None, (64, 64)) + self.assertEqual(im.size, (64, 64)) + + # Test thumbnail(), where only draft() is necessary to resize the image + im = Image.open("Tests/images/hopper.jpg") + im.thumbnail((64, 64)) + self.assert_image(im, im.mode, (64, 64)) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index ef4a7a88a..a1b51fae2 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2129,11 +2129,12 @@ class Image(object): self.draft(None, size) - im = self.resize(size, resample) + if self.size != size: + im = self.resize(size, resample) - self.im = im.im - self.mode = im.mode - self._size = size + self.im = im.im + self._size = size + self.mode = self.im.mode self.readonly = 0 self.pyaccess = None From 3c088db7bac783e2688ac7beac43ad80cfa0987f Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 14 Feb 2019 22:59:14 +0200 Subject: [PATCH 17/46] Depreate support for EOL PyQt4 and PySide --- Tests/test_imageqt.py | 10 ++++++++++ src/PIL/ImageQt.py | 13 ++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 2ded37c09..f0e5a4613 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -2,6 +2,12 @@ from .helper import PillowTestCase, hopper from PIL import ImageQt +try: + # Python 3 + from importlib import reload +except ImportError: + # Python 2.7 + pass if ImageQt.qt_is_installed: from PIL.ImageQt import qRgba @@ -78,3 +84,7 @@ class TestImageQt(PillowQtTestCase, PillowTestCase): def test_image(self): for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): ImageQt.ImageQt(hopper(mode)) + + def test_deprecated(self): + expected = DeprecationWarning if ImageQt.qt_version in ["4", "side"] else None + self.assert_warning(expected, reload, ImageQt) diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index b747781c5..02ce6354e 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -20,6 +20,7 @@ from . import Image from ._util import isPath, py3 from io import BytesIO import sys +import warnings qt_versions = [ ['5', 'PyQt5'], @@ -27,6 +28,12 @@ qt_versions = [ ['4', 'PyQt4'], ['side', 'PySide'] ] + +WARNING_TEXT = ( + "Support for EOL {} is deprecated and will be removed in a future version. " + "Please upgrade to PyQt5 or PySide2." +) + # If a version has already been imported, attempt it first qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True) @@ -41,9 +48,13 @@ for qt_version, qt_module in qt_versions: elif qt_module == 'PyQt4': from PyQt4.QtGui import QImage, qRgba, QPixmap from PyQt4.QtCore import QBuffer, QIODevice + + warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning) elif qt_module == 'PySide': from PySide.QtGui import QImage, qRgba, QPixmap from PySide.QtCore import QBuffer, QIODevice + + warnings.warn(WARNING_TEXT.format(qt_module), DeprecationWarning) except (ImportError, RuntimeError): continue qt_is_installed = True @@ -67,7 +78,7 @@ def fromqimage(im): """ buffer = QBuffer() buffer.open(QIODevice.ReadWrite) - # preserve alha channel with png + # preserve alpha channel with png # otherwise ppm is more friendly with Image.open if im.hasAlphaChannel(): im.save(buffer, 'png') From 186f7d943bf213abbaa508b23c06fc001e8b6ec7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 14 Feb 2019 23:44:07 +0200 Subject: [PATCH 18/46] Document deprecation --- docs/deprecations.rst | 12 ++++++++++++ docs/reference/ImageQt.rst | 6 ++++++ docs/releasenotes/6.0.0.rst | 12 ++++++++++++ 3 files changed, 30 insertions(+) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index adbf0f85f..12367ce6e 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,6 +12,18 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a ``DeprecationWarning`` is issued. + +PyQt4 and PySide +~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0.0 + +Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since +2018-08-31 and PySide since 2015-10-14. + +Support for PyQt4 and PySide has been deprecated from ``ImageQt`` and will be removed in +a future version. Please upgrade to PyQt5 or PySide2. + Setting the size of TIFF images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/reference/ImageQt.rst b/docs/reference/ImageQt.rst index 386401075..5128f28fb 100644 --- a/docs/reference/ImageQt.rst +++ b/docs/reference/ImageQt.rst @@ -7,6 +7,12 @@ The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5, PySide or PySide2 QImage objects from PIL images. +Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since +2018-08-31 and PySide since 2015-10-14. + +Support for PyQt4 and PySide is deprecated since Pillow 6.0.0 and will be removed in a +future version. Please upgrade to PyQt5 or PySide2. + .. versionadded:: 1.1.6 .. py:class:: ImageQt.ImageQt(image) diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index e1fa83cce..9fbb3d69a 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -32,6 +32,18 @@ API Changes Deprecations ^^^^^^^^^^^^ +PyQt4 and PySide +~~~~~~~~~~~~~~~~ + +Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since +2018-08-31 and PySide since 2015-10-14. + +Support for PyQt4 and PySide has been deprecated from ``ImageQt`` and will be removed in +a future version. Please upgrade to PyQt5 or PySide2. + +PIL.*ImagePlugin.__version__ attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + These version constants have been deprecated and will be removed in a future version. From 6e1227765e89b8e2994b1fdfaa096ed26c0a5d54 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 15 Feb 2019 10:13:03 +0200 Subject: [PATCH 19/46] Move PyQt4/PySide above plugin constants Both deprecated in the same version, but PyQt4/PySide has a bigger impact --- docs/deprecations.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 605ceb4ac..bc4e83161 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,6 +12,17 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a ``DeprecationWarning`` is issued. +PyQt4 and PySide +~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0.0 + +Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since +2018-08-31 and PySide since 2015-10-14. + +Support for PyQt4 and PySide has been deprecated from ``ImageQt`` and will be removed in +a future version. Please upgrade to PyQt5 or PySide2. + PIL.*ImagePlugin.__version__ attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -37,17 +48,6 @@ Deprecated Deprecated Deprecated ``IptcImagePlugin.__version__`` ``PixarImagePlugin.__version__`` =============================== ================================= ================================== -PyQt4 and PySide -~~~~~~~~~~~~~~~~ - -.. deprecated:: 6.0.0 - -Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since -2018-08-31 and PySide since 2015-10-14. - -Support for PyQt4 and PySide has been deprecated from ``ImageQt`` and will be removed in -a future version. Please upgrade to PyQt5 or PySide2. - Setting the size of TIFF images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 38b5255f58a9ff025f1237e1bd47d5fb162f90cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 16 Feb 2019 12:36:10 +1100 Subject: [PATCH 20/46] Catch DeprecationWarning from initial import --- Tests/test_imageqt.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index f0e5a4613..bd93828ef 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,13 +1,16 @@ from .helper import PillowTestCase, hopper -from PIL import ImageQt +import warnings -try: - # Python 3 - from importlib import reload -except ImportError: - # Python 2.7 - pass +deprecated = False +with warnings.catch_warnings(): + warnings.filterwarnings("error", category=DeprecationWarning) + try: + from PIL import ImageQt + except DeprecationWarning: + deprecated = True + warnings.filterwarnings("ignore", category=DeprecationWarning) + from PIL import ImageQt if ImageQt.qt_is_installed: from PIL.ImageQt import qRgba @@ -86,5 +89,4 @@ class TestImageQt(PillowQtTestCase, PillowTestCase): ImageQt.ImageQt(hopper(mode)) def test_deprecated(self): - expected = DeprecationWarning if ImageQt.qt_version in ["4", "side"] else None - self.assert_warning(expected, reload, ImageQt) + self.assertEqual(ImageQt.qt_version in ["4", "side"], deprecated) From 9e52fb0fe4ec6652f0fdf47723c0b1fef75dbe5a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Feb 2019 19:45:53 +1100 Subject: [PATCH 21/46] Use constants for tag types --- src/PIL/TiffImagePlugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a0609bb4f..3c00d92ae 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -423,7 +423,7 @@ class ImageFileDirectory_v2(MutableMapping): ifd = ImageFileDirectory_v2() ifd[key] = 'Some Data' - ifd.tagtype[key] = 2 + ifd.tagtype[key] = TiffTags.ASCII print(ifd[key]) 'Some Data' @@ -557,7 +557,7 @@ class ImageFileDirectory_v2(MutableMapping): if info.type: self.tagtype[tag] = info.type else: - self.tagtype[tag] = 7 + self.tagtype[tag] = TiffTags.UNDEFINED if all(isinstance(v, IFDRational) for v in values): self.tagtype[tag] = TiffTags.RATIONAL elif all(isinstance(v, int) for v in values): @@ -872,7 +872,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): ifd = ImageFileDirectory_v1() ifd[key] = 'Some Data' - ifd.tagtype[key] = 2 + ifd.tagtype[key] = TiffTags.ASCII print(ifd[key]) ('Some Data',) From dbc476255e736f3f9f72cb9a0a1f359aa16c15a5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 19 Feb 2019 19:49:50 +1100 Subject: [PATCH 22/46] Fixed typo --- src/PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a0609bb4f..e1d37e555 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1436,7 +1436,7 @@ def _save(im, fp, filename): try: ifd.tagtype[key] = info.tagtype[key] except Exception: - pass # might not be an IFD, Might not have populated type + 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 From 26db1824ba6ad03f2457af15a2832eda325c0b0a Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Feb 2019 11:53:19 +0200 Subject: [PATCH 23/46] Update CHANGES.rst --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e1c08d8bd..d82455616 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,15 @@ Changelog (Pillow) 6.0.0 (unreleased) ------------------ +- Add TIFF compression codecs: LZMA, Zstd, WebP #3555 + [cgohlke] + +- Fixed pickling of iTXt class with protocol > 1 #3537 + [radarhere] + +- _util.isPath returns True for pathlib.Path objects #3616 + [wbadart] + - Remove unnecessary unittest.main() boilerplate from test files #3631 [jdufresne] From d07d3d6972b4f944e963e137677609e88d55afa6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 20 Feb 2019 20:57:52 +1100 Subject: [PATCH 24/46] Restored required import --- Tests/test_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index 4dfc6ffef..08e9c1665 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,4 +1,4 @@ -from .helper import PillowTestCase +from .helper import unittest, PillowTestCase from PIL import _util From e9fd0192faf1aac354ef76b633eac6cfc673d1e1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Feb 2019 19:25:27 +1100 Subject: [PATCH 25/46] Update CHANGES.rst [ci skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index d82455616..eee909695 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,12 +5,15 @@ Changelog (Pillow) 6.0.0 (unreleased) ------------------ +- Deprecate support for PyQt4 and PySide #3655 + [hugovk, radarhere] + - Add TIFF compression codecs: LZMA, Zstd, WebP #3555 [cgohlke] - Fixed pickling of iTXt class with protocol > 1 #3537 [radarhere] - + - _util.isPath returns True for pathlib.Path objects #3616 [wbadart] From ac14c9d987d2bdc5f70e4d1bc2aea64e6c5fdd7f Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 20 Feb 2019 16:02:09 +0200 Subject: [PATCH 26/46] Add Azure Pipelines config from another project --- .azure-pipelines/jobs/lint.yml | 30 +++++++++++++++++++++ .azure-pipelines/jobs/test.yml | 49 ++++++++++++++++++++++++++++++++++ azure-pipelines.yml | 48 +++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 .azure-pipelines/jobs/lint.yml create mode 100644 .azure-pipelines/jobs/test.yml create mode 100644 azure-pipelines.yml diff --git a/.azure-pipelines/jobs/lint.yml b/.azure-pipelines/jobs/lint.yml new file mode 100644 index 000000000..224b6c914 --- /dev/null +++ b/.azure-pipelines/jobs/lint.yml @@ -0,0 +1,30 @@ +parameters: + name: '' # defaults for any parameters that aren't specified + vmImage: '' + +jobs: + +- job: ${{ parameters.name }} + pool: + vmImage: ${{ parameters.vmImage }} + + strategy: + matrix: + Python37: + python.version: '3.7' + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + + - script: | + python -m pip install --upgrade pip + python -m pip install --upgrade black flake8 + displayName: 'Install dependencies' + + - script: | + flake8 --statistics --count + black --check --diff . + displayName: 'Static analysis' diff --git a/.azure-pipelines/jobs/test.yml b/.azure-pipelines/jobs/test.yml new file mode 100644 index 000000000..49194ac8a --- /dev/null +++ b/.azure-pipelines/jobs/test.yml @@ -0,0 +1,49 @@ +parameters: + name: '' # defaults for any parameters that aren't specified + vmImage: '' + +jobs: + +- job: ${{ parameters.name }} + pool: + vmImage: ${{ parameters.vmImage }} + + strategy: + matrix: + Python36: + python.version: '3.6' + Python37: + python.version: '3.7' + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + + - script: | + python -m pip install --upgrade pip + python -m pip install --upgrade freezegun pytest pytest-cov requests_mock + python -m pip install -e . + displayName: 'Install dependencies' + + - script: | + pytest --cov pypistats --junitxml=junit/test-results.xml + displayName: 'Unit tests' + + - script: | + pypistats --help + pypistats recent --help + displayName: 'Test runs' + + - script: | + python -m pip install --upgrade codecov + codecov --name "Python $(python.version)" --build $(Build.BuildNumber) + condition: succeeded() + displayName: 'Upload to Codecov' + + - task: PublishTestResults@2 + inputs: + testResultsFiles: '**/test-results.xml' + testRunTitle: 'Python $(python.version)' + condition: succeededOrFailed() diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..d31de379e --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,48 @@ +# Python package +# Create and test a Python package on multiple Python versions. +# Add steps that analyze code, save the dist with the build record, +# publish to a PyPI-compatible index, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/python + +trigger: +- master + +jobs: + +- template: .azure-pipelines/jobs/lint.yml + parameters: + name: Lint + vmImage: 'Ubuntu-16.04' + +- template: .azure-pipelines/jobs/test.yml + parameters: + name: Linux + vmImage: 'Ubuntu-16.04' + +- template: .azure-pipelines/jobs/test.yml + parameters: + name: macOS + vmImage: 'xcode9-macos10.13' + +- template: .azure-pipelines/jobs/test.yml + parameters: + name: Windows + vmImage: 'vs2017-win2016' + +- job: Publish + dependsOn: + - Lint + - Linux + - macOS + - Windows + pool: + vmImage: 'Ubuntu-16.04' + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.x' + architecture: 'x64' + + - script: python setup.py sdist + displayName: 'Build sdist' From 1f6cc7a9a493864b0cab44406fde75594635f281 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 21 Feb 2019 20:26:08 +0200 Subject: [PATCH 27/46] Trigger for all branches --- azure-pipelines.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d31de379e..b6eac1ac9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,9 +4,6 @@ # publish to a PyPI-compatible index, and more: # https://docs.microsoft.com/azure/devops/pipelines/languages/python -trigger: -- master - jobs: - template: .azure-pipelines/jobs/lint.yml From 03fc6c05e8abdb4f19e983c413678ac056e13aff Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 21 Feb 2019 20:45:48 +0200 Subject: [PATCH 28/46] Remove test jobs, keep lint --- .azure-pipelines/jobs/test.yml | 49 ---------------------------------- azure-pipelines.yml | 33 ----------------------- 2 files changed, 82 deletions(-) delete mode 100644 .azure-pipelines/jobs/test.yml diff --git a/.azure-pipelines/jobs/test.yml b/.azure-pipelines/jobs/test.yml deleted file mode 100644 index 49194ac8a..000000000 --- a/.azure-pipelines/jobs/test.yml +++ /dev/null @@ -1,49 +0,0 @@ -parameters: - name: '' # defaults for any parameters that aren't specified - vmImage: '' - -jobs: - -- job: ${{ parameters.name }} - pool: - vmImage: ${{ parameters.vmImage }} - - strategy: - matrix: - Python36: - python.version: '3.6' - Python37: - python.version: '3.7' - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '$(python.version)' - architecture: 'x64' - - - script: | - python -m pip install --upgrade pip - python -m pip install --upgrade freezegun pytest pytest-cov requests_mock - python -m pip install -e . - displayName: 'Install dependencies' - - - script: | - pytest --cov pypistats --junitxml=junit/test-results.xml - displayName: 'Unit tests' - - - script: | - pypistats --help - pypistats recent --help - displayName: 'Test runs' - - - script: | - python -m pip install --upgrade codecov - codecov --name "Python $(python.version)" --build $(Build.BuildNumber) - condition: succeeded() - displayName: 'Upload to Codecov' - - - task: PublishTestResults@2 - inputs: - testResultsFiles: '**/test-results.xml' - testRunTitle: 'Python $(python.version)' - condition: succeededOrFailed() diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b6eac1ac9..ad8c10056 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,36 +10,3 @@ jobs: parameters: name: Lint vmImage: 'Ubuntu-16.04' - -- template: .azure-pipelines/jobs/test.yml - parameters: - name: Linux - vmImage: 'Ubuntu-16.04' - -- template: .azure-pipelines/jobs/test.yml - parameters: - name: macOS - vmImage: 'xcode9-macos10.13' - -- template: .azure-pipelines/jobs/test.yml - parameters: - name: Windows - vmImage: 'vs2017-win2016' - -- job: Publish - dependsOn: - - Lint - - Linux - - macOS - - Windows - pool: - vmImage: 'Ubuntu-16.04' - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '3.x' - architecture: 'x64' - - - script: python setup.py sdist - displayName: 'Build sdist' From 409e8109247b322e3d7459a5fdbe214e8c921c4a Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 21 Feb 2019 21:02:46 +0200 Subject: [PATCH 29/46] Lint with tox, like in Travis CI --- .azure-pipelines/jobs/lint.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.azure-pipelines/jobs/lint.yml b/.azure-pipelines/jobs/lint.yml index 224b6c914..d017590f8 100644 --- a/.azure-pipelines/jobs/lint.yml +++ b/.azure-pipelines/jobs/lint.yml @@ -20,11 +20,9 @@ jobs: architecture: 'x64' - script: | - python -m pip install --upgrade pip - python -m pip install --upgrade black flake8 + python -m pip install --upgrade tox displayName: 'Install dependencies' - script: | - flake8 --statistics --count - black --check --diff . - displayName: 'Static analysis' + tox -e lint + displayName: 'Lint' From b3151a9a0995103d1bfd77625503f120172c3c49 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 21 Feb 2019 21:20:30 +0200 Subject: [PATCH 30/46] Fix check-manifest --- MANIFEST.in | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 809d0d667..f11ee174c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -23,9 +23,10 @@ exclude .codecov.yml exclude .editorconfig exclude .landscape.yaml exclude .readthedocs.yml -exclude .travis -exclude .travis/* +exclude azure-pipelines.yml exclude tox.ini global-exclude .git* global-exclude *.pyc global-exclude *.so +prune .azure-pipelines +prune .travis From c31e00f112eaab37decd1f9a0dc907ad954b63af Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 24 Feb 2019 15:36:59 +0200 Subject: [PATCH 31/46] Add Docker builds to Azure Pipelines --- .azure-pipelines/jobs/test-docker.yml | 22 +++++++++++ azure-pipelines.yml | 55 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 .azure-pipelines/jobs/test-docker.yml diff --git a/.azure-pipelines/jobs/test-docker.yml b/.azure-pipelines/jobs/test-docker.yml new file mode 100644 index 000000000..41dc2daec --- /dev/null +++ b/.azure-pipelines/jobs/test-docker.yml @@ -0,0 +1,22 @@ +parameters: + docker: '' # defaults for any parameters that aren't specified + dockerTag: 'master' + name: '' + vmImage: 'Ubuntu-16.04' + +jobs: + +- job: ${{ parameters.name }} + pool: + vmImage: ${{ parameters.vmImage }} + + steps: + - script: | + docker pull pythonpillow/${{ parameters.docker }}:${{ parameters.dockerTag }} + displayName: 'Docker pull' + + - script: | + # The Pillow user in the docker container is UID 1000 + sudo chown -R 1000 $(Build.SourcesDirectory) + docker run -v $(Build.SourcesDirectory):/Pillow pythonpillow/${{ parameters.docker }}:${{ parameters.dockerTag }} + displayName: 'Docker build' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ad8c10056..bef5eeee6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,3 +10,58 @@ jobs: parameters: name: Lint vmImage: 'Ubuntu-16.04' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'alpine' + name: 'alpine' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'arch' + name: 'arch' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'ubuntu-trusty-x86' + name: 'ubuntu_trusty_x86' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'ubuntu-xenial-amd64' + name: 'ubuntu_xenial_amd64' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'debian-stretch-x86' + name: 'debian_stretch_x86' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'centos-6-amd64' + name: 'centos_6_amd64' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'centos-7-amd64' + name: 'centos_7_amd64' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'amazon-1-amd64' + name: 'amazon_1_amd64' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'amazon-2-amd64' + name: 'amazon_2_amd64' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'fedora-28-amd64' + name: 'fedora_28_amd64' + +- template: .azure-pipelines/jobs/test-docker.yml + parameters: + docker: 'fedora-29-amd64' + name: 'fedora_29_amd64' From 17bf2a3cbbe956b73755778bb941418a6ae89fbb Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 27 Feb 2019 23:39:41 +0200 Subject: [PATCH 32/46] Update CHANGES.rst --- CHANGES.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index eee909695..ec35ac171 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,21 @@ Changelog (Pillow) 6.0.0 (unreleased) ------------------ +- Fix 'BytesWarning: Comparison between bytes and string' in PdfDict #3580 + [jdufresne] + +- Do not resize in Image.thumbnail if already the destination size #3632 + [radarhere] + +- Replace .seek() magic numbers with io.SEEK_* constants #3572 + [jdufresne] + +- Make ContainerIO.isatty() return a bool, not int #3568 + [jdufresne] + +- Add support for I;16 modes for more transpose operations #3563 + [radarhere] + - Deprecate support for PyQt4 and PySide #3655 [hugovk, radarhere] From 95f77ef54afdfce9cc18543ced635bb62be03893 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Mar 2019 12:02:35 +1100 Subject: [PATCH 33/46] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ec35ac171..3ba4c2bbc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 6.0.0 (unreleased) ------------------ +- Removed deprecated VERSION #3624 + [hugovk] + - Fix 'BytesWarning: Comparison between bytes and string' in PdfDict #3580 [jdufresne] From c1150a2d0fa60c1078afadeb67f10f20c611c2a3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Mar 2019 18:22:34 +1100 Subject: [PATCH 34/46] Document removal of VERSION (#3624) [ci skip] --- docs/deprecations.rst | 15 +++++++++++---- docs/releasenotes/6.0.0.rst | 6 ++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index bc4e83161..306fde5ad 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -61,13 +61,12 @@ a ``DeprecationWarning``: Setting the size of a TIFF image directly is deprecated, and will be removed in a future version. Use the resize method instead. -PILLOW_VERSION and VERSION constants -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +PILLOW_VERSION constant +~~~~~~~~~~~~~~~~~~~~~~~ .. deprecated:: 5.2.0 -Two version constants – ``VERSION`` (the old PIL version, always 1.1.7) and -``PILLOW_VERSION`` – have been deprecated and will be removed in the next +``PILLOW_VERSION`` has been deprecated and will be removed in the next major release. Use ``__version__`` instead. Removed features @@ -76,6 +75,14 @@ Removed features Deprecated features are only removed in major releases after an appropriate period of deprecation has passed. +VERSION constant +~~~~~~~~~~~~~~~~ + +*Removed in version 6.0.0.* + +``VERSION`` (the old PIL version, always 1.1.7) has been removed. Use +``__version__`` instead. + Undocumented ImageOps functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index 9fbb3d69a..d936b63e3 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -26,6 +26,12 @@ Several undocumented functions in ``ImageOps`` were deprecated in Pillow 4.3.0 ( and have now been removed: ``gaussian_blur``, ``gblur``, ``unsharp_mask``, ``usm`` and ``box_blur``. Use the equivalent operations in ``ImageFilter`` instead. +Removed deprecated VERSION +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``VERSION`` (the old PIL version, always 1.1.7) has been removed. Use ``__version__`` +instead. + API Changes =========== From 9296e4f3a2e7de40de9088735191f6af1cba4dcf Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 2 Mar 2019 08:24:08 -0800 Subject: [PATCH 35/46] Merge multiple isinstance() calls to one --- src/PIL/ImageMath.py | 2 +- src/PIL/PdfParser.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index d985877a6..68247c290 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -28,7 +28,7 @@ VERBOSE = 0 def _isconstant(v): - return isinstance(v, int) or isinstance(v, float) + return isinstance(v, (int, float)) class _Operand(object): diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index eca550416..8f90b668d 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -356,8 +356,7 @@ def pdf_repr(x): return b"false" elif x is None: return b"null" - elif (isinstance(x, PdfName) or isinstance(x, PdfDict) or - isinstance(x, PdfArray) or isinstance(x, PdfBinary)): + elif isinstance(x, (PdfName, PdfDict, PdfArray, PdfBinary)): return bytes(x) elif isinstance(x, int): return str(x).encode("us-ascii") From 5f1b6bf752452b4f89f71a8a5b4e0c43414a91a0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 2 Mar 2019 20:18:52 +0200 Subject: [PATCH 36/46] Document Python 2.7 will be dropped on 2020-01-01 --- docs/deprecations.rst | 10 ++++++++++ docs/releasenotes/6.0.0.rst | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index bc4e83161..b1dea0ca4 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,6 +12,16 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a ``DeprecationWarning`` is issued. +Python 2.7 +~~~~~~~~~~ + +.. deprecated:: 6.0.0 + +Python 2.7 reaches end-of-life on 2020-01-01. + +Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python 2.7, making +Pillow 6.x the last series to support Python 2. + PyQt4 and PySide ~~~~~~~~~~~~~~~~ diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index 9fbb3d69a..a535e6a4c 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -32,6 +32,14 @@ API Changes Deprecations ^^^^^^^^^^^^ +Python 2.7 +~~~~~~~~~~ + +Python 2.7 reaches end-of-life on 2020-01-01. + +Pillow 7.0.0 will be released on 2020-01-01 and will drop support for Python 2.7, making +Pillow 6.x the last series to support Python 2. + PyQt4 and PySide ~~~~~~~~~~~~~~~~ From e96e2791cb25ae2a1c73820718b1e4dcddbc2b52 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 2 Mar 2019 20:27:35 +0200 Subject: [PATCH 37/46] Fix RST warning: 'Unknown target name' --- docs/installation.rst | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index abccfcf56..8f9b4273f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -15,21 +15,21 @@ Notes .. note:: Pillow is supported on the following Python versions -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|**Python** |**2.4**|**2.5**|**2.6**|**2.7**|**3.2**|**3.3**|**3.4**|**3.5**|**3.6**|**3.7**| -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow < 2.0.0 | Yes | Yes | Yes | Yes | | | | | | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 2.x - 3.x | | | Yes | Yes | Yes | Yes | Yes | Yes | | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 4.x | | | | Yes | | Yes | Yes | Yes | Yes | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 5.0.x - 5.1.x| | | | Yes | | | Yes | Yes | Yes | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 5.2.x - 5.4.x| | | | Yes | | | Yes | Yes | Yes | Yes | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow >= 6.0.0 | | | | Yes | | | | Yes | Yes | Yes | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ ++---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|**Python** |**2.4**|**2.5**|**2.6**|**2.7**|**3.2**|**3.3**|**3.4**|**3.5**|**3.6**|**3.7**| ++---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow < 2.0.0 | Yes | Yes | Yes | Yes | | | | | | | ++---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow 2.x - 3.x | | | Yes | Yes | Yes | Yes | Yes | Yes | | | ++---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow 4.x | | | | Yes | | Yes | Yes | Yes | Yes | | ++---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow 5.0.x - 5.1.x | | | | Yes | | | Yes | Yes | Yes | | ++---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow 5.2.x - 5.4.x | | | | Yes | | | Yes | Yes | Yes | Yes | ++---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow 6.x | | | | Yes | | | | Yes | Yes | Yes | ++---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ Basic Installation ------------------ From f77bb0a15a601d04c0f74eac3aa20788ffda6b34 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 2 Mar 2019 20:27:54 +0200 Subject: [PATCH 38/46] Document Python 2.7 will be dropped in Pillow 7.x --- docs/installation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index 8f9b4273f..44cd9af6f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -30,6 +30,8 @@ Notes +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ |Pillow 6.x | | | | Yes | | | | Yes | Yes | Yes | +---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +|Pillow >= 7.0.0 | | | | | | | | Yes | Yes | Yes | ++---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ Basic Installation ------------------ From e514fbbbb1be2c45ba299f3f51dc2e10aec2b2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sat, 2 Mar 2019 21:19:57 +0100 Subject: [PATCH 39/46] use the already import io instead of importing again from io (lgtm suggestion) --- src/PIL/Image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 05cd32654..4e1cff312 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -679,8 +679,7 @@ class Image(object): :returns: png version of the image as bytes """ - from io import BytesIO - b = BytesIO() + b = io.BytesIO() self.save(b, 'PNG') return b.getvalue() From e8fedc8589c1e19cb727ae45bc3cd856a629eabc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 3 Mar 2019 12:56:30 +1100 Subject: [PATCH 40/46] Use pillow-wheels update script [ci skip] --- RELEASING.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 40a48c4d3..f28e5f134 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -88,14 +88,7 @@ Released as needed privately to individual vendors for critical security-related ```bash git clone https://github.com/python-pillow/pillow-wheels cd pillow-wheels - git submodule init - git submodule update Pillow - cd Pillow - git fetch --all - git checkout [[release tag]] - cd .. - git commit -m "Pillow -> 5.2.0" Pillow - git push + ./update-pillow-tag.sh [[release tag]] ``` * [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/). ```bash From 525eaf738973615150349fa3c690e4c168bd4bc3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 3 Mar 2019 13:02:00 +1100 Subject: [PATCH 41/46] Renamed file variable --- Tests/test_file_msp.py | 6 +++--- src/PIL/GifImagePlugin.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 66af65fcd..724fc78b1 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -12,11 +12,11 @@ YA_EXTRA_DIR = "Tests/images/msp" class TestFileMsp(PillowTestCase): def test_sanity(self): - file = self.tempfile("temp.msp") + test_file = self.tempfile("temp.msp") - hopper("1").save(file) + hopper("1").save(test_file) - im = Image.open(file) + im = Image.open(test_file) im.load() self.assertEqual(im.mode, "1") self.assertEqual(im.size, (128, 128)) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 2d13de022..2ebd8b248 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -604,16 +604,16 @@ def _save_netpbm(im, fp, filename): import os from subprocess import Popen, check_call, PIPE, CalledProcessError - file = im._dump() + tempfile = im._dump() with open(filename, 'wb') as f: if im.mode != "RGB": with open(os.devnull, 'wb') as devnull: - check_call(["ppmtogif", file], stdout=f, stderr=devnull) + check_call(["ppmtogif", tempfile], stdout=f, stderr=devnull) else: # Pipe ppmquant output into ppmtogif - # "ppmquant 256 %s | ppmtogif > %s" % (file, filename) - quant_cmd = ["ppmquant", "256", file] + # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename) + quant_cmd = ["ppmquant", "256", tempfile] togif_cmd = ["ppmtogif"] with open(os.devnull, 'wb') as devnull: quant_proc = Popen(quant_cmd, stdout=PIPE, stderr=devnull) @@ -632,7 +632,7 @@ def _save_netpbm(im, fp, filename): raise CalledProcessError(retcode, togif_cmd) try: - os.unlink(file) + os.unlink(tempfile) except OSError: pass From 0e9e3dd304e31292befd825c58f574e107542b4e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 3 Mar 2019 21:48:00 +1100 Subject: [PATCH 42/46] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3ba4c2bbc..01fc87bf7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 6.0.0 (unreleased) ------------------ +- Python 2.7 support will be removed in Pillow 7.0.0 #3682 + [hugovk] + - Removed deprecated VERSION #3624 [hugovk] From 4be51c46ebb4efb2132b36ab4943a487574562ca Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 4 Mar 2019 18:17:12 +1100 Subject: [PATCH 43/46] Added mime types --- Tests/test_file_ppm.py | 15 +++++++++++++++ src/PIL/PpmImagePlugin.py | 11 ++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 47f8b845e..dc239cc4c 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -14,12 +14,14 @@ class TestFilePpm(PillowTestCase): self.assertEqual(im.mode, "RGB") self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "PPM") + self.assertEqual(im.get_format_mimetype(), "image/x-portable-pixmap") def test_16bit_pgm(self): im = Image.open('Tests/images/16_bit_binary.pgm') im.load() self.assertEqual(im.mode, 'I') self.assertEqual(im.size, (20, 100)) + self.assertEqual(im.get_format_mimetype(), "image/x-portable-graymap") tgt = Image.open('Tests/images/16_bit_binary_pgm.png') self.assert_image_equal(im, tgt) @@ -49,3 +51,16 @@ class TestFilePpm(PillowTestCase): with self.assertRaises(IOError): Image.open('Tests/images/negative_size.ppm') + + def test_mimetypes(self): + path = self.tempfile('temp.pgm') + + with open(path, 'w') as f: + f.write("P4\n128 128\n255") + im = Image.open(path) + self.assertEqual(im.get_format_mimetype(), "image/x-portable-bitmap") + + with open(path, 'w') as f: + f.write("PyCMYK\n128 128\n255") + im = Image.open(path) + self.assertEqual(im.get_format_mimetype(), "image/x-portable-anymap") diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 750454dc5..8a19c1b94 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -70,7 +70,14 @@ class PpmImageFile(ImageFile.ImageFile): s = self.fp.read(1) if s != b"P": raise SyntaxError("not a PPM file") - mode = MODES[self._token(s)] + magic_number = self._token(s) + mode = MODES[magic_number] + + self.custom_mimetype = { + b"P4": "image/x-portable-bitmap", + b"P5": "image/x-portable-graymap", + b"P6": "image/x-portable-pixmap" + }.get(magic_number) if mode == "1": self.mode = "1" @@ -158,3 +165,5 @@ Image.register_open(PpmImageFile.format, PpmImageFile, _accept) Image.register_save(PpmImageFile.format, _save) Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm"]) + +Image.register_mime(PpmImageFile.format, "image/x-portable-anymap") From 353d2a34ed18375f80f052e45c699d0ed0c236e4 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 6 Mar 2019 00:01:56 +1100 Subject: [PATCH 44/46] Update src/PIL/PpmImagePlugin.py Co-Authored-By: radarhere <3112309+radarhere@users.noreply.github.com> --- src/PIL/PpmImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 8a19c1b94..e3e411cad 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -76,7 +76,7 @@ class PpmImageFile(ImageFile.ImageFile): self.custom_mimetype = { b"P4": "image/x-portable-bitmap", b"P5": "image/x-portable-graymap", - b"P6": "image/x-portable-pixmap" + b"P6": "image/x-portable-pixmap", }.get(magic_number) if mode == "1": From 2edab165a14eb2b0055aeeca75a0ee5229c32090 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 6 Mar 2019 15:57:58 +0200 Subject: [PATCH 45/46] Since #2527, macOS saves to a temp PNG before showing --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 4e1cff312..fddb994a9 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2024,7 +2024,7 @@ class Image(object): PPM file, and calls either the **xv** utility or the **display** utility, depending on which one can be found. - On macOS, this method saves the image to a temporary BMP file, and + On macOS, this method saves the image to a temporary PNG file, and opens it with the native Preview application. On Windows, it saves the image to a temporary BMP file, and uses From b29365e8a01e7be1ab8cca039d4a180c6e33fae2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 7 Mar 2019 15:24:27 +1100 Subject: [PATCH 46/46] Updated list of Unix utilities used to show an image [ci skip] --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index fddb994a9..0f0d49e82 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2021,7 +2021,7 @@ class Image(object): debugging purposes. On Unix platforms, this method saves the image to a temporary - PPM file, and calls either the **xv** utility or the **display** + PPM file, and calls the **display**, **eog** or **xv** utility, depending on which one can be found. On macOS, this method saves the image to a temporary PNG file, and