From 3b74a4c8ce112251fca255da345040931dd4c00f Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jul 2018 11:55:58 +0300 Subject: [PATCH 01/96] Test ImageChops --- Tests/test_imagechops.py | 204 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 4e30dc175..ad8a43a44 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -3,6 +3,16 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image from PIL import ImageChops +BLACK = (0, 0, 0) +BROWN = (127, 64, 0) +CYAN = (0, 255, 255) +DARK_GREEN = (0, 128, 0) +GREEN = (0, 255, 0) +ORANGE = (255, 128, 0) +WHITE = (255, 255, 255) + +GREY = 128 + class TestImageChops(PillowTestCase): @@ -35,6 +45,200 @@ class TestImageChops(PillowTestCase): ImageChops.offset(im, 10) ImageChops.offset(im, 10, 20) + def test_add(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") + im2 = Image.open("Tests/images/imagedraw_floodfill.png") + + # Act + new = ImageChops.add(im1, im2) + + # Assert + self.assertEqual(new.getbbox(), (25, 25, 76, 76)) + self.assertEqual(new.getpixel((50, 50)), ORANGE) + + def test_add_modulo(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") + im2 = Image.open("Tests/images/imagedraw_floodfill.png") + + # Act + new = ImageChops.add_modulo(im1, im2) + + # Assert + self.assertEqual(new.getbbox(), (25, 25, 76, 76)) + self.assertEqual(new.getpixel((50, 50)), ORANGE) + + def test_blend(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") + im2 = Image.open("Tests/images/imagedraw_floodfill.png") + + # Act + new = ImageChops.blend(im1, im2, 0.5) + + # Assert + self.assertEqual(new.getbbox(), (25, 25, 76, 76)) + self.assertEqual(new.getpixel((50, 50)), BROWN) + + def test_constant(self): + # Arrange + im = Image.new("RGB", (20, 10)) + + # Act + new = ImageChops.constant(im, GREY) + + # Assert + self.assertEqual(new.size, im.size) + self.assertEqual(new.getpixel((0, 0)), GREY) + self.assertEqual(new.getpixel((19, 9)), GREY) + + def test_darker(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") + im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + + # Act + new = ImageChops.darker(im1, im2) + + # Assert + self.assert_image_equal(new, im2) + + def test_difference(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_arc_end_le_start.png") + im2 = Image.open("Tests/images/imagedraw_arc_no_loops.png") + + # Act + new = ImageChops.difference(im1, im2) + + # Assert + self.assertEqual(new.getbbox(), (25, 25, 76, 76)) + + def test_duplicate(self): + # Arrange + im = hopper() + + # Act + new = ImageChops.duplicate(im) + + # Assert + self.assert_image_equal(new, im) + + def test_invert(self): + # Arrange + im = Image.open("Tests/images/imagedraw_floodfill.png") + + # Act + new = ImageChops.invert(im) + + # Assert + self.assertEqual(new.getbbox(), (0, 0, 100, 100)) + self.assertEqual(new.getpixel((0, 0)), WHITE) + self.assertEqual(new.getpixel((50, 50)), CYAN) + + def test_lighter(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") + im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + + # Act + new = ImageChops.darker(im1, im2) + + # Assert + self.assert_image_equal(new, im2) + + def test_multiply_black(self): + """If you multiply an image with a solid black image, + the result is black.""" + # Arrange + im1 = hopper() + black = Image.new("RGB", im1.size, "black") + + # Act + new = ImageChops.multiply(im1, black) + + # Assert + self.assert_image_equal(new, black) + + def test_multiply_green(self): + # Arrange + im = Image.open("Tests/images/imagedraw_floodfill.png") + green = Image.new("RGB", im.size, "green") + + # Act + new = ImageChops.multiply(im, green) + + # Assert + self.assertEqual(new.getbbox(), (25, 25, 76, 76)) + self.assertEqual(new.getpixel((25, 25)), DARK_GREEN) + self.assertEqual(new.getpixel((50, 50)), BLACK) + + def test_multiply_white(self): + """If you multiply with a solid white image, + the image is unaffected.""" + # Arrange + im1 = hopper() + white = Image.new("RGB", im1.size, "white") + + # Act + new = ImageChops.multiply(im1, white) + + # Assert + self.assert_image_equal(new, im1) + + def test_offset(self): + # Arrange + im = Image.open("Tests/images/imagedraw_ellipse_RGB.png") + xoffset = 45 + yoffset = 20 + + # Act + new = ImageChops.offset(im, xoffset, yoffset) + + # Assert + self.assertEqual(new.getbbox(), (0, 45, 100, 96)) + self.assertEqual(new.getpixel((50, 50)), BLACK) + self.assertEqual(new.getpixel((50+xoffset, 50+yoffset)), DARK_GREEN) + + def test_screen(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") + im2 = Image.open("Tests/images/imagedraw_floodfill.png") + + # Act + new = ImageChops.screen(im1, im2) + + # Assert + self.assertEqual(new.getbbox(), (25, 25, 76, 76)) + self.assertEqual(new.getpixel((50, 50)), ORANGE) + + def test_subtract(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") + im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + + # Act + new = ImageChops.subtract(im1, im2) + + # Assert + self.assertEqual(new.getbbox(), (25, 50, 76, 76)) + self.assertEqual(new.getpixel((50, 50)), GREEN) + self.assertEqual(new.getpixel((50, 51)), BLACK) + + def test_subtract_modulo(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") + im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + + # Act + new = ImageChops.subtract_modulo(im1, im2) + + # Assert + self.assertEqual(new.getbbox(), (25, 50, 76, 76)) + self.assertEqual(new.getpixel((50, 50)), GREEN) + self.assertEqual(new.getpixel((50, 51)), BLACK) + def test_logical(self): def table(op, a, b): From 64bce1a583ffdbf1811263ae7dce3088a669d7c7 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Thu, 26 Jul 2018 12:52:51 -0400 Subject: [PATCH 02/96] Fix builds with --parallel Python 3.5's distutils added support for parallel builds, which means that we don't need to monkeypatch it anymore. But more importantly, this monkeypatch made build fail (hang in fact) whenever `--parallel` was passed to `python setup.py build`. This commit fixes the problem by not applying the monkeypatch on python 3.5+ and preserve the old behavior (parallel build by default) by injecting a `parallel` option when it's not specified. --- mp_compile.py | 6 +++++- setup.py | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mp_compile.py b/mp_compile.py index 5fac2399f..9043e24b8 100644 --- a/mp_compile.py +++ b/mp_compile.py @@ -1,5 +1,7 @@ # A monkey patch of the base distutils.ccompiler to use parallel builds # Tested on 2.7, looks to be identical to 3.3. +# Only applied on Python < 3.5 because otherwise, it conflicts with Python's +# own newly-added support for parallel builds. from __future__ import print_function from multiprocessing import Pool, cpu_count @@ -77,4 +79,6 @@ def install(): "%s processes" % MAX_PROCS) -install() +# We monkeypatch only versions earlier than 3.5 +if sys.version_info < (3, 5): + install() diff --git a/setup.py b/setup.py index 9529787f9..935f3d1cc 100755 --- a/setup.py +++ b/setup.py @@ -205,6 +205,12 @@ class pil_build_ext(build_ext): if self.debug: global DEBUG DEBUG = True + if sys.version_info >= (3, 5) and not self.parallel: + # For Python < 3.5, we monkeypatch distutils to have parallel + # builds. If --parallel (or -j) wasn't specified, we want to + # reproduce the same behavior as before, that is, auto-detect the + # number of jobs. + self.parallel = mp_compile.MAX_PROCS for x in self.feature: if getattr(self, 'disable_%s' % x): setattr(self.feature, x, False) From 24388addb647dbb7599bfefa739d56c7a180e8f8 Mon Sep 17 00:00:00 2001 From: yo1995 Date: Mon, 6 Aug 2018 18:13:57 +0800 Subject: [PATCH 03/96] feature improvement: improved performance of ImageDraw.floodfill with Python built-in set() datatype. --- src/PIL/ImageDraw.py | 52 ++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index ca8c1d17b..2db19af18 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -325,22 +325,23 @@ def getdraw(im=None, hints=None): def floodfill(image, xy, value, border=None, thresh=0): """ - (experimental) Fills a bounded region with a given color. + (experimental) Fills a bounded region with a given color. - :param image: Target image. - :param xy: Seed position (a 2-item coordinate tuple). See - :ref:`coordinate-system`. - :param value: Fill color. - :param border: Optional border value. If given, the region consists of - pixels with a color different from the border color. If not given, - the region consists of pixels having the same color as the seed - pixel. - :param thresh: Optional threshold value which specifies a maximum - tolerable difference of a pixel value from the 'background' in - order for it to be replaced. Useful for filling regions of non- - homogeneous, but similar, colors. - """ + :param image: Target image. + :param xy: Seed position (a 2-item coordinate tuple). See + :ref:`coordinate-system`. + :param value: Fill color. + :param border: Optional border value. If given, the region consists of + pixels with a color different from the border color. If not given, + the region consists of pixels having the same color as the seed + pixel. + :param thresh: Optional threshold value which specifies a maximum + tolerable difference of a pixel value from the 'background' in + order for it to be replaced. Useful for filling regions of non- + homogeneous, but similar, colors. + """ # based on an implementation by Eric S. Raymond + # amended by yo1995 @20180806 pixel = image.load() x, y = xy try: @@ -350,12 +351,15 @@ def floodfill(image, xy, value, border=None, thresh=0): pixel[x, y] = value except (ValueError, IndexError): return # seed point outside image - edge = [(x, y)] + edge = {(x, y)} + full_edge = set() # use a set to record each unique pixel processed if border is None: while edge: - newedge = [] - for (x, y) in edge: + new_edge = set() + for (x, y) in edge: # 4 adjacent method for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): + if (s, t) in full_edge: + continue # if already processed, skip try: p = pixel[s, t] except IndexError: @@ -363,13 +367,17 @@ def floodfill(image, xy, value, border=None, thresh=0): else: if _color_diff(p, background) <= thresh: pixel[s, t] = value - newedge.append((s, t)) - edge = newedge + new_edge.add((s, t)) + full_edge.add((s, t)) + full_edge = edge # do not record useless pixels to reduce memory consumption + edge = new_edge else: while edge: - newedge = [] + new_edge = set() for (x, y) in edge: for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): + if (s, t) in full_edge: + continue try: p = pixel[s, t] except IndexError: @@ -377,7 +385,9 @@ def floodfill(image, xy, value, border=None, thresh=0): else: if p != value and p != border: pixel[s, t] = value - newedge.append((s, t)) + new_edge.add((s, t)) + full_edge.add((s, t)) + full_edge = edge edge = newedge From 8676044a272f7d716892440429a5e45b4ca7960f Mon Sep 17 00:00:00 2001 From: yo1995 Date: Mon, 6 Aug 2018 18:47:49 +0800 Subject: [PATCH 04/96] fix docstring tab --- src/PIL/ImageDraw.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 2db19af18..4f0144d35 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -325,21 +325,21 @@ def getdraw(im=None, hints=None): def floodfill(image, xy, value, border=None, thresh=0): """ - (experimental) Fills a bounded region with a given color. + (experimental) Fills a bounded region with a given color. - :param image: Target image. - :param xy: Seed position (a 2-item coordinate tuple). See - :ref:`coordinate-system`. - :param value: Fill color. - :param border: Optional border value. If given, the region consists of - pixels with a color different from the border color. If not given, - the region consists of pixels having the same color as the seed - pixel. - :param thresh: Optional threshold value which specifies a maximum - tolerable difference of a pixel value from the 'background' in - order for it to be replaced. Useful for filling regions of non- - homogeneous, but similar, colors. - """ + :param image: Target image. + :param xy: Seed position (a 2-item coordinate tuple). See + :ref:`coordinate-system`. + :param value: Fill color. + :param border: Optional border value. If given, the region consists of + pixels with a color different from the border color. If not given, + the region consists of pixels having the same color as the seed + pixel. + :param thresh: Optional threshold value which specifies a maximum + tolerable difference of a pixel value from the 'background' in + order for it to be replaced. Useful for filling regions of non- + homogeneous, but similar, colors. + """ # based on an implementation by Eric S. Raymond # amended by yo1995 @20180806 pixel = image.load() From a221420ec399851f674761129648f4075c4ef8e4 Mon Sep 17 00:00:00 2001 From: yo1995 Date: Mon, 6 Aug 2018 19:36:18 +0800 Subject: [PATCH 05/96] fixe typo --- src/PIL/ImageDraw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 4f0144d35..87ee4de0a 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -388,7 +388,7 @@ def floodfill(image, xy, value, border=None, thresh=0): new_edge.add((s, t)) full_edge.add((s, t)) full_edge = edge - edge = newedge + edge = new_edge def _color_diff(rgb1, rgb2): From 84b32a03885ed3fe5c15d8ab02b7089f45cd003e Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Fri, 3 Aug 2018 14:21:47 -0700 Subject: [PATCH 06/96] Read/Save RGB webp as RGB (instead of RGBX) --- Tests/images/hopper_webp_bits.ppm | Bin 29024 -> 49167 bytes Tests/test_file_webp.py | 17 ++++------------- Tests/test_file_webp_lossless.py | 3 +-- src/PIL/WebPImagePlugin.py | 24 +++++++++++++++++------- src/_webp.c | 2 ++ 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Tests/images/hopper_webp_bits.ppm b/Tests/images/hopper_webp_bits.ppm index 6dce2da2eb95a3a1f95690ddd1aaf616c49268f7..f431bc7b1fc5819dd1aa732631a2f7fd3e67110e 100644 GIT binary patch literal 49167 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 mDIu6;VC@1i7^1E!suS4498>Hh(j?_W3o literal 29024 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 Xo)d}cWc5i%00000NkvXXu0mjfoQE1w diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 06e274d0a..305abf481 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -16,8 +16,7 @@ class TestFileWebp(PillowTestCase): self.skipTest('WebP support not installed') return - # WebPAnimDecoder only returns RGBA or RGBX, never RGB - self.rgb_mode = "RGBX" if _webp.HAVE_WEBPANIM else "RGB" + self.rgb_mode = "RGB" def test_version(self): _webp.WebPDecoderVersion() @@ -29,8 +28,7 @@ class TestFileWebp(PillowTestCase): Does it have the bits we expect? """ - file_path = "Tests/images/hopper.webp" - image = Image.open(file_path) + image = Image.open("Tests/images/hopper.webp") self.assertEqual(image.mode, self.rgb_mode) self.assertEqual(image.size, (128, 128)) @@ -40,9 +38,7 @@ class TestFileWebp(PillowTestCase): # generated with: # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm - target = Image.open('Tests/images/hopper_webp_bits.ppm') - target = target.convert(self.rgb_mode) - self.assert_image_similar(image, target, 20.0) + self.assert_image_similar_tofile(image, 'Tests/images/hopper_webp_bits.ppm', 1.0) def test_write_rgb(self): """ @@ -61,13 +57,8 @@ class TestFileWebp(PillowTestCase): image.load() image.getdata() - # If we're using the exact same version of WebP, this test should pass. - # but it doesn't if the WebP is generated on Ubuntu and tested on - # Fedora. - # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm - # target = Image.open('Tests/images/hopper_webp_write.ppm') - # self.assert_image_equal(image, target) + self.assert_image_similar_tofile(image, 'Tests/images/hopper_webp_write.ppm', 12.0) # This test asserts that the images are similar. If the average pixel # difference between the two images is less than the epsilon value, diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 10354c55f..4c35dad73 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -19,8 +19,7 @@ class TestFileWebpLossless(PillowTestCase): if (_webp.WebPDecoderVersion() < 0x0200): self.skipTest('lossless not included') - # WebPAnimDecoder only returns RGBA or RGBX, never RGB - self.rgb_mode = "RGBX" if _webp.HAVE_WEBPANIM else "RGB" + self.rgb_mode = "RGB" def test_write_lossless_rgb(self): temp_file = self.tempfile("temp.webp") diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 39a8f2e35..1d8a0c10b 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -5,6 +5,7 @@ from io import BytesIO _VALID_WEBP_MODES = { "RGBX": True, "RGBA": True, + "RGB": True, } _VALID_WEBP_LEGACY_MODES = { @@ -63,7 +64,8 @@ class WebPImageFile(ImageFile.ImageFile): bgcolor & 0xFF self.info["background"] = (bg_r, bg_g, bg_b, bg_a) self._n_frames = frame_count - self.mode = mode + self.mode = 'RGB' if mode == 'RGBX' else mode + self.rawmode = mode self.tile = [] # Attempt to read ICC / EXIF / XMP chunks from file @@ -154,7 +156,7 @@ class WebPImageFile(ImageFile.ImageFile): # Set tile self.fp = BytesIO(data) - self.tile = [("raw", (0, 0) + self.size, 0, self.mode)] + self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)] return super(WebPImageFile, self).load() @@ -240,16 +242,23 @@ def _save_all(im, fp, filename): # Make sure image mode is supported frame = ims + rawmode = ims.mode if ims.mode not in _VALID_WEBP_MODES: - alpha = ims.mode == 'P' and 'A' in ims.im.getpalettemode() - frame = ims.convert('RGBA' if alpha else 'RGBX') + alpha = 'A' in ims.mode or 'a' in ims.mode \ + or ims.mode == 'P' and 'A' in ims.im.getpalettemode() + rawmode = 'RGBA' if alpha else 'RGBX' + frame = ims.convert(rawmode) + + if rawmode == 'RGB': + # For faster conversion, use RGBX + rawmode = 'RGBX' # Append the frame to the animation encoder enc.add( - frame.tobytes(), + frame.tobytes('raw', rawmode), timestamp, frame.size[0], frame.size[1], - frame.mode, + rawmode, lossless, quality, method @@ -288,7 +297,8 @@ def _save(im, fp, filename): xmp = im.encoderinfo.get("xmp", "") if im.mode not in _VALID_WEBP_LEGACY_MODES: - alpha = im.mode == 'P' and 'A' in im.im.getpalettemode() + alpha = 'A' in im.mode or 'a' in im.mode \ + or im.mode == 'P' and 'A' in im.im.getpalettemode() im = im.convert('RGBA' if alpha else 'RGB') data = _webp.WebPEncode( diff --git a/src/_webp.c b/src/_webp.c index f23d950f7..66b6d3268 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -216,6 +216,8 @@ PyObject* _anim_encoder_add(PyObject* self, PyObject* args) WebPPictureImportRGBA(frame, rgb, 4 * width); } else if (strcmp(mode, "RGBX")==0) { WebPPictureImportRGBX(frame, rgb, 4 * width); + } else { + WebPPictureImportRGB(frame, rgb, 3 * width); } // Add the frame to the encoder From 070436795244acd220f73cb8a0c5cf4dcb821647 Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Sun, 12 Aug 2018 21:44:25 -0700 Subject: [PATCH 07/96] Add more parenthesis to make statement clearer --- src/PIL/WebPImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 1d8a0c10b..011570862 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -245,7 +245,7 @@ def _save_all(im, fp, filename): rawmode = ims.mode if ims.mode not in _VALID_WEBP_MODES: alpha = 'A' in ims.mode or 'a' in ims.mode \ - or ims.mode == 'P' and 'A' in ims.im.getpalettemode() + or (ims.mode == 'P' and 'A' in ims.im.getpalettemode()) rawmode = 'RGBA' if alpha else 'RGBX' frame = ims.convert(rawmode) @@ -298,7 +298,7 @@ def _save(im, fp, filename): if im.mode not in _VALID_WEBP_LEGACY_MODES: alpha = 'A' in im.mode or 'a' in im.mode \ - or im.mode == 'P' and 'A' in im.im.getpalettemode() + or (im.mode == 'P' and 'A' in im.im.getpalettemode()) im = im.convert('RGBA' if alpha else 'RGB') data = _webp.WebPEncode( From df328a89a40b2384d471bff72d7eb9f3cb82264b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Aug 2018 23:07:19 +1000 Subject: [PATCH 08/96] Added PySide2 --- Tests/test_imageqt.py | 4 ++++ Tests/test_qt_image_toqimage.py | 20 ++++++++++++++------ docs/reference/ImageQt.rst | 4 ++-- src/PIL/ImageQt.py | 4 ++++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index cf9712ace..cecb1b5ee 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -33,6 +33,8 @@ class PillowQPixmapTestCase(PillowQtTestCase): from PyQt4.QtGui import QGuiApplication elif ImageQt.qt_version == 'side': from PySide.QtGui import QGuiApplication + elif ImageQt.qt_version == 'side2': + from PySide2.QtGui import QGuiApplication except ImportError: self.skipTest('QGuiApplication not installed') @@ -56,6 +58,8 @@ class TestImageQt(PillowQtTestCase, PillowTestCase): from PyQt4.QtGui import qRgb elif ImageQt.qt_version == 'side': from PySide.QtGui import qRgb + elif ImageQt.qt_version == 'side2': + from PySide2.QtGui import qRgb self.assertEqual(qRgb(0, 0, 0), qRgba(0, 0, 0, 255)) diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index c9971cf73..95ff4747c 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -13,13 +13,21 @@ if ImageQt.qt_is_installed: QT_VERSION = 5 except (ImportError, RuntimeError): try: - from PyQt4 import QtGui - from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, QApplication - QT_VERSION = 4 + from PySide2 import QtGui + from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, \ + QApplication + QT_VERSION = 5 except (ImportError, RuntimeError): - from PySide import QtGui - from PySide.QtGui import QWidget, QHBoxLayout, QLabel, QApplication - QT_VERSION = 4 + try: + from PyQt4 import QtGui + from PyQt4.QtGui import QWidget, QHBoxLayout, QLabel, \ + QApplication + QT_VERSION = 4 + except (ImportError, RuntimeError): + from PySide import QtGui + from PySide.QtGui import QWidget, QHBoxLayout, QLabel, \ + QApplication + QT_VERSION = 4 class TestToQImage(PillowQtTestCase, PillowTestCase): diff --git a/docs/reference/ImageQt.rst b/docs/reference/ImageQt.rst index 7bc426eec..386401075 100644 --- a/docs/reference/ImageQt.rst +++ b/docs/reference/ImageQt.rst @@ -4,8 +4,8 @@ :py:mod:`ImageQt` Module ======================== -The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5 or -PySide QImage objects from PIL images. +The :py:mod:`ImageQt` module contains support for creating PyQt4, PyQt5, PySide or +PySide2 QImage objects from PIL images. .. versionadded:: 1.1.6 diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 2930c1d9c..5ce685916 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -23,6 +23,7 @@ import sys qt_versions = [ ['5', 'PyQt5'], + ['side2', 'PySide2'], ['4', 'PyQt4'], ['side', 'PySide'] ] @@ -33,6 +34,9 @@ for qt_version, qt_module in qt_versions: if qt_module == 'PyQt5': from PyQt5.QtGui import QImage, qRgba, QPixmap from PyQt5.QtCore import QBuffer, QIODevice + elif qt_module == 'PySide2': + from PySide2.QtGui import QImage, qRgba, QPixmap + from PySide2.QtCore import QBuffer, QIODevice elif qt_module == 'PyQt4': from PyQt4.QtGui import QImage, qRgba, QPixmap from PyQt4.QtCore import QBuffer, QIODevice From c8e00203ece19ae73d797a089bf935dda5eed6a4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 Aug 2018 20:18:36 +1000 Subject: [PATCH 09/96] Removed unnecessary setUp calls --- Tests/test_qt_image_fromqpixmap.py | 3 +-- Tests/test_qt_image_toqimage.py | 3 --- Tests/test_qt_image_toqpixmap.py | 4 +--- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Tests/test_qt_image_fromqpixmap.py b/Tests/test_qt_image_fromqpixmap.py index 543b74bbf..0127f77e2 100644 --- a/Tests/test_qt_image_fromqpixmap.py +++ b/Tests/test_qt_image_fromqpixmap.py @@ -1,5 +1,5 @@ from helper import unittest, PillowTestCase, hopper -from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase +from test_imageqt import PillowQPixmapTestCase from PIL import ImageQt @@ -7,7 +7,6 @@ from PIL import ImageQt class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase): def roundtrip(self, expected): - PillowQtTestCase.setUp(self) result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) # Qt saves all pixmaps as rgb self.assert_image_equal(result, expected.convert('RGB')) diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index c9971cf73..896cf13fb 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -25,7 +25,6 @@ if ImageQt.qt_is_installed: class TestToQImage(PillowQtTestCase, PillowTestCase): def test_sanity(self): - PillowQtTestCase.setUp(self) for mode in ('RGB', 'RGBA', 'L', 'P', '1'): src = hopper(mode) data = ImageQt.toqimage(src) @@ -61,8 +60,6 @@ class TestToQImage(PillowQtTestCase, PillowTestCase): self.assert_image_equal(reloaded, src) def test_segfault(self): - PillowQtTestCase.setUp(self) - app = QApplication([]) ex = Example() assert(app) # Silence warning diff --git a/Tests/test_qt_image_toqpixmap.py b/Tests/test_qt_image_toqpixmap.py index c6555d7ff..5de7810f5 100644 --- a/Tests/test_qt_image_toqpixmap.py +++ b/Tests/test_qt_image_toqpixmap.py @@ -1,5 +1,5 @@ from helper import unittest, PillowTestCase, hopper -from test_imageqt import PillowQtTestCase, PillowQPixmapTestCase +from test_imageqt import PillowQPixmapTestCase from PIL import ImageQt @@ -10,8 +10,6 @@ if ImageQt.qt_is_installed: class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): def test_sanity(self): - PillowQtTestCase.setUp(self) - for mode in ('1', 'RGB', 'RGBA', 'L', 'P'): data = ImageQt.toqpixmap(hopper(mode)) From e98469ecf6ad2b93afc1481e1ebb90bb5af3acc3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 25 Jun 2018 21:08:41 +1000 Subject: [PATCH 10/96] Added transparency to matrix conversion --- Tests/test_image_convert.py | 3 +++ src/PIL/Image.py | 21 ++++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 1e208d80c..84c1cc82e 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -187,6 +187,7 @@ class TestImageConvert(PillowTestCase): def matrix_convert(mode): # Arrange im = hopper('RGB') + im.info['transparency'] = (255, 0, 0) matrix = ( 0.412453, 0.357580, 0.180423, 0, 0.212671, 0.715160, 0.072169, 0, @@ -203,9 +204,11 @@ class TestImageConvert(PillowTestCase): target = Image.open('Tests/images/hopper-XYZ.png') if converted_im.mode == 'RGB': self.assert_image_similar(converted_im, target, 3) + self.assertEqual(converted_im.info['transparency'], (105, 54, 4)) else: self.assert_image_similar(converted_im, target.getchannel(0), 1) + self.assertEqual(converted_im.info['transparency'], 105) matrix_convert('RGB') matrix_convert('L') diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f13a98276..e31d9002d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -900,12 +900,28 @@ class Image(object): if not mode or (mode == self.mode and not matrix): return self.copy() + has_transparency = self.info.get('transparency') is not None if matrix: # matrix conversion if mode not in ("L", "RGB"): raise ValueError("illegal conversion") im = self.im.convert_matrix(mode, matrix) - return self._new(im) + new = self._new(im) + if has_transparency and self.im.bands == 3: + transparency = new.info['transparency'] + + def convert_transparency(m, v): + v = m[0]*v[0] + m[1]*v[1] + m[2]*v[2] + m[3]*0.5 + return max(0, min(255, int(v))) + if mode == "L": + transparency = convert_transparency(matrix, transparency) + elif len(mode) == 3: + transparency = tuple([ + convert_transparency(matrix[i*4:i*4+4], transparency) + for i in range(0, len(transparency)) + ]) + new.info['transparency'] = transparency + return new if mode == "P" and self.mode == "RGBA": return self.quantize(colors) @@ -913,8 +929,7 @@ class Image(object): trns = None delete_trns = False # transparency handling - if "transparency" in self.info and \ - self.info['transparency'] is not None: + if has_transparency: if self.mode in ('L', 'RGB') and mode == 'RGBA': # Use transparent conversion to promote from transparent # color to an alpha channel. From 0a1fae8c2dc08aebbccd9c57d75ead681d23ed14 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Aug 2018 08:56:41 +1000 Subject: [PATCH 11/96] Added tests --- Tests/test_image_filter.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 3636a73f7..75665d20a 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -94,6 +94,15 @@ class TestImageFilter(PillowTestCase): self.assertEqual(rankfilter.size, 1) self.assertEqual(rankfilter.rank, 2) + def test_kernel_not_enough_coefficients(self): + self.assertRaises(ValueError, + lambda: ImageFilter.Kernel((3, 3), (0, 0))) + + def test_kernel_filter_p(self): + kernel = ImageFilter.Kernel((2, 2), (0, 0, 0, 0)) + + self.assertRaises(ValueError, kernel.filter, hopper("P")) + def test_consistency_3x3(self): source = Image.open("Tests/images/hopper.bmp") reference = Image.open("Tests/images/hopper_emboss.bmp") From 9c5c66cc8fe6c807520a523994996e8ff0a51b97 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Aug 2018 11:59:27 +1000 Subject: [PATCH 12/96] Improved ImageChops tests --- Tests/test_imagechops.py | 111 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index ad8a43a44..3714675cf 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -57,6 +57,28 @@ class TestImageChops(PillowTestCase): self.assertEqual(new.getbbox(), (25, 25, 76, 76)) self.assertEqual(new.getpixel((50, 50)), ORANGE) + def test_add_scale_offset(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") + im2 = Image.open("Tests/images/imagedraw_floodfill.png") + + # Act + new = ImageChops.add(im1, im2, scale=2.5, offset=100) + + # Assert + self.assertEqual(new.getbbox(), (0, 0, 100, 100)) + self.assertEqual(new.getpixel((50, 50)), (202, 151, 100)) + + def test_add_clip(self): + # Arrange + im = hopper() + + # Act + new = ImageChops.add(im, im) + + # Assert + self.assertEqual(new.getpixel((50, 50)), (255, 255, 254)) + def test_add_modulo(self): # Arrange im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") @@ -69,6 +91,16 @@ class TestImageChops(PillowTestCase): self.assertEqual(new.getbbox(), (25, 25, 76, 76)) self.assertEqual(new.getpixel((50, 50)), ORANGE) + def test_add_modulo_no_clip(self): + # Arrange + im = hopper() + + # Act + new = ImageChops.add_modulo(im, im) + + # Assert + self.assertEqual(new.getpixel((50, 50)), (224, 76, 254)) + def test_blend(self): # Arrange im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") @@ -93,7 +125,7 @@ class TestImageChops(PillowTestCase): self.assertEqual(new.getpixel((0, 0)), GREY) self.assertEqual(new.getpixel((19, 9)), GREY) - def test_darker(self): + def test_darker_image(self): # Arrange im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") @@ -104,6 +136,17 @@ class TestImageChops(PillowTestCase): # Assert self.assert_image_equal(new, im2) + def test_darker_pixel(self): + # Arrange + im1 = hopper() + im2 = Image.open("Tests/images/imagedraw_chord_RGB.png") + + # Act + new = ImageChops.darker(im1, im2) + + # Assert + self.assertEqual(new.getpixel((50, 50)), (240, 166, 0)) + def test_difference(self): # Arrange im1 = Image.open("Tests/images/imagedraw_arc_end_le_start.png") @@ -115,6 +158,17 @@ class TestImageChops(PillowTestCase): # Assert self.assertEqual(new.getbbox(), (25, 25, 76, 76)) + def test_difference_pixel(self): + # Arrange + im1 = hopper() + im2 = Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") + + # Act + new = ImageChops.difference(im1, im2) + + # Assert + self.assertEqual(new.getpixel((50, 50)), (240, 166, 128)) + def test_duplicate(self): # Arrange im = hopper() @@ -137,16 +191,27 @@ class TestImageChops(PillowTestCase): self.assertEqual(new.getpixel((0, 0)), WHITE) self.assertEqual(new.getpixel((50, 50)), CYAN) - def test_lighter(self): + def test_lighter_image(self): # Arrange im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") # Act - new = ImageChops.darker(im1, im2) + new = ImageChops.lighter(im1, im2) # Assert - self.assert_image_equal(new, im2) + self.assert_image_equal(new, im1) + + def test_lighter_pixel(self): + # Arrange + im1 = hopper() + im2 = Image.open("Tests/images/imagedraw_chord_RGB.png") + + # Act + new = ImageChops.lighter(im1, im2) + + # Assert + self.assertEqual(new.getpixel((50, 50)), (255, 255, 127)) def test_multiply_black(self): """If you multiply an image with a solid black image, @@ -201,6 +266,10 @@ class TestImageChops(PillowTestCase): self.assertEqual(new.getpixel((50, 50)), BLACK) self.assertEqual(new.getpixel((50+xoffset, 50+yoffset)), DARK_GREEN) + # Test no yoffset + self.assertEqual(ImageChops.offset(im, xoffset), + ImageChops.offset(im, xoffset, xoffset)) + def test_screen(self): # Arrange im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") @@ -226,6 +295,29 @@ class TestImageChops(PillowTestCase): self.assertEqual(new.getpixel((50, 50)), GREEN) self.assertEqual(new.getpixel((50, 51)), BLACK) + def test_subtract_scale_offset(self): + # Arrange + im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") + im2 = Image.open("Tests/images/imagedraw_outline_chord_RGB.png") + + # Act + new = ImageChops.subtract(im1, im2, scale=2.5, offset=100) + + # Assert + self.assertEqual(new.getbbox(), (0, 0, 100, 100)) + self.assertEqual(new.getpixel((50, 50)), (100, 202, 100)) + + def test_subtract_clip(self): + # Arrange + im1 = hopper() + im2 = Image.open("Tests/images/imagedraw_chord_RGB.png") + + # Act + new = ImageChops.subtract(im1, im2) + + # Assert + self.assertEqual(new.getpixel((50, 50)), (0, 0, 127)) + def test_subtract_modulo(self): # Arrange im1 = Image.open("Tests/images/imagedraw_chord_RGB.png") @@ -239,6 +331,17 @@ class TestImageChops(PillowTestCase): self.assertEqual(new.getpixel((50, 50)), GREEN) self.assertEqual(new.getpixel((50, 51)), BLACK) + def test_subtract_modulo_no_clip(self): + # Arrange + im1 = hopper() + im2 = Image.open("Tests/images/imagedraw_chord_RGB.png") + + # Act + new = ImageChops.subtract_modulo(im1, im2) + + # Assert + self.assertEqual(new.getpixel((50, 50)), (241, 167, 127)) + def test_logical(self): def table(op, a, b): From 9d5db408c5f8093c048a3336c174c4a703d6970e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Aug 2018 13:57:24 +1000 Subject: [PATCH 13/96] Changed rm command to use force --- depends/install_extra_test_images.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/install_extra_test_images.sh b/depends/install_extra_test_images.sh index 667c74e6d..089a5e4d7 100755 --- a/depends/install_extra_test_images.sh +++ b/depends/install_extra_test_images.sh @@ -1,7 +1,7 @@ #!/bin/bash # install extra test images -rm -r test_images +rm -rf test_images # Use SVN to just fetch a single git subdirectory svn checkout https://github.com/python-pillow/pillow-depends/trunk/test_images From f115546b56a54b6458f189df0348806cfdf569bd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Aug 2018 14:57:15 +1000 Subject: [PATCH 14/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 972092010..1daa50703 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,18 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Tests: Added ImageChops tests #3230 + [hugovk, radarhere] + +- AppVeyor: Download lib if not present in pillow-depends #3316 + [radarhere] + +- Travis CI: Add Python 3.7 and Xenial #3234 + [hugovk] + +- Docs: Added documentation for NumPy conversion #3301 + [radarhere] + - Depends: Update libimagequant to 2.12.1 #3281 [radarhere] From 3ae5f054107f640383ec5ebb219dbf76f5b14b21 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Aug 2018 23:30:47 +1000 Subject: [PATCH 15/96] Changed Kernel to subclass BuiltinFilter, instead of the other way around --- Tests/test_image_filter.py | 10 +++++----- src/PIL/ImageFilter.py | 19 ++++++++----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 75665d20a..6936a84f0 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -94,15 +94,15 @@ class TestImageFilter(PillowTestCase): self.assertEqual(rankfilter.size, 1) self.assertEqual(rankfilter.rank, 2) + def test_builtinfilter_p(self): + builtinFilter = ImageFilter.BuiltinFilter() + + self.assertRaises(ValueError, builtinFilter.filter, hopper("P")) + def test_kernel_not_enough_coefficients(self): self.assertRaises(ValueError, lambda: ImageFilter.Kernel((3, 3), (0, 0))) - def test_kernel_filter_p(self): - kernel = ImageFilter.Kernel((2, 2), (0, 0, 0, 0)) - - self.assertRaises(ValueError, kernel.filter, hopper("P")) - def test_consistency_3x3(self): source = Image.open("Tests/images/hopper.bmp") reference = Image.open("Tests/images/hopper_emboss.bmp") diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index e77349df0..de99e6410 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -33,7 +33,14 @@ class MultibandFilter(Filter): pass -class Kernel(MultibandFilter): +class BuiltinFilter(MultibandFilter): + def filter(self, image): + if image.mode == "P": + raise ValueError("cannot filter palette images") + return image.filter(*self.filterargs) + + +class Kernel(BuiltinFilter): """ Create a convolution kernel. The current version only supports 3x3 and 5x5 integer and floating point kernels. @@ -60,16 +67,6 @@ class Kernel(MultibandFilter): raise ValueError("not enough coefficients in kernel") self.filterargs = size, scale, offset, kernel - def filter(self, image): - if image.mode == "P": - raise ValueError("cannot filter palette images") - return image.filter(*self.filterargs) - - -class BuiltinFilter(Kernel): - def __init__(self): - pass - class RankFilter(Filter): """ From f4d39c95573cf5e25a87221e9a63c8a2987a1588 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Aug 2018 23:33:12 +1000 Subject: [PATCH 16/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 1daa50703..f77b4137b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Tests: Added ImageFilter tests #3295 + [radarhere] + - Tests: Added ImageChops tests #3230 [hugovk, radarhere] From bdf2705cd3a76f03d0530d1b90b552d42cd16114 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jul 2018 12:15:06 +0300 Subject: [PATCH 17/96] Remove ununsed draw_line. Only draw_lines is used by ImageDraw.py --- src/_imaging.c | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 0cb2f56b9..4ff277b0c 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -2697,22 +2697,6 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* -_draw_line(ImagingDrawObject* self, PyObject* args) -{ - int x0, y0, x1, y1; - int ink; - if (!PyArg_ParseTuple(args, "(ii)(ii)i", &x0, &y0, &x1, &y1, &ink)) - return NULL; - - if (ImagingDrawLine(self->image->image, x0, y0, x1, y1, - &ink, self->blend) < 0) - return NULL; - - Py_INCREF(Py_None); - return Py_None; -} - static PyObject* _draw_lines(ImagingDrawObject* self, PyObject* args) { @@ -2961,7 +2945,6 @@ _draw_rectangle(ImagingDrawObject* self, PyObject* args) static struct PyMethodDef _draw_methods[] = { #ifdef WITH_IMAGEDRAW /* Graphics (ImageDraw) */ - {"draw_line", (PyCFunction)_draw_line, 1}, {"draw_lines", (PyCFunction)_draw_lines, 1}, #ifdef WITH_ARROW {"draw_outline", (PyCFunction)_draw_outline, 1}, From 6fc1e79e9601ff0990d93c22caf413d0fd51b11b Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jul 2018 12:19:32 +0300 Subject: [PATCH 18/96] Remove ununsed draw_point. Only draw_points is used by ImageDraw.py --- src/_imaging.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 4ff277b0c..3acbdb01c 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -2750,21 +2750,6 @@ _draw_lines(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* -_draw_point(ImagingDrawObject* self, PyObject* args) -{ - int x, y; - int ink; - if (!PyArg_ParseTuple(args, "(ii)i", &x, &y, &ink)) - return NULL; - - if (ImagingDrawPoint(self->image->image, x, y, &ink, self->blend) < 0) - return NULL; - - Py_INCREF(Py_None); - return Py_None; -} - static PyObject* _draw_points(ImagingDrawObject* self, PyObject* args) { @@ -2951,7 +2936,6 @@ static struct PyMethodDef _draw_methods[] = { #endif {"draw_polygon", (PyCFunction)_draw_polygon, 1}, {"draw_rectangle", (PyCFunction)_draw_rectangle, 1}, - {"draw_point", (PyCFunction)_draw_point, 1}, {"draw_points", (PyCFunction)_draw_points, 1}, {"draw_arc", (PyCFunction)_draw_arc, 1}, {"draw_bitmap", (PyCFunction)_draw_bitmap, 1}, From 3cf6ad1895ce6a77245d3bc96c8d19c440ad6132 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 5 Jul 2018 00:54:52 +0300 Subject: [PATCH 19/96] Remove ununsed font_getabc --- src/_imagingft.c | 42 ------------------------------------------ 1 file changed, 42 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 86e0fe45b..f94e55803 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -674,47 +674,6 @@ font_getsize(FontObject* self, PyObject* args) ); } -static PyObject* -font_getabc(FontObject* self, PyObject* args) -{ - FT_ULong ch; - FT_Face face; - double a, b, c; - - /* calculate ABC values for a given string */ - - PyObject* string; - if (!PyArg_ParseTuple(args, "O:getabc", &string)) - return NULL; - -#if PY_VERSION_HEX >= 0x03000000 - if (!PyUnicode_Check(string)) { -#else - if (!PyUnicode_Check(string) && !PyString_Check(string)) { -#endif - PyErr_SetString(PyExc_TypeError, "expected string"); - return NULL; - } - - if (font_getchar(string, 0, &ch)) { - int index, error; - face = self->face; - index = FT_Get_Char_Index(face, ch); - /* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */ - error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP); - if (error) - return geterror(error); - a = face->glyph->metrics.horiBearingX / 64.0; - b = face->glyph->metrics.width / 64.0; - c = (face->glyph->metrics.horiAdvance - - face->glyph->metrics.horiBearingX - - face->glyph->metrics.width) / 64.0; - } else - a = b = c = 0.0; - - return Py_BuildValue("ddd", a, b, c); -} - static PyObject* font_render(FontObject* self, PyObject* args) { @@ -854,7 +813,6 @@ font_dealloc(FontObject* self) static PyMethodDef font_methods[] = { {"render", (PyCFunction) font_render, METH_VARARGS}, {"getsize", (PyCFunction) font_getsize, METH_VARARGS}, - {"getabc", (PyCFunction) font_getabc, METH_VARARGS}, {NULL, NULL} }; From 970ea7d3c42cce231494e5a5fe35e370f38b1601 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jul 2018 16:23:38 +0300 Subject: [PATCH 20/96] Test two arbitrarily chosen jobs with PYTHONOPTIMIZE --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 23225dbbb..b5adc2da7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,8 +30,10 @@ matrix: - python: '3.5' - python: '3.5' dist: trusty + env: PYTHONOPTIMIZE=1 - python: '3.4' dist: trusty + env: PYTHONOPTIMIZE=2 - env: DOCKER="alpine" DOCKER_TAG="pytest" - env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5 - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest" From fc8717fb040a135b91be58af3c4faf86d9765516 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jul 2018 16:28:02 +0300 Subject: [PATCH 21/96] Remove docstring formatting for when PYTHONOPTIMIZE=2 --- src/PIL/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index a07280e31..bc8cfed8c 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -1,4 +1,4 @@ -"""Pillow {} (Fork of the Python Imaging Library) +"""Pillow (Fork of the Python Imaging Library) Pillow is the friendly PIL fork by Alex Clark and Contributors. https://github.com/python-pillow/Pillow/ @@ -24,8 +24,6 @@ PILLOW_VERSION = __version__ = _version.__version__ del _version -__doc__ = __doc__.format(__version__) # include version in docstring - _plugins = ['BlpImagePlugin', 'BmpImagePlugin', From d7e4d3db3fd73995639b72dfd55c4fcce1938c90 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jul 2018 16:47:48 +0300 Subject: [PATCH 22/96] Convert assert into exception --- Tests/test_imagefont.py | 2 +- src/PIL/ImageDraw.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index f2116bdc4..82108638c 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -215,7 +215,7 @@ class TestImageFont(PillowTestCase): # Act/Assert self.assertRaises( - AssertionError, + ValueError, draw.multiline_text, (0, 0), TEST_TEXT, font=ttf, align="unknown") def test_draw_align(self): diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index ca8c1d17b..a427e327d 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -246,7 +246,7 @@ class ImageDraw(object): elif align == "right": left += (max_width - widths[idx]) else: - assert False, 'align must be "left", "center" or "right"' + raise ValueError('align must be "left", "center" or "right"') self.text((left, top), line, fill, font, anchor, direction=direction, features=features) top += line_spacing From 4218a769d79c0f07a9ef1b67d7ace52bad88a835 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jul 2018 18:01:24 +0300 Subject: [PATCH 23/96] Refactor cffi import and skipping --- Tests/test_image_access.py | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 7a9378bbd..cdce3fd49 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -2,8 +2,9 @@ from helper import unittest, PillowTestCase, hopper, on_appveyor try: from PIL import PyAccess + import cffi except ImportError: - # Skip in setUp() + cffi = None pass from PIL import Image @@ -113,38 +114,20 @@ class TestImageGetPixel(AccessTest): self.check(mode, 2**16-1) +@unittest.skipIf(cffi is None, "No cffi") class TestCffiPutPixel(TestImagePutPixel): _need_cffi_access = True - def setUp(self): - try: - import cffi - assert cffi # silence warning - except ImportError: - self.skipTest("No cffi") - +@unittest.skipIf(cffi is None, "No cffi") class TestCffiGetPixel(TestImageGetPixel): _need_cffi_access = True - def setUp(self): - try: - import cffi - assert cffi # silence warning - except ImportError: - self.skipTest("No cffi") - +@unittest.skipIf(cffi is None, "No cffi") class TestCffi(AccessTest): _need_cffi_access = True - def setUp(self): - try: - import cffi - assert cffi # silence warning - except ImportError: - self.skipTest("No cffi") - def _test_get_access(self, im): """Do we get the same thing as the old pixel access From 663b06e223bfa445d0c81b2d3e5923a66887d1a9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jul 2018 18:26:09 +0300 Subject: [PATCH 24/96] Skip: CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 --- Tests/test_image_access.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index cdce3fd49..f87e75071 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -1,16 +1,21 @@ from helper import unittest, PillowTestCase, hopper, on_appveyor -try: - from PIL import PyAccess - import cffi -except ImportError: - cffi = None - pass - from PIL import Image import sys import os +# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 +# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670 +if os.environ.get("PYTHONOPTIMIZE") == "2": + cffi = None +else: + try: + from PIL import PyAccess + import cffi + except ImportError: + cffi = None + pass + class AccessTest(PillowTestCase): # initial value From 29b2c6e23dc98cd941ae7cca9f65041855143b09 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jul 2018 18:43:39 +0300 Subject: [PATCH 25/96] Only import cffi where needed, to avoid problems with PYTHONOPTIMIZE=2 --- src/PIL/ImageTk.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 17bf32f62..d58ee8528 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -32,13 +32,6 @@ if sys.version_info.major > 2: else: import Tkinter as tkinter -# required for pypy, which always has cffi installed -try: - from cffi import FFI - ffi = FFI() -except ImportError: - pass - from . import Image from io import BytesIO @@ -192,7 +185,11 @@ class PhotoImage(object): from . import _imagingtk try: if hasattr(tk, 'interp'): - # Pypy is using a ffi cdata element + # Required for PyPy, which always has CFFI installed + from cffi import FFI + ffi = FFI() + + # Pypy is using an FFI cdata element # (Pdb) self.tk.interp # _imagingtk.tkinit( From 847581f7dd752231e8ca9e72189fa4506d2041ac Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 14 Jul 2018 09:35:42 +0300 Subject: [PATCH 26/96] Remove redundant 'pass' --- Tests/test_image_access.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index f87e75071..a7e39a499 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -14,7 +14,6 @@ else: import cffi except ImportError: cffi = None - pass class AccessTest(PillowTestCase): From 2d6f0f77dafacba1f20b2a309476c3d5c11fe22e Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 14 Aug 2018 12:34:40 +0300 Subject: [PATCH 27/96] Fix typo --- src/PIL/ImageTk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index d58ee8528..c56f5560a 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -189,7 +189,7 @@ class PhotoImage(object): from cffi import FFI ffi = FFI() - # Pypy is using an FFI cdata element + # PyPy is using an FFI CDATA element # (Pdb) self.tk.interp # _imagingtk.tkinit( From 9c4370e5ef0631e201f5a61e35e2a2181bd95ebf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 27 Aug 2018 20:16:41 +1000 Subject: [PATCH 28/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f77b4137b..785d41f6a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Remove unused draw.draw_line, draw.draw_point and font.getabc methods #3232 + [hugovk] + - Tests: Added ImageFilter tests #3295 [radarhere] From 353159c74f94e15b68ee141677f84a2c5e5b5d52 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Aug 2018 20:41:47 +1000 Subject: [PATCH 29/96] Only update the Pillow submodule of pillow-wheels [ci skip] --- RELEASING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 7794612fa..b5e548e06 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -89,7 +89,7 @@ Released as needed privately to individual vendors for critical security-related $ git clone https://github.com/python-pillow/pillow-wheels $ cd pillow-wheels $ git submodule init - $ git submodule update + $ git submodule update Pillow $ cd Pillow $ git fetch --all $ git checkout [[release tag]] From c6ea6a1cc2fd6a13357d09776dd878bfb0a84444 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Aug 2018 20:42:29 +1000 Subject: [PATCH 30/96] Add updated submodule in pillow-wheels [ci skip] --- RELEASING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASING.md b/RELEASING.md index b5e548e06..8812318fc 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -94,6 +94,7 @@ Released as needed privately to individual vendors for critical security-related $ git fetch --all $ git checkout [[release tag]] $ cd .. + $ git add Pillow $ git commit -m "Pillow -> 5.2.0" Pillow $ git push ``` From 63fb52219cf2d7011b520ad6e0d99013bc486ba2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Aug 2018 19:21:00 +1000 Subject: [PATCH 31/96] Revert "Add updated submodule in pillow-wheels [ci skip]" This reverts commit c6ea6a1cc2fd6a13357d09776dd878bfb0a84444. --- RELEASING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/RELEASING.md b/RELEASING.md index 8812318fc..b5e548e06 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -94,7 +94,6 @@ Released as needed privately to individual vendors for critical security-related $ git fetch --all $ git checkout [[release tag]] $ cd .. - $ git add Pillow $ git commit -m "Pillow -> 5.2.0" Pillow $ git push ``` From 4ec322aa7d35f632db5da102c5f7ceb1cbc9609f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Sep 2018 09:50:49 +1000 Subject: [PATCH 32/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 785d41f6a..7efb178af 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Changed ImageFilter.Kernel to subclass ImageFilter.BuiltinFilter, instead of the other way around #3273 + [radarhere] + - Remove unused draw.draw_line, draw.draw_point and font.getabc methods #3232 [hugovk] From 0411caba67b8127694be4dabbdedd1dc65d12576 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Sep 2018 17:18:13 +1000 Subject: [PATCH 33/96] Catch ValueError when processing the edge of an image --- Tests/test_imagedraw.py | 5 +++++ src/PIL/ImageDraw.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 33e7f8477..5f9649a78 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -366,6 +366,11 @@ class TestImageDraw(PillowTestCase): ImageDraw.floodfill(im, (W, H), red) self.assert_image_equal(im, im_floodfill) + # Test filling at the edge of an image + im = Image.new("RGB", (1, 1)) + ImageDraw.floodfill(im, (0, 0), red) + self.assert_image_equal(im, Image.new("RGB", (1, 1), red)) + def test_floodfill_border(self): # floodfill() is experimental diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index ca8c1d17b..428174784 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -358,7 +358,7 @@ def floodfill(image, xy, value, border=None, thresh=0): for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): try: p = pixel[s, t] - except IndexError: + except (ValueError, IndexError): pass else: if _color_diff(p, background) <= thresh: @@ -372,7 +372,7 @@ def floodfill(image, xy, value, border=None, thresh=0): for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): try: p = pixel[s, t] - except IndexError: + except (ValueError, IndexError): pass else: if p != value and p != border: From 4a34116be5e800d05bed62ef7c2e588f7e686a07 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Sep 2018 18:30:04 +1000 Subject: [PATCH 34/96] Removed duplicate code --- src/PIL/ImageDraw.py | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 428174784..02fa865aa 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -351,34 +351,23 @@ def floodfill(image, xy, value, border=None, thresh=0): except (ValueError, IndexError): return # seed point outside image edge = [(x, y)] - if border is None: - while edge: - newedge = [] - for (x, y) in edge: - for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): - try: - p = pixel[s, t] - except (ValueError, IndexError): - pass + while edge: + newedge = [] + for (x, y) in edge: + for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): + try: + p = pixel[s, t] + except (ValueError, IndexError): + pass + else: + if border is None: + fill = _color_diff(p, background) <= thresh else: - if _color_diff(p, background) <= thresh: - pixel[s, t] = value - newedge.append((s, t)) - edge = newedge - else: - while edge: - newedge = [] - for (x, y) in edge: - for (s, t) in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)): - try: - p = pixel[s, t] - except (ValueError, IndexError): - pass - else: - if p != value and p != border: - pixel[s, t] = value - newedge.append((s, t)) - edge = newedge + fill = p != value and p != border + if fill: + pixel[s, t] = value + newedge.append((s, t)) + edge = newedge def _color_diff(rgb1, rgb2): From 3cf1a4ea87b8f17b59c623ccac4a62a16825464d Mon Sep 17 00:00:00 2001 From: yo1995 Date: Tue, 4 Sep 2018 12:03:20 -0400 Subject: [PATCH 35/96] improved comments and one logic according to PR 3294 discussion --- src/PIL/ImageDraw.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 3335760f4..517135a44 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -352,7 +352,7 @@ def floodfill(image, xy, value, border=None, thresh=0): except (ValueError, IndexError): return # seed point outside image edge = {(x, y)} - full_edge = set() # use a set to record each unique pixel processed + full_edge = set() # use a set to keep record of current and previous edge pixels to reduce memory consumption while edge: new_edge = set() for (x, y) in edge: # 4 adjacent method @@ -364,6 +364,7 @@ def floodfill(image, xy, value, border=None, thresh=0): except (ValueError, IndexError): pass else: + full_edge.add((s, t)) if border is None: fill = _color_diff(p, background) <= thresh else: @@ -371,8 +372,7 @@ def floodfill(image, xy, value, border=None, thresh=0): if fill: pixel[s, t] = value new_edge.add((s, t)) - full_edge.add((s, t)) - full_edge = edge # do not record useless pixels to reduce memory consumption + full_edge = edge # discard pixels proceeded edge = new_edge From f3edf52900d973ae6129e0c900d5e98911c1e6af Mon Sep 17 00:00:00 2001 From: Konstantin Kopachev Date: Tue, 4 Sep 2018 11:00:29 -0700 Subject: [PATCH 36/96] Convert unsupported WebP mode to RGB as .convert supports more src modes --- src/PIL/WebPImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 011570862..1334f8d4d 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -246,7 +246,7 @@ def _save_all(im, fp, filename): if ims.mode not in _VALID_WEBP_MODES: alpha = 'A' in ims.mode or 'a' in ims.mode \ or (ims.mode == 'P' and 'A' in ims.im.getpalettemode()) - rawmode = 'RGBA' if alpha else 'RGBX' + rawmode = 'RGBA' if alpha else 'RGB' frame = ims.convert(rawmode) if rawmode == 'RGB': From bb77f62586d31193f22388182ac2420ffcdcd440 Mon Sep 17 00:00:00 2001 From: yo1995 Date: Tue, 4 Sep 2018 21:15:25 -0400 Subject: [PATCH 37/96] fix typo: proceeded -> processed --- src/PIL/ImageDraw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 517135a44..ab328fe53 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -372,7 +372,7 @@ def floodfill(image, xy, value, border=None, thresh=0): if fill: pixel[s, t] = value new_edge.add((s, t)) - full_edge = edge # discard pixels proceeded + full_edge = edge # discard pixels processed edge = new_edge From 7e67b9c58f8966ba6f91bb3de0f33ee744756e35 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 11 Nov 2016 11:18:27 -0800 Subject: [PATCH 38/96] Use TextIOWrapper.detach() instead of NoCloseStream Usage and this pattern is discussed in Python bug: https://bugs.python.org/issue21363 --- src/PIL/EpsImagePlugin.py | 75 ++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index cb2c00d20..29fae1ed2 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -357,54 +357,49 @@ def _save(im, fp, filename, eps=1): else: raise ValueError("image mode is not supported") - class NoCloseStream(object): - def __init__(self, fp): - self.fp = fp - - def __getattr__(self, name): - return getattr(self.fp, name) - - def close(self): - pass - base_fp = fp + wrapped_fp = False if fp != sys.stdout: - fp = NoCloseStream(fp) if sys.version_info.major > 2: fp = io.TextIOWrapper(fp, encoding='latin-1') + wrapped_fp = True + + try: + if eps: + # + # write EPS header + fp.write("%!PS-Adobe-3.0 EPSF-3.0\n") + fp.write("%%Creator: PIL 0.1 EpsEncode\n") + # fp.write("%%CreationDate: %s"...) + fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size) + fp.write("%%Pages: 1\n") + fp.write("%%EndComments\n") + fp.write("%%Page: 1 1\n") + fp.write("%%ImageData: %d %d " % im.size) + fp.write("%d %d 0 1 1 \"%s\"\n" % operator) - if eps: # - # write EPS header - fp.write("%!PS-Adobe-3.0 EPSF-3.0\n") - fp.write("%%Creator: PIL 0.1 EpsEncode\n") - # fp.write("%%CreationDate: %s"...) - fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size) - fp.write("%%Pages: 1\n") - fp.write("%%EndComments\n") - fp.write("%%Page: 1 1\n") - fp.write("%%ImageData: %d %d " % im.size) - fp.write("%d %d 0 1 1 \"%s\"\n" % operator) + # image header + fp.write("gsave\n") + fp.write("10 dict begin\n") + fp.write("/buf %d string def\n" % (im.size[0] * operator[1])) + fp.write("%d %d scale\n" % im.size) + fp.write("%d %d 8\n" % im.size) # <= bits + fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) + fp.write("{ currentfile buf readhexstring pop } bind\n") + fp.write(operator[2] + "\n") + if hasattr(fp, "flush"): + fp.flush() - # - # image header - fp.write("gsave\n") - fp.write("10 dict begin\n") - fp.write("/buf %d string def\n" % (im.size[0] * operator[1])) - fp.write("%d %d scale\n" % im.size) - fp.write("%d %d 8\n" % im.size) # <= bits - fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1])) - fp.write("{ currentfile buf readhexstring pop } bind\n") - fp.write(operator[2] + "\n") - if hasattr(fp, "flush"): - fp.flush() + ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)]) - ImageFile._save(im, base_fp, [("eps", (0, 0)+im.size, 0, None)]) - - fp.write("\n%%%%EndBinary\n") - fp.write("grestore end\n") - if hasattr(fp, "flush"): - fp.flush() + fp.write("\n%%%%EndBinary\n") + fp.write("grestore end\n") + if hasattr(fp, "flush"): + fp.flush() + finally: + if wrapped_fp: + fp.detach() # # -------------------------------------------------------------------- From 875e8c4bda09332c016f609f1a96c5a4868ec2ff Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 6 Nov 2016 08:58:45 -0800 Subject: [PATCH 39/96] Avoid catching unexpected exceptions in tests Instead, allow exceptions to bubble up to the unittest exception handler. Prevents replacing the exception trace with a less informative message. As the exceptions are always unexpected, should not need to catch them explicitly in tests. --- Tests/test_file_jpeg.py | 8 +++----- Tests/test_file_libtiff.py | 6 ++---- Tests/test_file_tiff.py | 7 ++----- Tests/test_file_tiff_metadata.py | 18 ++++++------------ 4 files changed, 13 insertions(+), 26 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index c42d67885..1485651c7 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -141,11 +141,9 @@ class TestFileJpeg(PillowTestCase): im = Image.open('Tests/images/icc_profile_big.jpg') f = self.tempfile("temp.jpg") icc_profile = im.info["icc_profile"] - try: - im.save(f, format='JPEG', progressive=True, quality=95, - icc_profile=icc_profile, optimize=True) - except IOError: - self.fail("Failed saving image with icc larger than image size") + # Should not raise IOError for image with icc larger than image size. + im.save(f, format='JPEG', progressive=True, quality=95, + icc_profile=icc_profile, optimize=True) def test_optimize(self): im1 = self.roundtrip(hopper()) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 77caa0b9d..8b1d45708 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -529,10 +529,8 @@ class TestFileLibTiff(LibTiffTestCase): im = Image.open(tmpfile) im.n_frames im.close() - try: - os.remove(tmpfile) # Windows PermissionError here! - except: - self.fail("Should not get permission error here") + # Should not raise PermissionError. + os.remove(tmpfile) def test_read_icc(self): with Image.open("Tests/images/hopper.iccprofile.tif") as img: diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 79630c773..68429f249 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -133,11 +133,8 @@ class TestFileTiff(PillowTestCase): def test_bad_exif(self): i = Image.open('Tests/images/hopper_bad_exif.jpg') - try: - self.assert_warning(UserWarning, i._getexif) - except struct.error: - self.fail( - "Bad EXIF data passed incorrect values to _binary unpack") + # Should not raise struct.error. + self.assert_warning(UserWarning, i._getexif) def test_save_rgba(self): im = hopper("RGBA") diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index aabedd6c8..0a958454c 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -169,10 +169,8 @@ class TestFileTiffMetadata(PillowTestCase): f = io.BytesIO(b'II*\x00\x08\x00\x00\x00') head = f.read(8) info = TiffImagePlugin.ImageFileDirectory(head) - try: - self.assert_warning(UserWarning, info.load, f) - except struct.error: - self.fail("Should not be struct errors there.") + # Should not raise struct.error. + self.assert_warning(UserWarning, info.load, f) def test_iccprofile(self): # https://github.com/python-pillow/Pillow/issues/1462 @@ -223,10 +221,8 @@ class TestFileTiffMetadata(PillowTestCase): head = data.read(8) info = TiffImagePlugin.ImageFileDirectory_v2(head) info.load(data) - try: - info = dict(info) - except ValueError: - self.fail("Should not be struct value error there.") + # Should not raise ValueError. + info = dict(info) self.assertIn(33432, info) def test_PhotoshopInfo(self): @@ -245,10 +241,8 @@ class TestFileTiffMetadata(PillowTestCase): ifd._tagdata[277] = struct.pack('hh', 4, 4) ifd.tagtype[277] = TiffTags.SHORT - try: - self.assert_warning(UserWarning, lambda: ifd[277]) - except ValueError: - self.fail("Invalid Metadata count should not cause a Value Error.") + # Should not raise ValueError. + self.assert_warning(UserWarning, lambda: ifd[277]) if __name__ == '__main__': From 37f10651bdd97df4ea907b772ead24d1c8efcea6 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 4 Sep 2018 20:02:42 -0700 Subject: [PATCH 40/96] Clean up commented out debug print statements --- Tests/make_hash.py | 5 ----- Tests/test_bmp_reference.py | 2 -- Tests/test_format_hsv.py | 32 -------------------------------- Tests/test_image_resample.py | 3 --- Tests/test_imagewin_pointers.py | 1 - Tests/test_numpy.py | 1 - src/PIL/BdfFontFile.py | 14 -------------- src/PIL/CurImagePlugin.py | 8 -------- src/PIL/EpsImagePlugin.py | 1 - src/PIL/FontFile.py | 1 - src/PIL/FpxImagePlugin.py | 4 ---- src/PIL/Image.py | 3 --- src/PIL/ImageMorph.py | 5 ----- src/PIL/IptcImagePlugin.py | 2 -- src/PIL/JpegImagePlugin.py | 1 - src/PIL/SpiderImagePlugin.py | 1 - src/PIL/TiffImagePlugin.py | 4 ---- src/PIL/WmfImagePlugin.py | 2 -- 18 files changed, 90 deletions(-) diff --git a/Tests/make_hash.py b/Tests/make_hash.py index c5e32d606..c52e009ec 100644 --- a/Tests/make_hash.py +++ b/Tests/make_hash.py @@ -52,10 +52,5 @@ for i0 in range(65556): print() -# print(check(min_size, min_start)) - print("#define ACCESS_TABLE_SIZE", min_size) print("#define ACCESS_TABLE_HASH", min_start) - -# for m in modes: -# print(m, "=>", hash(m, min_start) % min_size) diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 8e84cc8f1..015a9ebdf 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -22,7 +22,6 @@ class TestBmpReference(PillowTestCase): im.load() except Exception: # as msg: pass - # print("Bad Image %s: %s" %(f,msg)) def test_questionable(self): """ These shouldn't crash/dos, but it's not well defined that these @@ -47,7 +46,6 @@ class TestBmpReference(PillowTestCase): except Exception: # as msg: if os.path.basename(f) in supported: raise - # print("Bad Image %s: %s" %(f,msg)) def test_good(self): """ These should all work. There's a set of target files in the diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index 31309da60..d7820400e 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -47,10 +47,6 @@ class TestFormatHSV(PillowTestCase): img = Image.merge('RGB', (r, g, b)) - # print(("%d, %d -> "% (int(1.75*px),int(.25*px))) + \ - # "(%s, %s, %s)"%img.getpixel((1.75*px, .25*px))) - # print(("%d, %d -> "% (int(.75*px),int(.25*px))) + \ - # "(%s, %s, %s)"%img.getpixel((.75*px, .25*px))) return img def to_xxx_colorsys(self, im, func, mode): @@ -95,15 +91,6 @@ class TestFormatHSV(PillowTestCase): im = src.convert('HSV') comparable = self.to_hsv_colorsys(src) - # print(im.getpixel((448, 64))) - # print(comparable.getpixel((448, 64))) - - # print(im.split()[0].histogram()) - # print(comparable.split()[0].histogram()) - - # im.split()[0].show() - # comparable.split()[0].show() - self.assert_image_similar(im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong") self.assert_image_similar(im.getchannel(1), comparable.getchannel(1), @@ -111,16 +98,9 @@ class TestFormatHSV(PillowTestCase): self.assert_image_similar(im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong") - # print(im.getpixel((192, 64))) - comparable = src im = im.convert('RGB') - # im.split()[0].show() - # comparable.split()[0].show() - # print(im.getpixel((192, 64))) - # print(comparable.getpixel((192, 64))) - self.assert_image_similar(im.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong") self.assert_image_similar(im.getchannel(1), comparable.getchannel(1), @@ -132,12 +112,6 @@ class TestFormatHSV(PillowTestCase): im = hopper('RGB').convert('HSV') comparable = self.to_hsv_colorsys(hopper('RGB')) -# print([ord(x) for x in im.split()[0].tobytes()[:80]]) -# print([ord(x) for x in comparable.split()[0].tobytes()[:80]]) - -# print(im.split()[0].histogram()) -# print(comparable.split()[0].histogram()) - self.assert_image_similar(im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong") self.assert_image_similar(im.getchannel(1), comparable.getchannel(1), @@ -150,12 +124,6 @@ class TestFormatHSV(PillowTestCase): converted = comparable.convert('RGB') comparable = self.to_rgb_colorsys(comparable) - # print(converted.split()[1].histogram()) - # print(target.split()[1].histogram()) - - # print([ord(x) for x in target.split()[1].tobytes()[:80]]) - # print([ord(x) for x in converted.split()[1].tobytes()[:80]]) - self.assert_image_similar(converted.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong") diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index 3687a1a2c..1b7081d55 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -352,10 +352,8 @@ class CoreResamplePassesTest(PillowTestCase): class CoreResampleCoefficientsTest(PillowTestCase): def test_reduce(self): test_color = 254 - # print() for size in range(400000, 400010, 2): - # print(size) i = Image.new('L', (size, 1), 0) draw = ImageDraw.Draw(i) draw.rectangle((0, 0, i.size[0] // 2 - 1, 0), test_color) @@ -363,7 +361,6 @@ class CoreResampleCoefficientsTest(PillowTestCase): px = i.resize((5, i.size[1]), Image.BICUBIC).load() if px[2, 0] != test_color // 2: self.assertEqual(test_color // 2, px[2, 0]) - # print('>', size, test_color // 2, px[2, 0]) def test_nonzero_coefficients(self): # regression test for the wrong coefficients calculation diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index 7178d8cb8..dc78b655c 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -96,7 +96,6 @@ if sys.platform.startswith('win32'): hdr.biClrImportant = 0 hdc = CreateCompatibleDC(None) - # print('hdc:',hex(hdc)) pixels = ctypes.c_void_p() dib = CreateDIBSection(hdc, ctypes.byref(hdr), DIB_RGB_COLORS, ctypes.byref(pixels), None, 0) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 4efcd2c51..03643ac1e 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -34,7 +34,6 @@ class TestNumpy(PillowTestCase): i = Image.fromarray(a) if list(i.getchannel(0).getdata()) != list(range(100)): print("data mismatch for", dtype) - # print(dtype, list(i.getdata())) return i # Check supported 1-bit integer formats diff --git a/src/PIL/BdfFontFile.py b/src/PIL/BdfFontFile.py index c8bc60461..eac19bde1 100644 --- a/src/PIL/BdfFontFile.py +++ b/src/PIL/BdfFontFile.py @@ -110,20 +110,6 @@ class BdfFontFile(FontFile.FontFile): if s.find(b"LogicalFontDescription") < 0: comments.append(s[i+1:-1].decode('ascii')) - # font = props["FONT"].split("-") - - # font[4] = bdf_slant[font[4].upper()] - # font[11] = bdf_spacing[font[11].upper()] - - # ascent = int(props["FONT_ASCENT"]) - # descent = int(props["FONT_DESCENT"]) - - # fontname = ";".join(font[1:]) - - # print("#", fontname) - # for i in comments: - # print("#", i) - while True: c = bdf_char(fp) if not c: diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index e4257cd5a..db9ea9874 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -56,14 +56,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): m = s elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]): m = s - # print("width", i8(s[0])) - # print("height", i8(s[1])) - # print("colors", i8(s[2])) - # print("reserved", i8(s[3])) - # print("hotspot x", i16(s[4:])) - # print("hotspot y", i16(s[6:])) - # print("bytes", i32(s[8:])) - # print("offset", i32(s[12:])) if not m: raise TypeError("No cursors were found") diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index cb2c00d20..0c50afe13 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -82,7 +82,6 @@ def Ghostscript(tile, size, fp, scale=1): # resolution is dependent on bbox and size res = (float((72.0 * size[0]) / (bbox[2]-bbox[0])), float((72.0 * size[1]) / (bbox[3]-bbox[1]))) - # print("Ghostscript", scale, size, orig_size, bbox, orig_bbox, res) import subprocess import tempfile diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py index 46e49bc4e..305e8afa2 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -90,7 +90,6 @@ class FontFile(object): x = xx s = src[0] + x0, src[1] + y0, src[2] + x0, src[3] + y0 self.bitmap.paste(im.crop(src), s) - # print(chr(i), dst, s) self.metrics[i] = d, dst, s def save(self, filename): diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index d7bba42eb..509fd7d91 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -114,8 +114,6 @@ class FpxImageFile(ImageFile.ImageFile): if id in prop: self.jpeg[i] = prop[id] - # print(len(self.jpeg), "tables loaded") - self._open_subimage(1, self.maxid) def _open_subimage(self, index=1, subimage=0): @@ -143,8 +141,6 @@ class FpxImageFile(ImageFile.ImageFile): offset = i32(s, 28) length = i32(s, 32) - # print(size, self.mode, self.rawmode) - if size != self.size: raise IOError("subimage mismatch") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f13a98276..76fb3be06 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -443,7 +443,6 @@ def _getdecoder(mode, decoder_name, args, extra=()): try: # get decoder decoder = getattr(core, decoder_name + "_decoder") - # print(decoder, mode, args + extra) return decoder(mode, *args + extra) except AttributeError: raise IOError("decoder %s not available" % decoder_name) @@ -465,7 +464,6 @@ def _getencoder(mode, encoder_name, args, extra=()): try: # get encoder encoder = getattr(core, encoder_name + "_encoder") - # print(encoder, mode, args + extra) return encoder(mode, *args + extra) except AttributeError: raise IOError("encoder %s not available" % encoder_name) @@ -2470,7 +2468,6 @@ def fromarray(obj, mode=None): typekey = (1, 1) + shape[2:], arr['typestr'] mode, rawmode = _fromarray_typemap[typekey] except KeyError: - # print(typekey) raise TypeError("Cannot handle this data type") else: rawmode = mode diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index 579ee4e1a..54ceb7905 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -151,11 +151,6 @@ class LutBuilder(object): patterns += self._pattern_permute(pattern, options, result) -# # Debugging -# for p, r in patterns: -# print(p, r) -# print('--') - # compile the patterns into regular expressions for speed for i, pattern in enumerate(patterns): p = pattern[0].replace('.', 'X').replace('X', '[01]') diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index f5a8de17e..83885e284 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -103,8 +103,6 @@ class IptcImageFile(ImageFile.ImageFile): else: self.info[tag] = tagdata - # print(tag, self.info[tag]) - # mode layers = i8(self.info[(3, 60)][0]) component = i8(self.info[(3, 60)][1]) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index a75e3d428..e38306041 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -334,7 +334,6 @@ class JpegImageFile(ImageFile.ImageFile): if i in MARKER: name, description, handler = MARKER[i] - # print(hex(i), name, description) if handler is not None: handler(self, i) if i == 0xFFDA: # start of scan diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index d502779e2..a0d5f17a4 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -74,7 +74,6 @@ def isSpiderHeader(t): labrec = int(h[13]) # no. records in file header labbyt = int(h[22]) # total no. of bytes in header lenbyt = int(h[23]) # record length in bytes - # print("labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt)) if labbyt != (labrec * lenbyt): return 0 # looks like a valid header diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 66b211cbf..715a543d9 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1254,9 +1254,6 @@ class TiffImageFile(ImageFile.ImageFile): h = self.tag_v2.get(ROWSPERSTRIP, ysize) w = self.size[0] if READ_LIBTIFF or self._compression != 'raw': - # if DEBUG: - # print("Activating g4 compression for whole file") - # Decoder expects entire file as one tile. # There's a buffer size limit in load (64k) # so large g4 images will fail if we use that @@ -1529,7 +1526,6 @@ def _save(im, fp, filename): rawmode = 'I;16N' a = (rawmode, compression, _fp, filename, atts) - # print(im.mode, compression, a, im.encoderconfig) e = Image._getencoder(im.mode, 'libtiff', a, im.encoderconfig) e.setimage(im.im, (0, 0)+im.size) while True: diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index aeb19374d..6811ddcec 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -109,8 +109,6 @@ class WmfStubImageFile(ImageFile.StubImageFile): self.info["dpi"] = 72 - # print(self.mode, self.size, self.info) - # sanity check (standard metafile header) if s[22:26] != b"\x01\x00\t\x00": raise SyntaxError("Unsupported WMF file format") From d86fa316ca328f1d6647afb06cf89f5fddd45a33 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Sep 2018 21:02:32 +1000 Subject: [PATCH 41/96] Fixed typo [ci skip] --- docs/handbook/writing-your-own-file-decoder.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index aa2463bd1..258e9af3e 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -204,7 +204,7 @@ table describes some commonly used **raw modes**: +-----------+-----------------------------------------------------------------+ | ``RGBX`` | 24-bit true colour, stored as (red, green, blue, pad). | +-----------+-----------------------------------------------------------------+ -| ``RGB;L`` | 24-bit true colour, line interleaved (first all red pixels, the | +| ``RGB;L`` | 24-bit true colour, line interleaved (first all red pixels, then| | | all green pixels, finally all blue pixels). | +-----------+-----------------------------------------------------------------+ From f4fd517373d5a36887d0cd20fcc16de39b85275e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Sep 2018 21:05:09 +1000 Subject: [PATCH 42/96] Removed blank line after heading for consistency [ci skip] --- docs/handbook/writing-your-own-file-decoder.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 258e9af3e..107e25f36 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -171,7 +171,6 @@ The fields are used as follows: stride defaults to 0. **orientation** - Whether the first line in the image is the top line on the screen (1), or the bottom line (-1). If omitted, the orientation defaults to 1. From ad5cf0a0e24dbfa942e685ba89416b307562463f Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 5 Sep 2018 17:36:27 +0300 Subject: [PATCH 43/96] Add RGBAX and RGBAXX tiff modes --- Tests/test_lib_pack.py | 4 ++++ src/PIL/TiffImagePlugin.py | 4 ++++ src/libImaging/Unpack.c | 2 ++ 3 files changed, 10 insertions(+) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 002db2e19..921af3bea 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -273,6 +273,10 @@ class TestLibUnpack(PillowTestCase): (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11)) self.assert_unpack("RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_unpack("RGBA", "RGBAX", 5, + (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) + self.assert_unpack("RGBA", "RGBAXX", 6, + (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) self.assert_unpack("RGBA", "RGBa", 4, (63, 127, 191, 4), (159, 191, 223, 8), (191, 212, 233, 12)) self.assert_unpack("RGBA", "RGBa", diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 6f032f49d..17b133c82 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -210,6 +210,10 @@ OPEN_INFO = { (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"), (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"), (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 05b4299b0..04e05dcb3 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1301,6 +1301,8 @@ static struct { {"RGBA", "LA", 16, unpackRGBALA}, {"RGBA", "LA;16B", 32, unpackRGBALA16B}, {"RGBA", "RGBA", 32, copy4}, + {"RGBA", "RGBAX", 40, copy4skip1}, + {"RGBA", "RGBAXX", 48, copy4skip2}, {"RGBA", "RGBa", 32, unpackRGBa}, {"RGBA", "RGBa;16L", 64, unpackRGBa16L}, {"RGBA", "RGBa;16B", 64, unpackRGBa16B}, From accc66fb5e4213c8a20edcf7a6a82f5eeb1a4515 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 5 Sep 2018 17:45:03 +0300 Subject: [PATCH 44/96] add support for RGBaXX and RGBaX raw tiff modes --- Tests/test_lib_pack.py | 10 +++++++-- src/PIL/TiffImagePlugin.py | 4 ++++ src/libImaging/Unpack.c | 44 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 921af3bea..d4e50b0ab 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -280,8 +280,14 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("RGBA", "RGBa", 4, (63, 127, 191, 4), (159, 191, 223, 8), (191, 212, 233, 12)) self.assert_unpack("RGBA", "RGBa", - b'\x01\x02\x03\x00\x10\x20\x30\xff', - (0, 0, 0, 0), (16, 32, 48, 255)) + b'\x01\x02\x03\x00\x10\x20\x30\x7f\x10\x20\x30\xff', + (0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255)) + self.assert_unpack("RGBA", "RGBaX", + b'\x01\x02\x03\x00-\x10\x20\x30\x7f-\x10\x20\x30\xff-', + (0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255)) + self.assert_unpack("RGBA", "RGBaXX", + b'\x01\x02\x03\x00==\x10\x20\x30\x7f!!\x10\x20\x30\xff??', + (0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255)) self.assert_unpack("RGBA", "RGBa;16L", 8, (63, 127, 191, 8), (159, 191, 223, 16), (191, 212, 233, 24)) self.assert_unpack("RGBA", "RGBa;16L", diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 17b133c82..e8ea365e8 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -208,6 +208,10 @@ OPEN_INFO = { (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"), (II, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"), (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"), + (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"), + (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"), (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"), diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 04e05dcb3..7bb8064ee 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -797,6 +797,48 @@ unpackRGBa(UINT8* _out, const UINT8* in, int pixels) } } +static void +unpackRGBaskip1(UINT8* _out, const UINT8* in, int pixels) +{ + int i; + UINT32* out = (UINT32*) _out; + /* premultiplied RGBA */ + for (i = 0; i < pixels; i++) { + int a = in[3]; + if ( ! a) { + out[i] = 0; + } else if (a == 255) { + out[i] = MAKE_UINT32(in[0], in[1], in[2], a); + } else { + out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a), + CLIP8(in[1] * 255 / a), + CLIP8(in[2] * 255 / a), a); + } + in += 5; + } +} + +static void +unpackRGBaskip2(UINT8* _out, const UINT8* in, int pixels) +{ + int i; + UINT32* out = (UINT32*) _out; + /* premultiplied RGBA */ + for (i = 0; i < pixels; i++) { + int a = in[3]; + if ( ! a) { + out[i] = 0; + } else if (a == 255) { + out[i] = MAKE_UINT32(in[0], in[1], in[2], a); + } else { + out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a), + CLIP8(in[1] * 255 / a), + CLIP8(in[2] * 255 / a), a); + } + in += 6; + } +} + static void unpackBGRa(UINT8* _out, const UINT8* in, int pixels) { @@ -1304,6 +1346,8 @@ static struct { {"RGBA", "RGBAX", 40, copy4skip1}, {"RGBA", "RGBAXX", 48, copy4skip2}, {"RGBA", "RGBa", 32, unpackRGBa}, + {"RGBA", "RGBaX", 40, unpackRGBaskip1}, + {"RGBA", "RGBaXX", 48, unpackRGBaskip2}, {"RGBA", "RGBa;16L", 64, unpackRGBa16L}, {"RGBA", "RGBa;16B", 64, unpackRGBa16B}, {"RGBA", "BGRa", 32, unpackBGRa}, From 5b24987fc3035ecae065ffcfdeb6dac78a7661c3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 5 Sep 2018 17:51:41 +0300 Subject: [PATCH 45/96] update code style in tests --- Tests/test_lib_pack.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index d4e50b0ab..94ea8e8ef 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -268,27 +268,33 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) def test_RGBA(self): - self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) - self.assert_unpack("RGBA", "LA;16B", 4, - (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11)) - self.assert_unpack("RGBA", "RGBA", 4, - (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) - self.assert_unpack("RGBA", "RGBAX", 5, - (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) - self.assert_unpack("RGBA", "RGBAXX", 6, - (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) - self.assert_unpack("RGBA", "RGBa", 4, + self.assert_unpack( + "RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) + self.assert_unpack( + "RGBA", "LA;16B", 4, (1, 1, 1, 3), (5, 5, 5, 7), (9, 9, 9, 11)) + self.assert_unpack( + "RGBA", "RGBA", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12)) + self.assert_unpack( + "RGBA", "RGBAX", 5, (1, 2, 3, 4), (6, 7, 8, 9), (11, 12, 13, 14)) + self.assert_unpack( + "RGBA", "RGBAXX", 6, (1, 2, 3, 4), (7, 8, 9, 10), (13, 14, 15, 16)) + self.assert_unpack( + "RGBA", "RGBa", 4, (63, 127, 191, 4), (159, 191, 223, 8), (191, 212, 233, 12)) - self.assert_unpack("RGBA", "RGBa", + self.assert_unpack( + "RGBA", "RGBa", b'\x01\x02\x03\x00\x10\x20\x30\x7f\x10\x20\x30\xff', (0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255)) - self.assert_unpack("RGBA", "RGBaX", + self.assert_unpack( + "RGBA", "RGBaX", b'\x01\x02\x03\x00-\x10\x20\x30\x7f-\x10\x20\x30\xff-', (0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255)) - self.assert_unpack("RGBA", "RGBaXX", + self.assert_unpack( + "RGBA", "RGBaXX", b'\x01\x02\x03\x00==\x10\x20\x30\x7f!!\x10\x20\x30\xff??', (0, 0, 0, 0), (32, 64, 96, 127), (16, 32, 48, 255)) - self.assert_unpack("RGBA", "RGBa;16L", 8, + self.assert_unpack( + "RGBA", "RGBa;16L", 8, (63, 127, 191, 8), (159, 191, 223, 16), (191, 212, 233, 24)) self.assert_unpack("RGBA", "RGBa;16L", b'\x88\x01\x88\x02\x88\x03\x88\x00' From 66207b47bca89a4fad8f4573a239b671d66b7b90 Mon Sep 17 00:00:00 2001 From: dinko Date: Thu, 23 Aug 2018 15:40:46 +0200 Subject: [PATCH 46/96] fix _crop and tests --- Tests/test_decompression_bomb.py | 23 +++++++++++++++++++++++ Tests/test_image_crop.py | 1 - src/PIL/Image.py | 7 ++----- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 4e3c26377..b0b9430d4 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -6,6 +6,7 @@ TEST_FILE = "Tests/images/hopper.ppm" ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS +from PIL.Image import DecompressionBombWarning, DecompressionBombError class TestDecompressionBomb(PillowTestCase): @@ -61,6 +62,28 @@ class TestDecompressionCrop(PillowTestCase): self.assert_warning(Image.DecompressionBombWarning, self.src.crop, box) + def test_crop_decompression_checks(self): + + im = Image.new("RGB", (100, 100)) + + good_values = ((-9999, -9999, -9990, -9990), + (-999, -999, -990, -990)) + + warning_values = ((-160, -160, 99, 99), + (160, 160, -99, -99)) + + error_values = ((-99909, -99990, 99999, 99999), + (99909, 99990, -99999, -99999)) + + for value in good_values: + self.assertEqual(im.crop(value).size, (9,9)) + + for value in warning_values: + self.assert_warning(DecompressionBombWarning, im.crop, value) + + for value in error_values: + with self.assertRaises(DecompressionBombError): + im.crop(value) if __name__ == '__main__': unittest.main() diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index fe92dd865..27a7f07d1 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -2,7 +2,6 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image - class TestImageCrop(PillowTestCase): def test_crop(self): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c58952657..9e73f901d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1104,12 +1104,9 @@ class Image(object): x0, y0, x1, y1 = map(int, map(round, box)) - if x1 < x0: - x1 = x0 - if y1 < y0: - y1 = y0 + absolute_values = (abs(x1 - x0), abs(y1 - y0)) - _decompression_bomb_check((x1, y1)) + _decompression_bomb_check(absolute_values) return im.crop((x0, y0, x1, y1)) From 41954f244705b247667f1ea228e932ca6390bcd6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 6 Sep 2018 21:10:29 +1000 Subject: [PATCH 47/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7efb178af..3d43539de 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Fix code for PYTHONOPTIMIZE #3233 + [hugovk] + - Changed ImageFilter.Kernel to subclass ImageFilter.BuiltinFilter, instead of the other way around #3273 [radarhere] From 4cfcc3b010cb854f22035a8f955bdbe5dd639746 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 13 Dec 2016 12:49:47 -0800 Subject: [PATCH 48/96] Tests for issue #1765 --- Tests/test_file_libtiff.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 77caa0b9d..7735b5bea 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -231,6 +231,16 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.WRITE_LIBTIFF = False + def test_int_dpi(self): + # issue #1765 + im = hopper('RGB') + out = self.tempfile('temp.tif') + TiffImagePlugin.WRITE_LIBTIFF = True + im.save(out, dpi=(72, 72)) + TiffImagePlugin.WRITE_LIBTIFF = False + reloaded = Image.open(out) + self.assertEqual(reloaded.info['dpi'], (72.0, 72.0)) + def test_g3_compression(self): i = Image.open('Tests/images/hopper_g4_500.tif') out = self.tempfile("temp.tif") From 0a44d583149299a185754d0ac35e0eaaec60fc71 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 7 Sep 2018 20:35:55 +1000 Subject: [PATCH 49/96] Convert int values of RATIONAL TIFF tags to floats --- src/PIL/TiffImagePlugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 66b211cbf..c1a785ef3 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -567,6 +567,9 @@ class ImageFileDirectory_v2(MutableMapping): if self.tagtype[tag] == 7 and py3: values = [value.encode("ascii", 'replace') if isinstance( value, str) else value] + elif self.tagtype[tag] == 5: + values = [float(v) if isinstance(v, int) else v + for v in values] values = tuple(info.cvt_enum(value) for value in values) From 9e4c54e10f40051011cdec4342021e4c2f202809 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Sep 2018 23:31:39 +1000 Subject: [PATCH 50/96] Added orientation, compression and id_section as keyword arguments --- Tests/test_file_tga.py | 84 +++++++++++++++++++++++++++++++-------- src/PIL/TgaImagePlugin.py | 20 ++++++++-- 2 files changed, 84 insertions(+), 20 deletions(-) diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 226b899dc..1fcd62b88 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -53,10 +53,10 @@ class TestFileTga(PillowTestCase): # Generate a new test name every time so the # test will not fail with permission error # on Windows. - test_file = self.tempfile("temp.tga") + out = self.tempfile("temp.tga") - original_im.save(test_file, rle=rle) - saved_im = Image.open(test_file) + original_im.save(out, rle=rle) + saved_im = Image.open(out) if rle: self.assertEqual( saved_im.info["compression"], @@ -95,34 +95,86 @@ class TestFileTga(PillowTestCase): test_file = "Tests/images/tga_id_field.tga" im = Image.open(test_file) - test_file = self.tempfile("temp.tga") + out = self.tempfile("temp.tga") # Save - im.save(test_file) - test_im = Image.open(test_file) + im.save(out) + test_im = Image.open(out) self.assertEqual(test_im.size, (100, 100)) + self.assertEqual(test_im.info["id_section"], im.info["id_section"]) # RGBA save - im.convert("RGBA").save(test_file) - test_im = Image.open(test_file) + im.convert("RGBA").save(out) + test_im = Image.open(out) self.assertEqual(test_im.size, (100, 100)) + def test_save_id_section(self): + test_file = "Tests/images/rgb32rle.tga" + im = Image.open(test_file) + + out = self.tempfile("temp.tga") + + # Check there is no id section + im.save(out) + test_im = Image.open(out) + self.assertNotIn("id_section", test_im.info) + + # Save with custom id section + im.save(out, id_section=b"Test content") + test_im = Image.open(out) + self.assertEqual(test_im.info["id_section"], b"Test content") + + test_file = "Tests/images/tga_id_field.tga" + im = Image.open(test_file) + + # Save with no id section + im.save(out, id_section="") + test_im = Image.open(out) + self.assertNotIn("id_section", test_im.info) + + def test_save_orientation(self): + test_file = "Tests/images/rgb32rle.tga" + im = Image.open(test_file) + self.assertEqual(im.info["orientation"], -1) + + out = self.tempfile("temp.tga") + + im.save(out, orientation=1) + test_im = Image.open(out) + self.assertEqual(test_im.info["orientation"], 1) + def test_save_rle(self): test_file = "Tests/images/rgb32rle.tga" im = Image.open(test_file) + self.assertEqual(im.info["compression"], "tga_rle") - test_file = self.tempfile("temp.tga") + out = self.tempfile("temp.tga") # Save - im.save(test_file) - test_im = Image.open(test_file) + im.save(out) + test_im = Image.open(out) self.assertEqual(test_im.size, (199, 199)) + self.assertEqual(test_im.info["compression"], "tga_rle") + + # Save without compression + im.save(out, compression=None) + test_im = Image.open(out) + self.assertNotIn("compression", test_im.info) # RGBA save - im.convert("RGBA").save(test_file) - test_im = Image.open(test_file) + im.convert("RGBA").save(out) + test_im = Image.open(out) self.assertEqual(test_im.size, (199, 199)) + test_file = "Tests/images/tga_id_field.tga" + im = Image.open(test_file) + self.assertNotIn("compression", im.info) + + # Save with compression + im.save(out, compression="tga_rle") + test_im = Image.open(out) + self.assertEqual(test_im.info["compression"], "tga_rle") + def test_save_l_transparency(self): # There are 559 transparent pixels in la.tga. num_transparent = 559 @@ -133,10 +185,10 @@ class TestFileTga(PillowTestCase): self.assertEqual( im.getchannel("A").getcolors()[0][0], num_transparent) - test_file = self.tempfile("temp.tga") - im.save(test_file) + out = self.tempfile("temp.tga") + im.save(out) - test_im = Image.open(test_file) + test_im = Image.open(out) self.assertEqual(test_im.mode, "LA") self.assertEqual( test_im.getchannel("A").getcolors()[0][0], num_transparent) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index 57b6ae2c8..c60cb3ef1 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -151,11 +151,19 @@ def _save(im, fp, filename): except KeyError: raise IOError("cannot write mode %s as TGA" % im.mode) - rle = im.encoderinfo.get("rle", False) - + if "rle" in im.encoderinfo: + rle = im.encoderinfo["rle"] + else: + compression = im.encoderinfo.get("compression", + im.info.get("compression")) + rle = compression == "tga_rle" if rle: imagetype += 8 + id_section = im.encoderinfo.get("id_section", + im.info.get("id_section", "")) + idlen = len(id_section) + if colormaptype: colormapfirst, colormaplength, colormapentry = 0, 256, 24 else: @@ -166,11 +174,12 @@ def _save(im, fp, filename): else: flags = 0 - orientation = im.info.get("orientation", -1) + orientation = im.encoderinfo.get("orientation", + im.info.get("orientation", -1)) if orientation > 0: flags = flags | 0x20 - fp.write(b"\000" + + fp.write(o8(idlen) + o8(colormaptype) + o8(imagetype) + o16(colormapfirst) + @@ -183,6 +192,9 @@ def _save(im, fp, filename): o8(bits) + o8(flags)) + if id_section: + fp.write(id_section) + if colormaptype: fp.write(im.im.getpalette("RGB", "BGR")) From 325ca3cede708a82ae76f294c9f8d1222d26a758 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Sep 2018 08:26:32 +1000 Subject: [PATCH 51/96] Trim id_section if it is greater than 255 characters --- Tests/test_file_tga.py | 7 +++++++ src/PIL/TgaImagePlugin.py | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 1fcd62b88..77695f2d1 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -124,6 +124,13 @@ class TestFileTga(PillowTestCase): test_im = Image.open(out) self.assertEqual(test_im.info["id_section"], b"Test content") + # Save with custom id section greater than 255 characters + id_section = b"Test content" * 25 + self.assert_warning(UserWarning, + lambda: im.save(out, id_section=id_section)) + test_im = Image.open(out) + self.assertEqual(test_im.info["id_section"], id_section[:255]) + test_file = "Tests/images/tga_id_field.tga" im = Image.open(test_file) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index c60cb3ef1..c622f1f5e 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -20,6 +20,8 @@ from . import Image, ImageFile, ImagePalette from ._binary import i8, i16le as i16, o8, o16le as o16 +import warnings + __version__ = "0.3" @@ -163,6 +165,10 @@ def _save(im, fp, filename): id_section = im.encoderinfo.get("id_section", im.info.get("id_section", "")) idlen = len(id_section) + if idlen > 255: + idlen = 255 + id_section = id_section[:255] + warnings.warn("id_section has been trimmed to 255 characters") if colormaptype: colormapfirst, colormaplength, colormapentry = 0, 256, 24 From 83216b96de9d262d0fc54818744657ef4ef4fbc3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Sep 2018 18:00:44 +1000 Subject: [PATCH 52/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3d43539de..5f81c8618 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Convert int values of RATIONAL TIFF tags to floats #3338 + [radarhere, wiredfool] + - Fix code for PYTHONOPTIMIZE #3233 [hugovk] From a9d504e91d82a211a5ce968282e2cb8bdac65480 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Sep 2018 19:02:03 +1000 Subject: [PATCH 53/96] Renamed idlen variable to id_len --- src/PIL/TgaImagePlugin.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index c622f1f5e..02893e837 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -55,7 +55,7 @@ class TgaImageFile(ImageFile.ImageFile): # process header s = self.fp.read(18) - idlen = i8(s[0]) + id_len = i8(s[0]) colormaptype = i8(s[1]) imagetype = i8(s[2]) @@ -102,8 +102,8 @@ class TgaImageFile(ImageFile.ImageFile): if imagetype & 8: self.info["compression"] = "tga_rle" - if idlen: - self.info["id_section"] = self.fp.read(idlen) + if id_len: + self.info["id_section"] = self.fp.read(id_len) if colormaptype: # read palette @@ -164,9 +164,9 @@ def _save(im, fp, filename): id_section = im.encoderinfo.get("id_section", im.info.get("id_section", "")) - idlen = len(id_section) - if idlen > 255: - idlen = 255 + id_len = len(id_section) + if id_len > 255: + id_len = 255 id_section = id_section[:255] warnings.warn("id_section has been trimmed to 255 characters") @@ -185,7 +185,7 @@ def _save(im, fp, filename): if orientation > 0: flags = flags | 0x20 - fp.write(o8(idlen) + + fp.write(o8(id_len) + o8(colormaptype) + o8(imagetype) + o16(colormapfirst) + From 2780f8f4a406051f1b2ed1423ed08eaeb26a2771 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 8 Sep 2018 13:13:53 +0300 Subject: [PATCH 54/96] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 5f81c8618..ae45960b6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Add orientation, compression and id_section as TGA save keyword arguments #3327 + [radarhere] + - Convert int values of RATIONAL TIFF tags to floats #3338 [radarhere, wiredfool] From 9d9da79caa713cc8b6fe5a80144f963073bc8aa8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Sep 2018 23:08:17 +1000 Subject: [PATCH 55/96] Close existing fp before setting new fp --- Tests/test_file_webp.py | 7 +++++++ src/PIL/WebPImagePlugin.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 06e274d0a..202836f4c 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -135,6 +135,13 @@ class TestFileWebp(PillowTestCase): self.assertRaises(TypeError, _webp.WebPAnimDecoder) self.assertRaises(TypeError, _webp.WebPDecode) + def test_no_resource_warning(self): + file_path = "Tests/images/hopper.webp" + image = Image.open(file_path) + + temp_file = self.tempfile("temp.webp") + self.assert_warning(None, image.save, temp_file) + if __name__ == '__main__': unittest.main() diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 39a8f2e35..8c26da180 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -153,6 +153,8 @@ class WebPImageFile(ImageFile.ImageFile): self.__loaded = self.__logical_frame # Set tile + if self.fp: + self.fp.close() self.fp = BytesIO(data) self.tile = [("raw", (0, 0) + self.size, 0, self.mode)] From d71eacd7694508b980b41360923ddd40177d87c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Sep 2018 23:39:57 +1000 Subject: [PATCH 56/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ae45960b6..6462d495d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Close existing WebP fp before setting new fp #3341 + [radarhere] + - Add orientation, compression and id_section as TGA save keyword arguments #3327 [radarhere] From a95e57af4632811ec8506fa88f178c332a366449 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 8 Sep 2018 18:21:02 +0300 Subject: [PATCH 57/96] Wrong raw mode for YCbCr with two extra channels --- src/PIL/TiffImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 349f7acbf..683482020 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -261,8 +261,8 @@ OPEN_INFO = { (MM, 6, (1,), 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"), (II, 6, (1,), 1, (8, 8, 8, 8), (0,)): ("YCbCr", "YCbCrX"), (MM, 6, (1,), 1, (8, 8, 8, 8), (0,)): ("YCbCr", "YCbCrX"), - (II, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXXX"), - (MM, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXXX"), + (II, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXX"), + (MM, 6, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("YCbCr", "YCbCrXX"), (II, 6, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("YCbCr", "YCbCrXXX"), (MM, 6, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("YCbCr", "YCbCrXXX"), From cb8613ad2abcb5ef2b5794fc927c7dc420449dcd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Sep 2018 19:43:40 +1000 Subject: [PATCH 58/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 6462d495d..f9251185e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Add more raw Tiff modes (RGBaX, RGBaXX, RGBAX, RGBAXX) #3335 + [homm] + - Close existing WebP fp before setting new fp #3341 [radarhere] From 37f0e9595c19ce5f8b4dd5b957b6efca71f2f037 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Sep 2018 21:10:11 +1000 Subject: [PATCH 59/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f9251185e..d844e98d8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Fix builds with --parallel #3272 + [hsoft] + - Add more raw Tiff modes (RGBaX, RGBaXX, RGBAX, RGBAXX) #3335 [homm] From e5160bd3732b881437bb7f6b5d5fbfaffc6fe69c Mon Sep 17 00:00:00 2001 From: Giovanni Cavallin <37183651+mawanda-jun@users.noreply.github.com> Date: Tue, 11 Sep 2018 10:30:42 +0200 Subject: [PATCH 60/96] From KeyError to ValueError when saving image When saving an image, if the extension is not determined it raises a ValueError (while internally it manages a KeyError) so I propose this change --- 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 f13a98276..1ceabab45 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1894,7 +1894,7 @@ class Image(object): parameter should always be used. :param params: Extra parameters to the image writer. :returns: None - :exception KeyError: If the output format could not be determined + :exception ValueError: If the output format could not be determined from the file name. Use the format option to solve this. :exception IOError: If the file could not be written. The file may have been created, and may contain partial data. From e94878c8a8067861ebf8b667a2415a23e2a4355e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Sep 2018 06:26:40 +1000 Subject: [PATCH 61/96] Simplified duplicate code --- setup.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 935f3d1cc..ba1fd2219 100755 --- a/setup.py +++ b/setup.py @@ -524,10 +524,7 @@ class pil_build_ext(build_ext): if _find_include_file(self, 'tiff.h'): if _find_library_file(self, "tiff"): feature.tiff = "tiff" - if (sys.platform == "win32" and - _find_library_file(self, "libtiff")): - feature.tiff = "libtiff" - if (sys.platform == "darwin" and + if (sys.platform in ["win32", "darwin"] and _find_library_file(self, "libtiff")): feature.tiff = "libtiff" From d8dfc6fc1c2e9a486ccfc0bd5b90da01431bdd29 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Sep 2018 07:29:21 +1000 Subject: [PATCH 62/96] Removed unnecessary line --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 935f3d1cc..753028d0d 100755 --- a/setup.py +++ b/setup.py @@ -553,7 +553,6 @@ class pil_build_ext(build_ext): break if freetype_version: feature.freetype = "freetype" - feature.freetype_version = freetype_version if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) From 4503735df7872a4adf1f269b007738da4944a138 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 15 Sep 2018 12:28:35 -0700 Subject: [PATCH 63/96] Remove additional references to nose tests --- .gitignore | 1 - Tests/check_jpeg_leaks.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 57494ded8..87ae69d97 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,6 @@ htmlcov/ .coverage .cache .pytest_cache -nosetests.xml coverage.xml # Test files diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index 065e9d817..c85f6f030 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -9,8 +9,7 @@ iterations = 5000 When run on a system without the jpeg leak fixes, the valgrind runs look like this. -NOSE_PROCESSES=0 NOSE_TIMEOUT=600 valgrind --tool=massif \ - python test-installed.py -s -v Tests/check_jpeg_leaks.py +valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py """ From b8b51f8ddf75cbc6a6032e5fd40552ef300360c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 16 Sep 2018 12:50:33 +1000 Subject: [PATCH 64/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index d844e98d8..5c4195325 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Improved performance of ImageDraw floodfill method #3294 + [yo1995] + - Fix builds with --parallel #3272 [hsoft] From f3842460ba7694e9c1c9190bd6b08357254c2808 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 16 Sep 2018 21:29:09 +1000 Subject: [PATCH 65/96] Added line joints --- Tests/images/imagedraw_line_joint_curve.png | Bin 0 -> 3535 bytes Tests/test_imagedraw.py | 14 +++++ docs/reference/ImageDraw.rst | 7 ++- src/PIL/ImageDraw.py | 58 +++++++++++++++++++- 4 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 Tests/images/imagedraw_line_joint_curve.png diff --git a/Tests/images/imagedraw_line_joint_curve.png b/Tests/images/imagedraw_line_joint_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..ad729f52858daee4d20e1d3e24d4a1cdf9a2aeb4 GIT binary patch literal 3535 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 4: + for i in range(1, len(xy)-1): + point = xy[i] + angles = [ + math.degrees(math.atan2( + end[0] - start[0], start[1] - end[1] + )) % 360 + for start, end in ((xy[i-1], point), (point, xy[i+1])) + ] + if angles[0] == angles[1]: + # This is a straight line, so no joint is required + continue + + def coord_at_angle(coord, angle): + x, y = coord + angle -= 90 + distance = width/2 - 1 + return tuple([ + p + + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) + for p, p_d in + ((x, distance * math.cos(math.radians(angle))), + (y, distance * math.sin(math.radians(angle)))) + ]) + flipped = ((angles[1] > angles[0] and + angles[1] - 180 > angles[0]) or + (angles[1] < angles[0] and + angles[1] + 180 > angles[0])) + coords = [ + (point[0] - width/2 + 1, point[1] - width/2 + 1), + (point[0] + width/2 - 1, point[1] + width/2 - 1) + ] + if flipped: + start, end = (angles[1] + 90, angles[0] + 90) + else: + start, end = (angles[0] - 90, angles[1] - 90) + self.pieslice(coords, start - 90, end - 90, fill) + + if width > 8: + # Cover potential gaps between the line and the joint + if flipped: + gapCoords = [ + coord_at_angle(point, angles[0]+90), + point, + coord_at_angle(point, angles[1]+90) + ] + else: + gapCoords = [ + coord_at_angle(point, angles[0]-90), + point, + coord_at_angle(point, angles[1]-90) + ] + self.line(gapCoords, fill, width=3) def shape(self, shape, fill=None, outline=None): """(Experimental) Draw a shape.""" From 53acbfc4d512aab4fce6bc7cb83896f745ddec0c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 16 Sep 2018 22:30:11 +1000 Subject: [PATCH 66/96] Added versionadded [ci skip] --- docs/reference/ImageDraw.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 89d0bfa28..c66fb59dd 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -186,6 +186,8 @@ Methods :param joint: Joint type between a sequence of lines. It can be "curve", for rounded edges, or None. + .. versionadded:: 5.3.0 + .. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None) Same as arc, but also draws straight lines between the end points and the From 68717f4a28b5e6ba7627ad88998b8073fa85a531 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 16 Sep 2018 16:11:46 +0300 Subject: [PATCH 67/96] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 5c4195325..13a2364ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- ImageDraw: Add line joints #3250 + [radarhere] + - Improved performance of ImageDraw floodfill method #3294 [yo1995] From c570ee3ba1c1b659e8de208c0b89605f7acef12f Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 17 Sep 2018 12:08:08 +0300 Subject: [PATCH 68/96] Update CHANGES.rst --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 13a2364ef..923e515c3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Read/Save RGB webp as RGB (instead of RGBX) #3298 + [kkopachev] + - ImageDraw: Add line joints #3250 [radarhere] From dd5f370a39c67e0b6cc17691e913f0c3d6dd6c9d Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 18 Sep 2018 09:40:16 +0300 Subject: [PATCH 69/96] Issue template: add prompts for versions [CI skip] --- .github/ISSUE_TEMPLATE.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6c91b6427..01ae7a43c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,7 +4,11 @@ ### What actually happened? -### What versions of Pillow and Python are you using? +### What versions of OS, Python and Pillow are you using? + +* OS: +* Python: +* Pillow: Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive. From 7da1cc23382961cc22bdf82078c8d5fa3a8a8d16 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 18 Sep 2018 13:10:34 +0300 Subject: [PATCH 70/96] Reword --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 01ae7a43c..6cea87df2 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -4,7 +4,7 @@ ### What actually happened? -### What versions of OS, Python and Pillow are you using? +### What are your OS, Python and Pillow versions? * OS: * Python: From 292fa6120e1a404330d0afa98072ccf856704836 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 18 Sep 2018 13:41:55 +0300 Subject: [PATCH 71/96] 'btt' (bottom to top) is not supported by libraqm --- docs/reference/ImageDraw.rst | 20 ++++++++------------ docs/reference/ImageFont.rst | 5 ++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index c66fb59dd..7e5192499 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -253,9 +253,8 @@ Methods :param align: If the text is passed on to multiline_text(), "left", "center" or "right". :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right), 'ttb' (top to - bottom) or 'btt' (bottom to top). Requires - libraqm. + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. .. versionadded:: 4.2.0 @@ -283,9 +282,8 @@ Methods :param spacing: The number of pixels between lines. :param align: "left", "center" or "right". :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right), 'ttb' (top to - bottom) or 'btt' (bottom to top). Requires - libraqm. + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. .. versionadded:: 4.2.0 @@ -312,9 +310,8 @@ Methods :param spacing: If the text is passed on to multiline_textsize(), the number of pixels between lines. :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right), 'ttb' (top to - bottom) or 'btt' (bottom to top). Requires - libraqm. + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. .. versionadded:: 4.2.0 @@ -339,9 +336,8 @@ Methods :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. :param spacing: The number of pixels between lines. :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right), 'ttb' (top to - bottom) or 'btt' (bottom to top). Requires - libraqm. + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. .. versionadded:: 4.2.0 diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 080e52137..55ce3d382 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -67,9 +67,8 @@ Methods .. versionadded:: 1.1.5 :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right), 'ttb' (top to - bottom) or 'btt' (bottom to top). Requires - libraqm. + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. .. versionadded:: 4.2.0 From fe236fe73289b912395fbe2e75323567e7840ce6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 18 Sep 2018 21:47:10 +1000 Subject: [PATCH 72/96] Create pacman package cache directory --- winbuild/appveyor_install_msys2_deps.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/winbuild/appveyor_install_msys2_deps.sh b/winbuild/appveyor_install_msys2_deps.sh index fbab280b3..f8cb8c641 100644 --- a/winbuild/appveyor_install_msys2_deps.sh +++ b/winbuild/appveyor_install_msys2_deps.sh @@ -1,5 +1,6 @@ #!/bin/sh +mkdir /var/cache/pacman/pkg pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \ mingw32/mingw-w64-i686-python3-setuptools \ mingw32/mingw-w64-i686-python2-pip \ From 0b3036454c9b73d650fd187de457d1377bbc1b55 Mon Sep 17 00:00:00 2001 From: Martin Packman Date: Mon, 17 Sep 2018 16:30:42 +0100 Subject: [PATCH 73/96] Give correct extrema for I;16 format images Currently gives None for a 16 bit greyscale image rather than the true min and max values in the 0-65536 range. The internal ImagingGetProjection function already supports I;16 but the _getextrema needs to know to unpack the result. --- Tests/images/16_bit_noise.tif | Bin 0 -> 2446 bytes Tests/test_image_getextrema.py | 8 ++++++++ src/_imaging.c | 5 +++++ 3 files changed, 13 insertions(+) create mode 100644 Tests/images/16_bit_noise.tif diff --git a/Tests/images/16_bit_noise.tif b/Tests/images/16_bit_noise.tif new file mode 100644 index 0000000000000000000000000000000000000000..19180638efa88ebf827c78a7ed5e4a112815e10b GIT binary patch literal 2446 zcmbuB+f$oW6vbBx#q`D7c&WD;A07V>ANrs#>ObM5R_%-(iy)8?E+K^w8VDrNgmMe_ zON0Okgm5cRZYDsTLTP0Lt+%nxIIX|)nTNc~H|L!FeK~vYwbx$f43{pwE4y9#NxM4M zymlD7npRB1n$(QO6xB`afO-^GttQl=TMDWL+bfT5s9cr0q7!9xo99GnZD~X6ct^FW zaXhxwrCyCN2Z-Tku9u4uAVM8asjBp|lKMl1wl)2TZvqqtnj%gN1TDOGYn04AYSVRm zTU3MPQZ?CY_9_9dV5k?c0SHWLBqe5l8IaNaKTlf#*dqm}1 zR6eIQsyG3CFHe(J@D8yf2m;f^Qqb4JLYcm0zN?2kXLSD%&mAJf!8=KuSv&?o+eZf? z)>|!{do1=H9HQcVFLeEsy?NH+xU+1K4fVqK@}mm9H*DU9B_rTB9PLrQl{fZh^c8{`+9wWAR@F{!{SB@Y@8- z67vMvmw3jB(+lzlR*e3)(#w9L7}dJrun`rxIR88Jt;n{mbG8N4lxFX=brQm`)XTTPU5{X^An*^;O?iw0A$l?3?$nZ`IfD_}LZNQm!VdNBlsQn0t- z`xVxg;Qbx;vSePM6Ek!>3r}(Oc4D`(GlPmE#Oq>DtHnYMb2aQ&aQ_t9Hqq*!)%`7W zlA-bxJG+SY-?@qN>oA=+XWqYIBvYldB1@M-0 z+?+nE>jTu#0V{6&Yj`{`aG8qk;&(v(DQ0tXOw#*V@=mdPoa}={>BJ7=?*zS3juRXX zeF}ohoc#Of^o%+-snh5-P0o4Pj}vPi6k#iB5BZFK2heRJ8f*eXCH3!Ag2BgHL$rE2S<21>6thf*IXH;ny@o7S?#&VE*t^1;zNG)(srq8L~ z&H2j`VVAK@gfz82XG@TGmiptw>fq@levs9NYR&!QN4X``Qo>!b#|^fJ_c3hmf;_|C zd3qQJOA_=$RMbs1t@t^J;RHnq=cdt`UF>;;?tTEfxxeS|y2F#=G1+U;VHD4PtNUT< z@xntr_?ua;=?lH2Cn}Upxj$QhWxvgimage->mode, "I;16") == 0) { + return Py_BuildValue("HH", extrema.s[0], extrema.s[1]); + } } Py_INCREF(Py_None); From 2666fa00984fbcf83e98a80d62fe7d149dfd8daf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 20 Sep 2018 19:52:56 +1000 Subject: [PATCH 74/96] Retry svn checkout on failure up to 3 times --- depends/install_extra_test_images.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/depends/install_extra_test_images.sh b/depends/install_extra_test_images.sh index 089a5e4d7..0a98fc9d9 100755 --- a/depends/install_extra_test_images.sh +++ b/depends/install_extra_test_images.sh @@ -3,7 +3,17 @@ rm -rf test_images -# Use SVN to just fetch a single git subdirectory -svn checkout https://github.com/python-pillow/pillow-depends/trunk/test_images +# Use SVN to just fetch a single Git subdirectory +svn_checkout() +{ + if [ ! -z $1 ]; then + echo "" + echo "Retrying svn checkout..." + echo "" + fi + + svn checkout https://github.com/python-pillow/pillow-depends/trunk/test_images +} +svn_checkout || svn_checkout retry || svn_checkout retry || svn_checkout retry cp -r test_images/* ../Tests/images From ed4de6cb62f90c2a7ea3f3ac40f4d3f7f5abdfa3 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 20 Sep 2018 15:27:30 +0300 Subject: [PATCH 75/96] Fix docstring typo If we `import numpy as np`, use `np` not `numpy` --- 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 64bb39b34..9f2379337 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2446,7 +2446,7 @@ def fromarray(obj, mode=None): from PIL import Image import numpy as np im = Image.open('hopper.jpg') - a = numpy.asarray(im) + a = np.asarray(im) Then this can be used to convert it to a Pillow image:: From b5ca36902f7563c6561c737a17bef1babcdaf66c Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 21 Sep 2018 12:43:49 +0300 Subject: [PATCH 76/96] Install CFFI without any PYTHONOPTIMIZE --- .travis/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/install.sh b/.travis/install.sh index f18aff079..b123fd302 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -7,7 +7,7 @@ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk\ python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick\ libharfbuzz-dev libfribidi-dev -pip install cffi +PYTHONOPTIMIZE=0 pip install cffi pip install check-manifest pip install coverage pip install olefile From 3775aad9f1030af254e3d96a64a3229695c92ca1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 21 Sep 2018 13:31:16 +0300 Subject: [PATCH 77/96] Move PYTHONOPTIMIZE to Python versions which have two jobs --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5adc2da7..36a956e95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,13 +27,13 @@ matrix: - python: '3.6' - python: '3.6' dist: trusty - - python: '3.5' - - python: '3.5' - dist: trusty env: PYTHONOPTIMIZE=1 - - python: '3.4' + - python: '3.5' + - python: '3.5' dist: trusty env: PYTHONOPTIMIZE=2 + - python: '3.4' + dist: trusty - env: DOCKER="alpine" DOCKER_TAG="pytest" - env: DOCKER="arch" DOCKER_TAG="pytest" # contains PyQt5 - env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="pytest" From 0c374912012c691fe2d77b64f774d21c7a0dd60e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 20 Sep 2018 20:15:49 +1000 Subject: [PATCH 78/96] Corrected tags --- src/PIL/TiffTags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index ef19ee66e..bb15a64ee 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -122,7 +122,7 @@ TAGS_V2 = { 316: ("HostComputer", ASCII, 1), 317: ("Predictor", SHORT, 1, {"none": 1, "Horizontal Differencing": 2}), 318: ("WhitePoint", RATIONAL, 2), - 319: ("PrimaryChromaticities", SHORT, 6), + 319: ("PrimaryChromaticities", RATIONAL, 6), 320: ("ColorMap", SHORT, 0), 321: ("HalftoneHints", SHORT, 2), @@ -159,7 +159,7 @@ TAGS_V2 = { 529: ("YCbCrCoefficients", RATIONAL, 3), 530: ("YCbCrSubSampling", SHORT, 2), 531: ("YCbCrPositioning", SHORT, 1), - 532: ("ReferenceBlackWhite", LONG, 0), + 532: ("ReferenceBlackWhite", RATIONAL, 6), 700: ('XMP', BYTE, 1), From b535d78fd8ec03e4ac02ed741732019f4de08f0f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 23 Sep 2018 17:25:21 +1000 Subject: [PATCH 79/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 923e515c3..26339670b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- CI: Install CFFI and pycparser without any PYTHONOPTIMIZE #3374 + [hugovk] + - Read/Save RGB webp as RGB (instead of RGBX) #3298 [kkopachev] From 6d52cd2db8ced7c2ff415e69568f73cd524d4b08 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 25 Sep 2018 06:16:52 +1000 Subject: [PATCH 80/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 26339670b..f87005d03 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Corrected TIFF tags #3369 + [radarhere] + - CI: Install CFFI and pycparser without any PYTHONOPTIMIZE #3374 [hugovk] From b09b43d8b2a9a3b78ff831968df5358174015256 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Sep 2018 20:07:46 +1000 Subject: [PATCH 81/96] Added ImageOps pad method --- Tests/images/imageops_pad_h_0.jpg | Bin 0 -> 12784 bytes Tests/images/imageops_pad_h_1.jpg | Bin 0 -> 12785 bytes Tests/images/imageops_pad_h_2.jpg | Bin 0 -> 12783 bytes Tests/images/imageops_pad_v_0.jpg | Bin 0 -> 12766 bytes Tests/images/imageops_pad_v_1.jpg | Bin 0 -> 12771 bytes Tests/images/imageops_pad_v_2.jpg | Bin 0 -> 12768 bytes Tests/test_imageops.py | 24 ++++++++++++++++ src/PIL/ImageOps.py | 44 ++++++++++++++++++++++++++++++ 8 files changed, 68 insertions(+) create mode 100644 Tests/images/imageops_pad_h_0.jpg create mode 100644 Tests/images/imageops_pad_h_1.jpg create mode 100644 Tests/images/imageops_pad_h_2.jpg create mode 100644 Tests/images/imageops_pad_v_0.jpg create mode 100644 Tests/images/imageops_pad_v_1.jpg create mode 100644 Tests/images/imageops_pad_v_2.jpg diff --git a/Tests/images/imageops_pad_h_0.jpg b/Tests/images/imageops_pad_h_0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f9fcb1cdb40eda48500dc9a804c631d69cc36735 GIT binary patch literal 12784 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�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 Pxybh8|8j;CTu=Qs`IF3W literal 0 HcmV?d00001 diff --git a/Tests/images/imageops_pad_h_2.jpg b/Tests/images/imageops_pad_h_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2c822489253bce80100f07d2789a5a138411992f GIT binary patch literal 12783 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 Co6vv& literal 0 HcmV?d00001 diff --git a/Tests/images/imageops_pad_v_0.jpg b/Tests/images/imageops_pad_v_0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..caf435796cfb5c1c9fae8f3cc3afedf15f314a8c GIT binary patch literal 12766 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 XX}!MxU+ur<;JAP4u($X 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 n+N;xAP4u($X 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 bjHZ765%aZIr!$u%Vl7Oo!exiRo0 dest_ratio: + new_height = int(image.height / image.width * size[0]) + if new_height != size[1]: + image = image.resize((size[0], new_height), resample=method) + + y = int((size[1] - new_height) * max(0, min(centering[1], 1))) + out.paste(image, (0, y)) + else: + new_width = int(image.width / image.height * size[1]) + if new_width != size[0]: + image = image.resize((new_width, size[1]), resample=method) + + x = int((size[0] - new_width) * max(0, min(centering[0], 1))) + out.paste(image, (x, 0)) + return out + + def crop(image, border=0): """ Remove border from image. The same amount of pixels are removed From 1c47313658a0c6c833c27e1f7c695b8949ff13ad Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Sep 2018 20:26:00 +1000 Subject: [PATCH 82/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f87005d03..dcd76bb82 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Give correct extrema for I;16 format images #3359 + [bz2] + +- Added PySide2 #3279 + [radarhere] + - Corrected TIFF tags #3369 [radarhere] From 7b425a96c118916ca2df093c28752235c831ffb9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Sep 2018 20:57:01 +1000 Subject: [PATCH 83/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index dcd76bb82..453c0e27b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Added ImageOps pad method #3364 + [radarhere] + - Give correct extrema for I;16 format images #3359 [bz2] From 36baea18eee2f157cee5c9c6235c3797a04233a6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 26 Sep 2018 13:58:15 +0300 Subject: [PATCH 84/96] flake8 --- Tests/test_image_crop.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index 27a7f07d1..fe92dd865 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -2,6 +2,7 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image + class TestImageCrop(PillowTestCase): def test_crop(self): From 2389492f96f8fc82e702a9311b3389f2fbe3a494 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 26 Sep 2018 14:09:31 +0300 Subject: [PATCH 85/96] flake8 --- Tests/test_decompression_bomb.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index b0b9430d4..0a30e25f1 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -6,7 +6,6 @@ TEST_FILE = "Tests/images/hopper.ppm" ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS -from PIL.Image import DecompressionBombWarning, DecompressionBombError class TestDecompressionBomb(PillowTestCase): @@ -67,7 +66,7 @@ class TestDecompressionCrop(PillowTestCase): im = Image.new("RGB", (100, 100)) good_values = ((-9999, -9999, -9990, -9990), - (-999, -999, -990, -990)) + (-999, -999, -990, -990)) warning_values = ((-160, -160, 99, 99), (160, 160, -99, -99)) @@ -76,14 +75,15 @@ class TestDecompressionCrop(PillowTestCase): (99909, 99990, -99999, -99999)) for value in good_values: - self.assertEqual(im.crop(value).size, (9,9)) + self.assertEqual(im.crop(value).size, (9, 9)) for value in warning_values: - self.assert_warning(DecompressionBombWarning, im.crop, value) + self.assert_warning(Image.DecompressionBombWarning, im.crop, value) for value in error_values: - with self.assertRaises(DecompressionBombError): + with self.assertRaises(Image.DecompressionBombError): im.crop(value) + if __name__ == '__main__': unittest.main() From b5af2837328a9f5a7ba1c4732058214814a772cd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Sep 2018 22:22:17 +1000 Subject: [PATCH 86/96] Added support to floodfill for non-RGB colors --- Tests/images/imagedraw_floodfill_L.png | Bin 0 -> 144 bytes ...odfill.png => imagedraw_floodfill_RGB.png} | Bin Tests/images/imagedraw_floodfill_RGBA.png | Bin 0 -> 253 bytes Tests/test_imagechops.py | 14 ++++----- Tests/test_imagedraw.py | 27 +++++++++++------- src/PIL/ImageDraw.py | 9 ++++-- 6 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 Tests/images/imagedraw_floodfill_L.png rename Tests/images/{imagedraw_floodfill.png => imagedraw_floodfill_RGB.png} (100%) create mode 100644 Tests/images/imagedraw_floodfill_RGBA.png diff --git a/Tests/images/imagedraw_floodfill_L.png b/Tests/images/imagedraw_floodfill_L.png new file mode 100644 index 0000000000000000000000000000000000000000..4139e66d84e5159d161ed71025a97e987c6b32c1 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-NBp52o_Rg}?3lo%Viqd;QLuiGs}XuS=^L_6w@G tO^|R*V(Fwgn5V0s%Q~loCIFcRNE`qF literal 0 HcmV?d00001 diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 3714675cf..06febc6d2 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -48,7 +48,7 @@ class TestImageChops(PillowTestCase): def test_add(self): # Arrange im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill.png") + im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") # Act new = ImageChops.add(im1, im2) @@ -60,7 +60,7 @@ class TestImageChops(PillowTestCase): def test_add_scale_offset(self): # Arrange im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill.png") + im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") # Act new = ImageChops.add(im1, im2, scale=2.5, offset=100) @@ -82,7 +82,7 @@ class TestImageChops(PillowTestCase): def test_add_modulo(self): # Arrange im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill.png") + im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") # Act new = ImageChops.add_modulo(im1, im2) @@ -104,7 +104,7 @@ class TestImageChops(PillowTestCase): def test_blend(self): # Arrange im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill.png") + im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") # Act new = ImageChops.blend(im1, im2, 0.5) @@ -181,7 +181,7 @@ class TestImageChops(PillowTestCase): def test_invert(self): # Arrange - im = Image.open("Tests/images/imagedraw_floodfill.png") + im = Image.open("Tests/images/imagedraw_floodfill_RGB.png") # Act new = ImageChops.invert(im) @@ -228,7 +228,7 @@ class TestImageChops(PillowTestCase): def test_multiply_green(self): # Arrange - im = Image.open("Tests/images/imagedraw_floodfill.png") + im = Image.open("Tests/images/imagedraw_floodfill_RGB.png") green = Image.new("RGB", im.size, "green") # Act @@ -273,7 +273,7 @@ class TestImageChops(PillowTestCase): def test_screen(self): # Arrange im1 = Image.open("Tests/images/imagedraw_ellipse_RGB.png") - im2 = Image.open("Tests/images/imagedraw_floodfill.png") + im2 = Image.open("Tests/images/imagedraw_floodfill_RGB.png") # Act new = ImageChops.screen(im1, im2) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 76868cbf8..ad3e8b2cb 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -344,19 +344,26 @@ class TestImageDraw(PillowTestCase): self.assert_image_similar(im, Image.open(expected), 1) def test_floodfill(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - draw.rectangle(BBOX2, outline="yellow", fill="green") - centre_point = (int(W/2), int(H/2)) red = ImageColor.getrgb("red") - im_floodfill = Image.open("Tests/images/imagedraw_floodfill.png") - # Act - ImageDraw.floodfill(im, centre_point, red) + for mode, value in [ + ("L", 1), + ("RGBA", (255, 0, 0, 0)), + ("RGB", red) + ]: + # Arrange + im = Image.new(mode, (W, H)) + draw = ImageDraw.Draw(im) + draw.rectangle(BBOX2, outline="yellow", fill="green") + centre_point = (int(W/2), int(H/2)) - # Assert - self.assert_image_equal(im, im_floodfill) + # Act + ImageDraw.floodfill(im, centre_point, value) + + # Assert + expected = "Tests/images/imagedraw_floodfill_"+mode+".png" + im_floodfill = Image.open(expected) + self.assert_image_equal(im, im_floodfill) # Test that using the same colour does not change the image ImageDraw.floodfill(im, centre_point, red) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 3c4cd3d23..511bae537 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -430,8 +430,11 @@ def floodfill(image, xy, value, border=None, thresh=0): edge = new_edge -def _color_diff(rgb1, rgb2): +def _color_diff(color1, color2): """ - Uses 1-norm distance to calculate difference between two rgb values. + Uses 1-norm distance to calculate difference between two values. """ - return abs(rgb1[0]-rgb2[0]) + abs(rgb1[1]-rgb2[1]) + abs(rgb1[2]-rgb2[2]) + if isinstance(color2, tuple): + return sum([abs(color1[i]-color2[i]) for i in range(0, len(color2))]) + else: + return abs(color1-color2) From e2c17ab59b1d244b2ff21855c7b7e42b94bc3e58 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 26 Sep 2018 23:29:08 +1000 Subject: [PATCH 87/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 453c0e27b..fce3adca5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,18 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Added support to ImageDraw.floodfill for non-RGB colors #3377 + [radarhere] + +- Tests: Avoid catching unexpected exceptions in tests #2203 + [jdufresne] + +- Use TextIOWrapper.detach() instead of NoCloseStream #2214 + [jdufresne] + +- Added transparency to matrix conversion #3205 + [radarhere] + - Added ImageOps pad method #3364 [radarhere] From ddf85de4812932333c258a78e8fb75075a1e2cf1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Sep 2018 06:22:54 +1000 Subject: [PATCH 88/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fce3adca5..7d7856924 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 5.3.0 (unreleased) ------------------ +- Fixed decompression bomb check in _crop #3313 + [dinkolubina, hugovk] + - Added support to ImageDraw.floodfill for non-RGB colors #3377 [radarhere] From e5491f8162ae4295417f3d757040b6914bb3b86e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Sep 2018 19:40:12 +1000 Subject: [PATCH 89/96] Expected 2 blank lines --- Tests/test_image_rotate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 17d394a02..77c934e5d 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -124,5 +124,6 @@ class TestImageRotate(PillowTestCase): corner = im.getpixel((0,0)) self.assertEqual(corner, (255, 0, 0, 255)) + if __name__ == '__main__': unittest.main() From 9ee467d84345acaadf7f274de74175c1265ea4ef Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Sep 2018 19:41:27 +1000 Subject: [PATCH 90/96] Too many blank lines --- Tests/test_imageops.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index ce8a6629d..cf6a7b0d9 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -93,7 +93,6 @@ class TestImageOps(PillowTestCase): "Tests/images/imageops_pad_"+label+"_"+str(i)+".jpg") self.assert_image_similar(new_im, target, 6) - def test_pil163(self): # Division by zero in equalize if < 255 pixels in image (@PIL163) From 6dd0e48d9a687e6aa244fe70b809558d7bdb909f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Sep 2018 19:43:39 +1000 Subject: [PATCH 91/96] Missing whitespace --- Tests/test_image_rotate.py | 4 ++-- Tests/test_imagedraw.py | 12 ++++++------ Tests/test_imageops.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 77c934e5d..e788e722f 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -114,14 +114,14 @@ class TestImageRotate(PillowTestCase): # Alpha images are handled differently internally im = Image.new('RGBA', (10, 10), 'green') im = im.rotate(45, expand=1) - corner = im.getpixel((0,0)) + corner = im.getpixel((0, 0)) self.assertEqual(corner, (0, 0, 0, 0)) def test_alpha_rotate_with_fill(self): # Alpha images are handled differently internally im = Image.new('RGBA', (10, 10), 'green') im = im.rotate(45, expand=1, fillcolor=(255, 0, 0, 255)) - corner = im.getpixel((0,0)) + corner = im.getpixel((0, 0)) self.assertEqual(corner, (255, 0, 0, 255)) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index ad3e8b2cb..5a945e8b2 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -622,12 +622,12 @@ class TestImageDraw(PillowTestCase): ["red", "#f00"] ]: for operation, args in { - 'chord':[BBOX1, 0, 180], - 'ellipse':[BBOX1], - 'shape':[s], - 'pieslice':[BBOX1, -90, 45], - 'polygon':[[(18, 30), (85, 30), (60, 72)]], - 'rectangle':[BBOX1] + 'chord': [BBOX1, 0, 180], + 'ellipse': [BBOX1], + 'shape': [s], + 'pieslice': [BBOX1, -90, 45], + 'polygon': [[(18, 30), (85, 30), (60, 72)]], + 'rectangle': [BBOX1] }.items(): # Arrange im = Image.new(mode, (W, H)) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index cf6a7b0d9..70b1659d9 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -84,7 +84,7 @@ class TestImageOps(PillowTestCase): ("h", None, (im.width * 4, im.height * 2)), ("v", "#f00", (im.width * 2, im.height * 4)) ]: - for i, centering in enumerate([(0,0), (0.5, 0.5), (1, 1)]): + for i, centering in enumerate([(0, 0), (0.5, 0.5), (1, 1)]): new_im = ImageOps.pad(im, new_size, color=color, centering=centering) self.assertEqual(new_im.size, new_size) From 9b0d4baa8c4e2b0811d87e89c4691bca56072b8d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Sep 2018 20:14:22 +1000 Subject: [PATCH 92/96] Continuation line under-indented for visual indent --- Tests/test_color_lut.py | 12 ++++++------ Tests/test_imagecms.py | 20 ++++++++++---------- src/PIL/PdfParser.py | 10 +++++----- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index 6d0b76fed..ec370f70a 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -335,35 +335,35 @@ class TestColorLut3DFilter(PillowTestCase): g.transpose(Image.ROTATE_180)]) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float32)[:-1] with self.assertRaisesRegex(ValueError, "should have table_channels"): im.filter(lut) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lambda r, g, b: (r, g, b)) lut.table = (numpy.array(lut.table, dtype=numpy.float32) .reshape((7 * 9 * 11), 3)) with self.assertRaisesRegex(ValueError, "should have table_channels"): im.filter(lut) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float16) self.assert_image_equal(im, im.filter(lut)) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float32) self.assert_image_equal(im, im.filter(lut)) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float64) self.assert_image_equal(im, im.filter(lut)) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), - lambda r, g, b: (r, g, b)) + lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.int32) im.filter(lut) lut.table = numpy.array(lut.table, dtype=numpy.int8) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 1fdfe916d..dc6c85f05 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -175,11 +175,11 @@ class TestImageCms(PillowTestCase): def test_unsupported_color_space(self): self.assertRaises(ImageCms.PyCMSError, - ImageCms.createProfile, "unsupported") + ImageCms.createProfile, "unsupported") def test_invalid_color_temperature(self): self.assertRaises(ImageCms.PyCMSError, - ImageCms.createProfile, "LAB", "invalid") + ImageCms.createProfile, "LAB", "invalid") def test_simple_lab(self): i = Image.new('RGB', (10, 10), (128, 128, 128)) @@ -446,20 +446,20 @@ class TestImageCms(PillowTestCase): self.assert_image_equal(source_image_aux, result_image_aux) def test_preserve_auxiliary_channels_rgba(self): - self.assert_aux_channel_preserved(mode='RGBA', - transform_in_place=False, preserved_channel='A') + self.assert_aux_channel_preserved( + mode='RGBA', transform_in_place=False, preserved_channel='A') def test_preserve_auxiliary_channels_rgba_in_place(self): - self.assert_aux_channel_preserved(mode='RGBA', - transform_in_place=True, preserved_channel='A') + self.assert_aux_channel_preserved( + mode='RGBA', transform_in_place=True, preserved_channel='A') def test_preserve_auxiliary_channels_rgbx(self): - self.assert_aux_channel_preserved(mode='RGBX', - transform_in_place=False, preserved_channel='X') + self.assert_aux_channel_preserved( + mode='RGBX', transform_in_place=False, preserved_channel='X') def test_preserve_auxiliary_channels_rgbx_in_place(self): - self.assert_aux_channel_preserved(mode='RGBX', - transform_in_place=True, preserved_channel='X') + self.assert_aux_channel_preserved( + mode='RGBX', transform_in_place=True, preserved_channel='X') def test_auxiliary_channels_isolated(self): # test data in aux channels does not affect non-aux channels diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 971f44514..e99040bce 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -450,12 +450,12 @@ class PdfParser: self.pages_ref = self.next_object_id(0) self.rewrite_pages() self.write_obj(self.root_ref, - Type=PdfName(b"Catalog"), - Pages=self.pages_ref) + Type=PdfName(b"Catalog"), + Pages=self.pages_ref) self.write_obj(self.pages_ref, - Type=PdfName(b"Pages"), - Count=len(self.pages), - Kids=self.pages) + Type=PdfName(b"Pages"), + Count=len(self.pages), + Kids=self.pages) return self.root_ref def rewrite_pages(self): From 90a9e85db39745cb828e4c9ef28d36092c2d293c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Sep 2018 20:00:42 +1000 Subject: [PATCH 93/96] Local variable is assigned to but never used --- Tests/test_imagetk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 2df35c584..14ce74eb1 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -25,7 +25,7 @@ class TestImageTk(PillowTestCase): self.skipTest("Tk not installed") try: # setup tk - app = tk.Frame() + tk.Frame() # root = tk.Tk() except (tk.TclError) as v: self.skipTest("TCL Error: %s" % v) From f8fbac68ded91d8371ad7f04582a0991eb220f3c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Sep 2018 20:15:37 +1000 Subject: [PATCH 94/96] Removed unused imports --- Tests/test_file_gd.py | 2 -- Tests/test_file_tiff.py | 1 - src/PIL/ImageFile.py | 1 - 3 files changed, 4 deletions(-) diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index 326b0c4b2..b303369b4 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -2,8 +2,6 @@ from helper import unittest, PillowTestCase from PIL import GdImageFile -import io - TEST_GD_FILE = "Tests/images/hopper.gd" diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 68429f249..4209e3911 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,6 +1,5 @@ import logging from io import BytesIO -import struct import sys from helper import unittest, PillowTestCase, hopper diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index bdcc43d1f..915557a57 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -30,7 +30,6 @@ from . import Image from ._util import isPath import io -import os import sys import struct From a8261a2e89bdcac02a601a176db856cbc32ea650 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Sep 2018 20:35:00 +1000 Subject: [PATCH 95/96] Line too long --- Tests/check_imaging_leaks.py | 5 ++--- Tests/helper.py | 4 ++-- Tests/test_bmp_reference.py | 3 ++- Tests/test_file_tiff.py | 3 ++- Tests/test_file_tiff_metadata.py | 16 ++++++++++------ Tests/test_file_webp.py | 6 ++++-- Tests/test_image_convert.py | 3 ++- Tests/test_lib_pack.py | 15 ++++++++++----- Tests/test_pdfparser.py | 8 ++++++-- src/PIL/GdImageFile.py | 3 ++- src/PIL/ImageFont.py | 3 ++- src/PIL/ImageQt.py | 3 ++- 12 files changed, 46 insertions(+), 26 deletions(-) diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index eb9426eee..0c41b6da1 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -25,9 +25,8 @@ class TestImagingLeaks(PillowTestCase): if i < min_iterations: mem_limit = mem + 1 continue - self.assertLessEqual(mem, mem_limit, - msg='memory usage limit exceeded after %d iterations' - % (i + 1)) + msg = 'memory usage limit exceeded after %d iterations' % (i + 1) + self.assertLessEqual(mem, mem_limit, msg) def test_leak_putdata(self): im = Image.new('RGB', (25, 25)) diff --git a/Tests/helper.py b/Tests/helper.py index b6ef6dc13..8fb34848b 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -272,8 +272,8 @@ class PillowLeakTestCase(PillowTestCase): for cycle in range(self.iterations): core() mem = (self._get_mem_usage() - start_mem) - self.assertLess(mem, self.mem_limit, - msg='memory usage limit exceeded in iteration %d' % cycle) + msg = 'memory usage limit exceeded in iteration %d' % cycle + self.assertLess(mem, self.mem_limit, msg) # helpers diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 015a9ebdf..af25f7162 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -42,7 +42,8 @@ class TestBmpReference(PillowTestCase): im = Image.open(f) im.load() if os.path.basename(f) not in supported: - print("Please add %s to the partially supported bmp specs." % f) + print("Please add %s to the partially supported" + " bmp specs." % f) except Exception: # as msg: if os.path.basename(f) in supported: raise diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 4209e3911..cbfc63d74 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -58,7 +58,8 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im.mode, "RGBA") self.assertEqual(im.size, (52, 53)) - self.assertEqual(im.tile, [('raw', (0, 0, 52, 53), 160, ('RGBA', 0, 1))]) + self.assertEqual(im.tile, + [('raw', (0, 0, 52, 53), 160, ('RGBA', 0, 1))]) im.load() def test_set_legacy_api(self): diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 0a958454c..d1e2b577e 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -56,7 +56,8 @@ class TestFileTiffMetadata(PillowTestCase): loaded = Image.open(f) self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),)) - self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (len(bindata),)) + self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], + (len(bindata),)) self.assertEqual(loaded.tag[ImageJMetaData], bindata) self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata) @@ -75,8 +76,10 @@ class TestFileTiffMetadata(PillowTestCase): img.save(f, tiffinfo=info) loaded = Image.open(f) - self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8)) - self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8)) + self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], + (8, len(bindata) - 8)) + self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], + (8, len(bindata) - 8)) def test_read_metadata(self): img = Image.open('Tests/images/hopper_g4.tif') @@ -133,8 +136,8 @@ class TestFileTiffMetadata(PillowTestCase): if isinstance(v, IFDRational): original[k] = IFDRational(*_limit_rational(v, 2**31)) if isinstance(v, tuple) and isinstance(v[0], IFDRational): - original[k] = tuple([IFDRational( - *_limit_rational(elt, 2**31)) for elt in v]) + original[k] = tuple([IFDRational(*_limit_rational(elt, 2**31)) + for elt in v]) ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets'] @@ -184,7 +187,8 @@ class TestFileTiffMetadata(PillowTestCase): def test_iccprofile_binary(self): # https://github.com/python-pillow/Pillow/issues/1526 - # We should be able to load this, but probably won't be able to save it. + # We should be able to load this, + # but probably won't be able to save it. im = Image.open('Tests/images/hopper.iccprofile_binary.tif') self.assertEqual(im.tag_v2.tagtype[34675], 1) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 25d3cb462..fa01cf93e 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -38,7 +38,8 @@ class TestFileWebp(PillowTestCase): # generated with: # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm - self.assert_image_similar_tofile(image, 'Tests/images/hopper_webp_bits.ppm', 1.0) + self.assert_image_similar_tofile( + image, 'Tests/images/hopper_webp_bits.ppm', 1.0) def test_write_rgb(self): """ @@ -58,7 +59,8 @@ class TestFileWebp(PillowTestCase): image.getdata() # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm - self.assert_image_similar_tofile(image, 'Tests/images/hopper_webp_write.ppm', 12.0) + self.assert_image_similar_tofile( + image, 'Tests/images/hopper_webp_write.ppm', 12.0) # This test asserts that the images are similar. If the average pixel # difference between the two images is less than the epsilon value, diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 84c1cc82e..1b3815d80 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -204,7 +204,8 @@ class TestImageConvert(PillowTestCase): target = Image.open('Tests/images/hopper-XYZ.png') if converted_im.mode == 'RGB': self.assert_image_similar(converted_im, target, 3) - self.assertEqual(converted_im.info['transparency'], (105, 54, 4)) + self.assertEqual(converted_im.info['transparency'], + (105, 54, 4)) else: self.assert_image_similar(converted_im, target.getchannel(0), 1) diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 635b5679a..2fb7da281 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -221,7 +221,8 @@ class TestLibUnpack(PillowTestCase): data_len = data * len(pixels) data = bytes(bytearray(range(1, data_len + 1))) - im = Image.frombytes(mode, (len(pixels), 1), data, "raw", rawmode, 0, 1) + im = Image.frombytes(mode, (len(pixels), 1), data, + "raw", rawmode, 0, 1) for x, pixel in enumerate(pixels): self.assertEqual(pixel, im.getpixel((x, 0))) @@ -265,9 +266,11 @@ class TestLibUnpack(PillowTestCase): def test_P(self): self.assert_unpack("P", "P;1", b'\xe4', 1, 1, 1, 0, 0, 1, 0, 0) self.assert_unpack("P", "P;2", b'\xe4', 3, 2, 1, 0) - # self.assert_unpack("P", "P;2L", b'\xe4', 1, 1, 1, 0) # erroneous? + # erroneous? + # self.assert_unpack("P", "P;2L", b'\xe4', 1, 1, 1, 0) self.assert_unpack("P", "P;4", b'\x02\xef', 0, 2, 14, 15) - # self.assert_unpack("P", "P;4L", b'\x02\xef', 2, 10, 10, 0) # erroneous? + # erroneous? + # self.assert_unpack("P", "P;4L", b'\x02\xef', 2, 10, 10, 0) self.assert_unpack("P", "P", 1, 1, 2, 3, 4) self.assert_unpack("P", "P;R", 1, 128, 64, 192, 32) @@ -373,7 +376,8 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack( "RGBA", "YCCA;P", b']bE\x04\xdd\xbej\xed57T\xce\xac\xce:\x11', # random data - (0, 161, 0, 4), (255, 255, 255, 237), (27, 158, 0, 206), (0, 118, 0, 17)) + (0, 161, 0, 4), (255, 255, 255, 237), + (27, 158, 0, 206), (0, 118, 0, 17)) self.assert_unpack( "RGBA", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) self.assert_unpack( @@ -425,7 +429,8 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack( "RGBX", "YCC;P", b'D]\x9c\x82\x1a\x91\xfaOC\xe7J\x12', # random data - (127, 102, 0, X), (192, 227, 0, X), (213, 255, 170, X), (98, 255, 133, X)) + (127, 102, 0, X), (192, 227, 0, X), + (213, 255, 170, X), (98, 255, 133, X)) self.assert_unpack("RGBX", "R", 1, (1, 0, 0, 0), (2, 0, 0, 0), (3, 0, 0, 0)) self.assert_unpack("RGBX", "G", 1, diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py index 42c813520..b7373842e 100644 --- a/Tests/test_pdfparser.py +++ b/Tests/test_pdfparser.py @@ -26,7 +26,9 @@ class TestPdfParser(PillowTestCase): def test_parsing(self): self.assertEqual(PdfParser.interpret_name(b"Name#23Hash"), b"Name#Hash") - self.assertEqual(PdfParser.interpret_name(b"Name#23Hash", as_text=True), "Name#Hash") + self.assertEqual(PdfParser.interpret_name( + b"Name#23Hash", as_text=True + ), "Name#Hash") self.assertEqual(PdfParser.get_value(b"1 2 R ", 0), (IndirectReference(1, 2), 5)) self.assertEqual(PdfParser.get_value(b"true[", 0), (True, 4)) @@ -72,7 +74,9 @@ class TestPdfParser(PillowTestCase): self.assertIsInstance(a, list) self.assertEqual(len(a), 4) self.assertEqual(a[0], PdfName("Name")) - s = PdfParser.get_value(b"<>\nstream\nabcde\nendstream<<...", 0)[0] + s = PdfParser.get_value( + b"<>\nstream\nabcde\nendstream<<...", 0 + )[0] self.assertIsInstance(s, PdfStream) self.assertEqual(s.dictionary.Name, "value") self.assertEqual(s.decode(), b"abcde") diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 2ca1e8218..1a0b2c910 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -61,7 +61,8 @@ class GdImageFile(ImageFile.ImageFile): self.palette = ImagePalette.raw("XBGR", s[7+trueColorOffset+4:7+trueColorOffset+4+256*4]) - self.tile = [("raw", (0, 0)+self.size, 7+trueColorOffset+4+256*4, ("L", 0, 1))] + self.tile = [("raw", (0, 0)+self.size, 7+trueColorOffset+4+256*4, + ("L", 0, 1))] def open(fp, mode="r"): diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 099ccc4ff..5384a725b 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -162,7 +162,8 @@ class FreeTypeFont(object): size, offset = self.font.getsize(text, direction, features) return (size[0] + offset[0], size[1] + offset[1]) - def getsize_multiline(self, text, direction=None, spacing=4, features=None): + def getsize_multiline(self, text, direction=None, + spacing=4, features=None): max_width = 0 lines = self._multiline_split(text) line_spacing = self.getsize('A')[1] + spacing diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 5ce685916..e60261360 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -28,7 +28,8 @@ qt_versions = [ ['side', 'PySide'] ] # 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) +qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, + reverse=True) for qt_version, qt_module in qt_versions: try: if qt_module == 'PyQt5': From 2a2603ba90653acc530ee695a093c6773638db20 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 27 Sep 2018 21:01:05 +1000 Subject: [PATCH 96/96] Update CHANGES.rst [ci skip] --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7d7856924..4cb7320ea 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -95,7 +95,7 @@ Changelog (Pillow) - Skip outline if the draw operation fills with the same colour #2922 [radarhere] -- Flake8 fixes #3173 +- Flake8 fixes #3173, #3380 [radarhere] - Avoid deprecated 'U' mode when opening files #2187