diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 5d92ee797..a185b3a63 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/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 30fe25557..0f2955574 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, fromstring, hopper, tostring class TestImageThumbnail(PillowTestCase): @@ -53,3 +53,15 @@ class TestImageThumbnail(PillowTestCase): with Image.open("Tests/images/hopper.jpg") as im: im.thumbnail((64, 64)) self.assertEqual(im.size, (64, 64)) + + 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", (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) + # This is still JPEG, some error is present. Without the fix it is 11.5 + self.assert_image_similar(thumb, ref, 1.5) 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 f293e1027..c4be97603 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1126,12 +1126,15 @@ class Image: """ 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. + 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 + 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 @@ -2176,14 +2179,17 @@ class Image: x = max(round(x * size[1] / y), 1) y = 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 diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index a343bd43c..c8d6b6ba3 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -122,11 +122,6 @@ class ImageFile(Image.Image): self.fp.close() raise - 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 4286e1ae7..fe3d41e16 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -412,7 +412,8 @@ class JpegImageFile(ImageFile.ImageFile): return d, e, o, a = self.tile[0] - scale = 0 + scale = 1 + original_size = self.size if a[0] == "RGB" and mode in ["L", "YCbCr"]: self.mode = mode @@ -435,7 +436,8 @@ 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):