diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 6771b46b051..1f28f330e77 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -53,6 +53,22 @@ def test_args_factor(size: int | tuple[int, int], expected: tuple[int, int]) -> assert expected == im.reduce(size).size +@pytest.mark.parametrize("mode", ("L", "I", "F")) +@pytest.mark.parametrize( + "size, expected", + ( + (2**31 - 1, (1, 1)), + ((2**31 - 1, 1), (1, 10)), + ((1, 2**31 - 1), (10, 1)), + ), +) +def test_args_factor_large( + size: int | tuple[int, int], expected: tuple[int, int], mode: str +) -> None: + im = Image.new(mode, (10, 10)) + assert im.reduce(size).size == expected + + @pytest.mark.parametrize( "size, expected_error", ((0, ValueError), (2.0, TypeError), ((0, 10), ValueError)) ) diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c index a4e58ced81b..c4cb4bc76be 100644 --- a/src/libImaging/Reduce.c +++ b/src/libImaging/Reduce.c @@ -1460,8 +1460,11 @@ ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) { return (Imaging)ImagingError_ModeError(); } + /* Round the size up. Dividing before adding avoids overflowing for a + large scale, and guarantees a size of at least 1x1. + */ imOut = ImagingNewDirty( - imIn->mode, (box[2] + xscale - 1) / xscale, (box[3] + yscale - 1) / yscale + imIn->mode, (box[2] - 1) / xscale + 1, (box[3] - 1) / yscale + 1 ); if (!imOut) { return NULL;