diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index f2519a284cf..c4bc0921502 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -128,6 +128,18 @@ def test_contain_round() -> None: assert new_im.height == 5 +@pytest.mark.parametrize( + "operation", (ImageOps.contain, ImageOps.cover, ImageOps.pad, ImageOps.fit) +) +@pytest.mark.parametrize("size", ((0, 10), (10, 0), (-1, 10), (10, -1))) +def test_size_must_be_positive(operation, size: tuple[int, int]) -> None: + # A zero or negative dimension must raise a clear ValueError, not a + # ZeroDivisionError (or silently return a wrong-sized image). + with Image.new("RGB", (100, 50)) as im: + with pytest.raises(ValueError, match="height and width must be > 0"): + operation(im, size) + + @pytest.mark.parametrize( "image_name, expected_size", ( diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 45667d9ec07..42dc63f7f4b 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -101,3 +101,12 @@ to help projects prepare for 3.15, and to ensure Pillow could be used immediately at the release of 3.15.0 final (2026-10-01, :pep:`790`). Pillow 13.0.0 now officially supports Python 3.15. + +ImageOps invalid sizes +^^^^^^^^^^^^^^^^^^^^^^^ + +:py:meth:`~PIL.ImageOps.contain`, :py:meth:`~PIL.ImageOps.cover`, +:py:meth:`~PIL.ImageOps.pad` and :py:meth:`~PIL.ImageOps.fit` now raise a +``ValueError`` when the requested size has a dimension that is zero or negative. +Previously a zero height raised ``ZeroDivisionError``, and some invalid sizes +returned an incorrectly sized image. diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index cdec4d5dc7d..bd9edd9e41c 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -277,6 +277,12 @@ def colorize( return _lut(image, red + green + blue) +def _check_size(size: tuple[int, int]) -> None: + if size[0] <= 0 or size[1] <= 0: + msg = "height and width must be > 0" + raise ValueError(msg) + + def contain( image: Image.Image, size: tuple[int, int], method: int = Image.Resampling.BICUBIC ) -> Image.Image: @@ -293,6 +299,8 @@ def contain( :return: An image. """ + _check_size(size) + im_ratio = image.width / image.height dest_ratio = size[0] / size[1] @@ -324,6 +332,8 @@ def cover( :return: An image. """ + _check_size(size) + im_ratio = image.width / image.height dest_ratio = size[0] / size[1] @@ -562,6 +572,8 @@ def fit( :return: An image. """ + _check_size(size) + # by Kevin Cazabon, Feb 17/2000 # kevin@cazabon.com # https://www.cazabon.com