From 2d1482b400a14027e54f92b6751a75d8526ae90f Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 24 Nov 2019 04:33:34 +0300 Subject: [PATCH 1/7] minor fixes related to draft --- src/PIL/Image.py | 4 ++-- src/PIL/ImageFile.py | 5 ----- src/PIL/JpegImagePlugin.py | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0cdfcc9a9..f8367a40c 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1186,12 +1186,12 @@ class Image(object): """ Configures the image file loader so it returns a version of the image that as closely as possible matches the given mode and - size. For example, you can use this method to convert a color + size. For example, you can use this method to convert a color JPEG to greyscale while loading it, or to extract a 128x192 version from a PCD file. Note that this method modifies the :py:class:`~PIL.Image.Image` object - in place. If the image has already been loaded, this method has no + in place. If the image has already been loaded, this method has no effect. Note: This method is not implemented for most images. It is diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 836e6318c..93f59d0fb 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -119,11 +119,6 @@ class ImageFile(Image.Image): if not self.mode or self.size[0] <= 0: raise SyntaxError("not identified by this driver") - def draft(self, mode, size): - """Set draft mode""" - - pass - def get_format_mimetype(self): if self.custom_mimetype: return self.custom_mimetype diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 020b95219..2a96b169c 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -415,7 +415,7 @@ class JpegImageFile(ImageFile.ImageFile): return d, e, o, a = self.tile[0] - scale = 0 + scale = 1 if a[0] == "RGB" and mode in ["L", "YCbCr"]: self.mode = mode From 4126f6cdf7881c91e41abf5fd604f70f7d90d2e3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 24 Nov 2019 04:55:49 +0300 Subject: [PATCH 2/7] return chosen image mode and the box of the image --- Tests/test_image_draft.py | 6 +++++- src/PIL/Image.py | 3 +++ src/PIL/JpegImagePlugin.py | 5 ++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 5d92ee797..0f7402698 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -13,7 +13,11 @@ class TestImageDraft(PillowTestCase): im = Image.new(in_mode, in_size) data = tostring(im, "JPEG") im = fromstring(data) - im.draft(req_mode, req_size) + mode, box = im.draft(req_mode, req_size) + scale, _ = im.decoderconfig + self.assertEqual(box[:2], (0, 0)) + self.assertTrue((im.width - scale) < box[2] <= im.width) + self.assertTrue((im.height - scale) < box[3] <= im.height) return im def test_size(self): diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f8367a40c..05ff9239e 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1190,6 +1190,9 @@ class Image(object): JPEG to greyscale while loading it, or to extract a 128x192 version from a PCD file. + If any changes are made, returns a tuple with the chosen `mode` and + `box` with coordinates of the original image within the altered one. + Note that this method modifies the :py:class:`~PIL.Image.Image` object in place. If the image has already been loaded, this method has no effect. diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 2a96b169c..57564a245 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -416,6 +416,7 @@ class JpegImageFile(ImageFile.ImageFile): d, e, o, a = self.tile[0] scale = 1 + original_size = self.size if a[0] == "RGB" and mode in ["L", "YCbCr"]: self.mode = mode @@ -438,7 +439,9 @@ class JpegImageFile(ImageFile.ImageFile): self.tile = [(d, e, o, a)] self.decoderconfig = (scale, 0) - return self + box = (0, 0, original_size[0] / float(scale), + original_size[1] / float(scale)) + return (self.mode, box) def load_djpeg(self): From 375556ffb5434e58408cc0eebc507fcb3d439644 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 24 Nov 2019 05:24:00 +0300 Subject: [PATCH 3/7] use a box from draft in thumbnail --- Tests/test_image_thumbnail.py | 13 ++++++++++++- src/PIL/Image.py | 7 +++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index bd7c98c28..c6db3b59b 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,6 +1,6 @@ from PIL import Image -from .helper import PillowTestCase, hopper +from .helper import PillowTestCase, hopper, fromstring, tostring class TestImageThumbnail(PillowTestCase): @@ -47,3 +47,14 @@ class TestImageThumbnail(PillowTestCase): im = Image.open("Tests/images/hopper.jpg") im.thumbnail((64, 64)) self.assert_image(im, im.mode, (64, 64)) + + def test_DCT_scaling_edges(self): + # Make an image with red borders with size (N * 8) + 1 to cross DCT grid + im = Image.new('RGB', (97, 97), 'red') + im.paste(Image.new('RGB', (95, 95)), (1, 1)) + + thumb = fromstring(tostring(im, "JPEG", quality=95)) + thumb.thumbnail((24, 24), Image.BICUBIC) + + ref = im.resize((24, 24), Image.BICUBIC) + self.assert_image_similar(thumb, ref, 2) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 05ff9239e..d2b44ff09 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2212,14 +2212,17 @@ class Image(object): x = int(max(x * size[1] / y, 1)) y = int(size[1]) size = x, y + box = None if size == self.size: return - self.draft(None, size) + res = self.draft(None, size) + if res is not None: + box = res[1] if self.size != size: - im = self.resize(size, resample) + im = self.resize(size, resample, box=box) self.im = im.im self._size = size From e234445682d362ccf929c404984927d3e52ce786 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 24 Nov 2019 15:22:13 +0300 Subject: [PATCH 4/7] linter fixes --- Tests/test_image_draft.py | 2 +- Tests/test_image_thumbnail.py | 8 ++++---- src/PIL/JpegImagePlugin.py | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 0f7402698..a185b3a63 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -17,7 +17,7 @@ class TestImageDraft(PillowTestCase): scale, _ = im.decoderconfig self.assertEqual(box[:2], (0, 0)) self.assertTrue((im.width - scale) < box[2] <= im.width) - self.assertTrue((im.height - scale) < box[3] <= im.height) + self.assertTrue((im.height - scale) < box[3] <= im.height) return im def test_size(self): diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 1e887520b..3f7263311 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,6 +1,6 @@ from PIL import Image -from .helper import PillowTestCase, hopper, fromstring, tostring +from .helper import PillowTestCase, fromstring, hopper, tostring class TestImageThumbnail(PillowTestCase): @@ -49,9 +49,9 @@ class TestImageThumbnail(PillowTestCase): self.assert_image(im, im.mode, (64, 64)) def test_DCT_scaling_edges(self): - # Make an image with red borders with size (N * 8) + 1 to cross DCT grid - im = Image.new('RGB', (97, 97), 'red') - im.paste(Image.new('RGB', (95, 95)), (1, 1)) + # Make an image with red borders and size (N * 8) + 1 to cross DCT grid + im = Image.new("RGB", (97, 97), "red") + im.paste(Image.new("RGB", (95, 95)), (1, 1)) thumb = fromstring(tostring(im, "JPEG", quality=95)) thumb.thumbnail((24, 24), Image.BICUBIC) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index f5fd3b8c6..a7985421d 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -433,8 +433,7 @@ class JpegImageFile(ImageFile.ImageFile): self.tile = [(d, e, o, a)] self.decoderconfig = (scale, 0) - box = (0, 0, original_size[0] / float(scale), - original_size[1] / float(scale)) + box = (0, 0, original_size[0] / float(scale), original_size[1] / float(scale)) return (self.mode, box) def load_djpeg(self): From c23f29481c096f660bf522e4e87918c30cb4d284 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 24 Nov 2019 15:34:12 +0300 Subject: [PATCH 5/7] try to deal with different libjpeg version --- Tests/test_image_thumbnail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 3f7263311..ecf98188b 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -50,11 +50,11 @@ class TestImageThumbnail(PillowTestCase): def test_DCT_scaling_edges(self): # Make an image with red borders and size (N * 8) + 1 to cross DCT grid - im = Image.new("RGB", (97, 97), "red") - im.paste(Image.new("RGB", (95, 95)), (1, 1)) + im = Image.new("RGB", (257, 257), "red") + im.paste(Image.new("RGB", (255, 255)), (1, 1)) - thumb = fromstring(tostring(im, "JPEG", quality=95)) - thumb.thumbnail((24, 24), Image.BICUBIC) + thumb = fromstring(tostring(im, "JPEG", quality=99, subsampling=0)) + thumb.thumbnail((32, 32), Image.BICUBIC) - ref = im.resize((24, 24), Image.BICUBIC) + ref = im.resize((32, 32), Image.BICUBIC) self.assert_image_similar(thumb, ref, 2) From 4e092153fc3e499928e1451c4846dec28b8c02ac Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 30 Nov 2019 18:17:10 +0300 Subject: [PATCH 6/7] add release notes --- docs/releasenotes/7.0.0.rst | 14 ++++++++++++++ src/PIL/Image.py | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst index 386a26757..f0612351f 100644 --- a/docs/releasenotes/7.0.0.rst +++ b/docs/releasenotes/7.0.0.rst @@ -47,6 +47,20 @@ Setting the size of TIFF images Setting the size of a TIFF image directly (eg. ``im.size = (256, 256)``) throws an error. Use ``Image.resize`` instead. +Image.draft() return value +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``Image.draft()`` method used to return ``None`` or the image itself. +Unlike other `chain methods`_, ``draft()`` modifies the image in-place +rather than returning a modified version. + +In the new version, ``draft()`` returns ``None`` if it has no effect or +a tuple of new image mode and the box of original coordinates in the +bounds of resulting image otherwise +(the box could be useful in subsequent ``resize()`` call). + +.. _chain methods: https://en.wikipedia.org/wiki/Method_chaining + API Changes =========== diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 12280d026..928f33c5f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1130,8 +1130,8 @@ class Image: JPEG to greyscale while loading it, or to extract a 128x192 version from a PCD file. - If any changes are made, returns a tuple with the chosen `mode` and - `box` with coordinates of the original image within the altered one. + If any changes are made, returns a tuple with the chosen ``mode`` and + ``box`` with coordinates of the original image within the altered one. Note that this method modifies the :py:class:`~PIL.Image.Image` object in place. If the image has already been loaded, this method has no From b35cbef23483cf6a04e056d77568bc3f2f1ed43c Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 17 Dec 2019 02:25:40 +0300 Subject: [PATCH 7/7] modify the test image --- Tests/test_image_thumbnail.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 2cc2d732d..0f2955574 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -57,10 +57,11 @@ class TestImageThumbnail(PillowTestCase): def test_DCT_scaling_edges(self): # Make an image with red borders and size (N * 8) + 1 to cross DCT grid im = Image.new("RGB", (257, 257), "red") - im.paste(Image.new("RGB", (255, 255)), (1, 1)) + im.paste(Image.new("RGB", (235, 235)), (11, 11)) thumb = fromstring(tostring(im, "JPEG", quality=99, subsampling=0)) thumb.thumbnail((32, 32), Image.BICUBIC) ref = im.resize((32, 32), Image.BICUBIC) - self.assert_image_similar(thumb, ref, 2) + # This is still JPEG, some error is present. Without the fix it is 11.5 + self.assert_image_similar(thumb, ref, 1.5)