Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Tests/test_image_reduce.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
)
Expand Down
5 changes: 4 additions & 1 deletion src/libImaging/Reduce.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd maybe add a comment here about the operation order, and that it guarantees the image to have at least size 1x1.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in bf60f3c.

While checking this I noticed that xscale * yscale in ImagingReduceNxN and (box[2] % xscale) * yscale in ImagingReduceCorners still overflow with a factor this large. Those values only feed loops that run zero iterations, so the output pixels are correct and no bad memory access happens, but an instrumented build will still report the overflow on the same input. Clamping both scales to the box size after the size calculation would remove it.

I am happy to add that here, or to keep this PR to the crash and raise it separately.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean with "Those values only feed loops that run zero iterations"? Those functions probably shouldn't end up being called...

In fact, it could be a good idea to add separate paths when the reduction ends up being one-dimensional in one dimension or the other, or both, since isn't the end result then just an average over rows, columns or both (separable)?

);
if (!imOut) {
return NULL;
Expand Down
Loading