From b9fde7b74cc3b3f9bf03bbd269d829e58ef04eea Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Sep 2023 11:37:46 +1000 Subject: [PATCH] Added ImageOps cover method --- Tests/test_imageops.py | 17 +++++++++++++++++ docs/reference/ImageOps.rst | 1 + src/PIL/ImageOps.py | 31 ++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index b05785be0ec..6d153cceaf9 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -39,6 +39,9 @@ def test_sanity(): ImageOps.contain(hopper("L"), (128, 128)) ImageOps.contain(hopper("RGB"), (128, 128)) + ImageOps.cover(hopper("L"), (128, 128)) + ImageOps.cover(hopper("RGB"), (128, 128)) + ImageOps.crop(hopper("L"), 1) ImageOps.crop(hopper("RGB"), 1) @@ -119,6 +122,20 @@ def test_contain_round(): assert new_im.height == 5 +@pytest.mark.parametrize( + "image_name, expected_size", + ( + ("colr_bungee.png", (1024, 256)), # landscape + ("imagedraw_stroke_multiline.png", (256, 640)), # portrait + ("hopper.png", (256, 256)), # square + ), +) +def test_cover(image_name, expected_size): + with Image.open("Tests/images/" + image_name) as im: + new_im = ImageOps.cover(im, (256, 256)) + assert new_im.size == expected_size + + def test_pad(): # Same ratio im = hopper() diff --git a/docs/reference/ImageOps.rst b/docs/reference/ImageOps.rst index d1c43cf6092..ad475e7fcc4 100644 --- a/docs/reference/ImageOps.rst +++ b/docs/reference/ImageOps.rst @@ -13,6 +13,7 @@ only work on L and RGB images. .. autofunction:: autocontrast .. autofunction:: colorize .. autofunction:: contain +.. autofunction:: cover .. autofunction:: pad .. autofunction:: crop .. autofunction:: scale diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 1231ad6ebda..5b057e0628e 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -242,7 +242,7 @@ def contain(image, size, method=Image.Resampling.BICUBIC): 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 image: The image to resize. :param size: The requested output size in pixels, given as a (width, height) tuple. :param method: Resampling method to use. Default is @@ -266,6 +266,35 @@ def contain(image, size, method=Image.Resampling.BICUBIC): return image.resize(size, resample=method) +def cover(image, size, method=Image.Resampling.BICUBIC): + """ + Returns a resized version of the image, set to the minimum width and height + within the requested size, while maintaining the original aspect ratio. + + :param image: The image to resize. + :param size: The requested output size in pixels, given as a + (width, height) tuple. + :param method: Resampling method to use. Default is + :py:attr:`~PIL.Image.Resampling.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 = round(image.height / image.width * size[0]) + if new_height != size[1]: + size = (size[0], new_height) + else: + new_width = round(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.Resampling.BICUBIC, color=None, centering=(0.5, 0.5)): """ Returns a resized and padded version of the image, expanded to fill the