From 40eceec70db497385eb00b649288e326cc2da12e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 19 Apr 2021 20:12:52 +1000 Subject: [PATCH 1/8] Added contain method --- Tests/test_imageops.py | 18 ++++++++++++++ src/PIL/ImageOps.py | 54 ++++++++++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 93be34bf821..987a531dfee 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -37,6 +37,9 @@ def test_sanity(): ImageOps.pad(hopper("L"), (128, 128)) ImageOps.pad(hopper("RGB"), (128, 128)) + ImageOps.contain(hopper("L"), (128, 128)) + ImageOps.contain(hopper("RGB"), (128, 128)) + ImageOps.crop(hopper("L"), 1) ImageOps.crop(hopper("RGB"), 1) @@ -99,6 +102,21 @@ def test_fit_same_ratio(): assert new_im.size == (1000, 755) +def test_contain(): + # Same ratio + im = hopper() + new_size = (im.width * 2, im.height * 2) + new_im = ImageOps.contain(im, new_size) + assert new_im.size == new_size + + for new_size in [ + (im.width * 4, im.height * 2), + (im.width * 2, im.height * 4), + ]: + new_im = ImageOps.contain(im, new_size) + assert new_im.size == (im.width * 2, im.height * 2) + + def test_pad(): # Same ratio im = hopper() diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index d69a304ca97..6c15957d878 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -236,6 +236,34 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi return _lut(image, red + green + blue) +def contain(image, size, method=Image.BICUBIC): + """ + Returns a sized version of the image, expanded to fill the requested aspect ratio + and size. + + :param image: The image to size and crop. + :param size: The requested output size in pixels, given as a + (width, height) tuple. + :param method: What resampling method to use. Default is + :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. + :return: An image. + """ + + im_ratio = image.width / image.height + dest_ratio = size[0] / size[1] + + if im_ratio != dest_ratio: + if im_ratio > dest_ratio: + new_height = int(image.height / image.width * size[0]) + if new_height != size[1]: + size = (size[0], new_height) + else: + new_width = int(image.width / image.height * size[1]) + if new_width != size[0]: + size = (new_width, size[1]) + return image.resize(size, resample=method) + + def pad(image, size, method=Image.BICUBIC, color=None, centering=(0.5, 0.5)): """ Returns a sized and padded version of the image, expanded to fill the @@ -257,27 +285,17 @@ def pad(image, size, method=Image.BICUBIC, color=None, centering=(0.5, 0.5)): :return: An image. """ - im_ratio = image.width / image.height - dest_ratio = size[0] / size[1] - - if im_ratio == dest_ratio: - out = image.resize(size, resample=method) + resized = contain(image, size, method) + if resized.size == size: + out = resized else: out = Image.new(image.mode, size, color) - if im_ratio > 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)) + if resized.width != size[0]: + x = int((size[0] - resized.width) * max(0, min(centering[0], 1))) + out.paste(resized, (x, 0)) 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)) + y = int((size[1] - resized.height) * max(0, min(centering[1], 1))) + out.paste(resized, (0, y)) return out From 91b3a9d6a10c57f1e1fe187e361df9f400422d8f Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 1 May 2021 21:56:49 +1000 Subject: [PATCH 2/8] Updated docstring Co-authored-by: Hugo van Kemenade --- src/PIL/ImageOps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 6c15957d878..4ac2ff33c2b 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -241,10 +241,10 @@ def contain(image, size, method=Image.BICUBIC): Returns a sized version of the image, expanded to fill the requested aspect ratio and size. - :param image: The image to size and crop. + :param image: The image to resize and crop. :param size: The requested output size in pixels, given as a (width, height) tuple. - :param method: What resampling method to use. Default is + :param method: Resampling method to use. Default is :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. :return: An image. """ From 38a520c109e725cae1a106f12882bb1915f52554 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 May 2021 22:35:53 +1000 Subject: [PATCH 3/8] Updated docstrings --- src/PIL/ImageOps.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 4ac2ff33c2b..f9c35b2c69f 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -238,8 +238,8 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi def contain(image, size, method=Image.BICUBIC): """ - Returns a sized version of the image, expanded to fill the requested aspect ratio - and size. + Returns a resized version of the image, set to the maximum width and height + within the requested size, while maintaining the original aspect ratio. :param image: The image to resize and crop. :param size: The requested output size in pixels, given as a @@ -266,13 +266,13 @@ def contain(image, size, method=Image.BICUBIC): def pad(image, size, method=Image.BICUBIC, color=None, centering=(0.5, 0.5)): """ - Returns a sized and padded version of the image, expanded to fill the + Returns a resized and padded version of the image, expanded to fill the requested aspect ratio and size. - :param image: The image to size and crop. + :param image: The image to resize and crop. :param size: The requested output size in pixels, given as a (width, height) tuple. - :param method: What resampling method to use. Default is + :param method: Resampling method to use. Default is :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. :param color: The background color of the padded image. :param centering: Control the position of the original image within the @@ -322,7 +322,7 @@ def scale(image, factor, resample=Image.BICUBIC): :param image: The image to rescale. :param factor: The expansion factor, as a float. - :param resample: What resampling method to use. Default is + :param resample: Resampling method to use. Default is :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. :returns: An :py:class:`~PIL.Image.Image` object. """ @@ -399,15 +399,15 @@ def expand(image, border=0, fill=0): def fit(image, size, method=Image.BICUBIC, bleed=0.0, centering=(0.5, 0.5)): """ - Returns a sized and cropped version of the image, cropped to the + Returns a resized and cropped version of the image, cropped to the requested aspect ratio and size. This function was contributed by Kevin Cazabon. - :param image: The image to size and crop. + :param image: The image to resize and crop. :param size: The requested output size in pixels, given as a (width, height) tuple. - :param method: What resampling method to use. Default is + :param method: Resampling method to use. Default is :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. :param bleed: Remove a border around the outside of the image from all four edges. The value is a decimal percentage (use 0.01 for From d22fe41776f5c29cd693bd368cfe7b0dace096a6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 May 2021 22:04:14 +1000 Subject: [PATCH 4/8] Reorganised test to use parametrize --- Tests/test_imageops.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 987a531dfee..ff2445a5168 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -102,19 +102,11 @@ def test_fit_same_ratio(): assert new_im.size == (1000, 755) -def test_contain(): - # Same ratio +@pytest.mark.parametrize("new_size", ((256, 256), (512, 256), (256, 512))) +def test_contain(new_size): im = hopper() - new_size = (im.width * 2, im.height * 2) new_im = ImageOps.contain(im, new_size) - assert new_im.size == new_size - - for new_size in [ - (im.width * 4, im.height * 2), - (im.width * 2, im.height * 4), - ]: - new_im = ImageOps.contain(im, new_size) - assert new_im.size == (im.width * 2, im.height * 2) + assert new_im.size == (256, 256) def test_pad(): From b432f838f1d67fc280a6e33fe8d81cd328b06994 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 May 2021 22:53:46 +1000 Subject: [PATCH 5/8] Documented ImageOps contain method --- docs/reference/ImageOps.rst | 1 + docs/releasenotes/8.3.0.rst | 49 +++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 docs/releasenotes/8.3.0.rst diff --git a/docs/reference/ImageOps.rst b/docs/reference/ImageOps.rst index 9a16d6625e7..d1c43cf6092 100644 --- a/docs/reference/ImageOps.rst +++ b/docs/reference/ImageOps.rst @@ -12,6 +12,7 @@ only work on L and RGB images. .. autofunction:: autocontrast .. autofunction:: colorize +.. autofunction:: contain .. autofunction:: pad .. autofunction:: crop .. autofunction:: scale diff --git a/docs/releasenotes/8.3.0.rst b/docs/releasenotes/8.3.0.rst new file mode 100644 index 00000000000..4459083ea02 --- /dev/null +++ b/docs/releasenotes/8.3.0.rst @@ -0,0 +1,49 @@ +8.3.0 +----- + +Deprecations +============ + +TODO +^^^^ + +TODO + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +ImageOps.contain +^^^^^^^^^^^^^^^^ + +Returns a resized version of the image, set to the maximum width and height within +``size``, while maintaining the original aspect ratio. + +To compare it to other ImageOps methods: +- :py:meth:`~PIL.ImageOps.fit` expands an image until is fills ``size``, cropping the +parts of the image that do not fit. +- :py:meth:`~PIL.ImageOps.pad` expands an image to fill ``size``, without cropping, but +instead filling the extra space with ``color``. +- :py:meth:`~PIL.ImageOps.contain` is similar to :py:meth:`~PIL.ImageOps.pad`, but +it does not fill the extra space. Instead, the original aspect ratio is maintained. So +unlike the other two methods, it is not guaranteed to return an image of ``size``. + +Security +======== + +TODO + +Other Changes +============= + +TODO +^^^^ + +TODO From f45f7dcc23801202ff89d7eec3faacb67a7268d0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 May 2021 23:08:49 +1000 Subject: [PATCH 6/8] Documented #5450 --- docs/releasenotes/8.3.0.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.3.0.rst b/docs/releasenotes/8.3.0.rst index 4459083ea02..958f1c48df6 100644 --- a/docs/releasenotes/8.3.0.rst +++ b/docs/releasenotes/8.3.0.rst @@ -12,10 +12,11 @@ TODO API Changes =========== -TODO -^^^^ +Changed WebP default "method" value when saving +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +Previously, it was 0, for the best speed. The default has now been changed to 4, to +match WebP's default, for higher quality with still some speed optimisation. API Additions ============= From 5b0031c9fabd703b860502aeb8ca1aa748452db1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 May 2021 23:09:20 +1000 Subject: [PATCH 7/8] Documented #5411 --- docs/releasenotes/8.3.0.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/releasenotes/8.3.0.rst b/docs/releasenotes/8.3.0.rst index 958f1c48df6..312161da6ce 100644 --- a/docs/releasenotes/8.3.0.rst +++ b/docs/releasenotes/8.3.0.rst @@ -18,6 +18,13 @@ Changed WebP default "method" value when saving Previously, it was 0, for the best speed. The default has now been changed to 4, to match WebP's default, for higher quality with still some speed optimisation. +Default resampling filter for special image modes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Pillow 7.0 changed the default resampling filter to ``Image.BICUBIC``. However, as this +is not supported yet for images with a custom number of bits, the default filter for +those modes has been reverted to ``Image.NEAREST``. + API Additions ============= From c0624109850d7af4f2aa0fe0c599d2c5baa982ec Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 May 2021 23:44:09 +1000 Subject: [PATCH 8/8] Documented #5414 --- docs/releasenotes/8.3.0.rst | 17 ++++++++++++----- docs/releasenotes/index.rst | 1 + 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/releasenotes/8.3.0.rst b/docs/releasenotes/8.3.0.rst index 312161da6ce..a4b8cb88c86 100644 --- a/docs/releasenotes/8.3.0.rst +++ b/docs/releasenotes/8.3.0.rst @@ -25,6 +25,12 @@ Pillow 7.0 changed the default resampling filter to ``Image.BICUBIC``. However, is not supported yet for images with a custom number of bits, the default filter for those modes has been reverted to ``Image.NEAREST``. +ImageMorph incorrect mode errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For ``apply()``, ``match()`` and ``get_on_pixels()``, if the image mode is not L, an +:py:exc:`Exception` was thrown. This has now been changed to a :py:exc:`ValueError`. + API Additions ============= @@ -35,13 +41,14 @@ Returns a resized version of the image, set to the maximum width and height with ``size``, while maintaining the original aspect ratio. To compare it to other ImageOps methods: + - :py:meth:`~PIL.ImageOps.fit` expands an image until is fills ``size``, cropping the -parts of the image that do not fit. + parts of the image that do not fit. - :py:meth:`~PIL.ImageOps.pad` expands an image to fill ``size``, without cropping, but -instead filling the extra space with ``color``. -- :py:meth:`~PIL.ImageOps.contain` is similar to :py:meth:`~PIL.ImageOps.pad`, but -it does not fill the extra space. Instead, the original aspect ratio is maintained. So -unlike the other two methods, it is not guaranteed to return an image of ``size``. + instead filling the extra space with ``color``. +- :py:meth:`~PIL.ImageOps.contain` is similar to :py:meth:`~PIL.ImageOps.pad`, but it + does not fill the extra space. Instead, the original aspect ratio is maintained. So + unlike the other two methods, it is not guaranteed to return an image of ``size``. Security ======== diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 11773867551..3e23e43d3a1 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -14,6 +14,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 8.3.0 8.2.0 8.1.2 8.1.1