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
22 changes: 3 additions & 19 deletions Tests/test_image_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,6 @@ def test_f_mode() -> None:
im.point([])


def test_overstated_length() -> None:
# shouldn't segfault
# see https://github.com/python-pillow/Pillow/issues/9892

class OverstatedLengthSequence:
def __len__(self) -> int:
return 256

def __getitem__(self, index: int) -> int:
if index >= 8:
raise IndexError
return index

im = Image.new("L", (4, 4))
with pytest.raises(ValueError):
im.point(OverstatedLengthSequence(), "F") # type: ignore[arg-type]


def test_raising_length() -> None:
class RaisingLengthSequence:
def __len__(self) -> int:
Expand All @@ -93,5 +75,7 @@ def __getitem__(self, index: int) -> float:
return index

im = Image.new("L", (1, 1))
with pytest.raises(RuntimeError):
with pytest.raises(
TypeError, match="argument must be a sequence, callable or ImagePointHandler"
):
im.point(RaisingLengthSequence(), "F") # type: ignore[arg-type]
20 changes: 1 addition & 19 deletions Tests/test_image_putdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,24 +105,6 @@ def test_too_many_entries() -> None:
im.putdata(list(range(17)))


def test_overstated_length() -> None:
# shouldn't segfault
# see https://github.com/python-pillow/Pillow/issues/9892

class OverstatedLengthSequence:
def __len__(self) -> int:
return 16

def __getitem__(self, index: int) -> int:
if index >= 2:
raise IndexError
return index + 1

im = Image.new("L", (4, 4))
im.putdata(OverstatedLengthSequence()) # type: ignore[arg-type]
assert im.get_flattened_data() == (1, 2, *(0,) * 14)


def test_raising_length() -> None:
class RaisingLengthSequence:
def __len__(self) -> int:
Expand All @@ -132,7 +114,7 @@ def __getitem__(self, index: int) -> int:
return index

im = Image.new("L", (1, 1))
with pytest.raises(RuntimeError):
with pytest.raises(TypeError, match="argument must be a sequence"):
im.putdata(RaisingLengthSequence()) # type: ignore[arg-type]


Expand Down
48 changes: 48 additions & 0 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Callable, Iterator, Sequence
from types import ModuleType
from typing import Any, Literal, Self


Expand Down Expand Up @@ -614,6 +615,17 @@ def getdata(
) -> tuple[Transform, Sequence[int]]: ...


def _is_numpy_array(data: object) -> bool:
numpy: ModuleType | None = None
if hasattr(data, "shape"):
try:
import numpy
except ImportError:
pass

return numpy is not None and isinstance(data, numpy.ndarray)


class Image:
"""
This class represents an image object. To create
Expand Down Expand Up @@ -2045,6 +2057,14 @@ def point(self, im: Image) -> Image:
# for other modes, convert the function to a table
flatLut = [lut(i) for i in range(256)] * self.im.bands # type: ignore[arg-type]
else:
if type(lut) in (list, tuple):
for value in lut:
if type(value) not in (int, float):
msg = "sequence must be flattened"
raise TypeError(msg)
elif not _is_numpy_array(lut):
msg = "argument must be a sequence, callable or ImagePointHandler"
raise TypeError(msg)
flatLut = lut

if self.mode == "F":
Expand Down Expand Up @@ -2130,6 +2150,34 @@ def putdata(
:param scale: An optional scale value. The default is 1.0.
:param offset: An optional offset value. The default is 0.0.
"""
if type(data) in (list, tuple):
singleChannel = getmodebands(self.mode) == 1
for value in data:
if singleChannel:
if type(value) not in (int, float):
msg = "sequence must be flattened"
raise TypeError(msg)
elif type(value) in (list, tuple):
assert not isinstance(value, float)
if not all(type(value2) is int for value2 in value):
msg = (
"color must be int, or tuple of one, three or four elements"
)
raise TypeError(msg)
elif type(value) is not int:
msg = "color must be int or tuple"
raise TypeError(msg)
else:
import array

if (
not isinstance(data, array.array)
and not _is_numpy_array(data)
# ImagingCore is only to support deprecated getdata()
and data.__class__.__name__ != "ImagingCore"
):
msg = "argument must be a sequence"
raise TypeError(msg)

self._ensure_mutable()

Expand Down
23 changes: 13 additions & 10 deletions src/PIL/ImageFilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
import abc
from typing import cast

from PIL import Image

TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Callable, Sequence
from types import ModuleType
from typing import Any

from . import _imaging
Expand Down Expand Up @@ -73,6 +74,15 @@ def __init__(
scale: float | None = None,
offset: float = 0,
) -> None:
if type(kernel) in (list, tuple):
for value in kernel:
if type(value) not in (int, float):
msg = "sequence must be flattened"
raise TypeError(msg)
else:
msg = "argument must be a sequence"
raise TypeError(msg)

if scale is None:
# default scale is sum of kernel
scale = sum(kernel)
Expand Down Expand Up @@ -407,15 +417,8 @@ def __init__(
items = size[0] * size[1] * size[2]
wrong_size = False

numpy: ModuleType | None = None
if hasattr(table, "shape"):
try:
import numpy
except ImportError:
pass

if numpy and isinstance(table, numpy.ndarray):
numpy_table: NumpyArray = table
if Image._is_numpy_array(table):
numpy_table = cast("NumpyArray", table)
if copy_table:
numpy_table = numpy_table.copy()

Expand Down
35 changes: 9 additions & 26 deletions src/_imaging.c
Original file line number Diff line number Diff line change
Expand Up @@ -428,14 +428,6 @@ getlist_impl(PyObject *arg, Py_ssize_t length, const char *wrong_length, int typ
return NULL;
}

Py_ssize_t reported = PySequence_Size(arg);
if (reported < 0) {
return NULL;
} else if (reported != length) {
PyErr_SetString(PyExc_ValueError, wrong_length);
return NULL;
}

seq = PySequence_Fast(arg, must_be_sequence);
if (!seq) {
return NULL;
Expand Down Expand Up @@ -1657,25 +1649,8 @@ _putdata(ImagingObject *self, PyObject *args) {

image = self->image;

if (image->image8 && PyBytes_Check(data)) {
n = PyBytes_GET_SIZE(data);
} else {
Py_ssize_t reported = PySequence_Size(data);
if (reported < 0) {
return NULL;
} else if (reported > (Py_ssize_t)image->xsize * (Py_ssize_t)image->ysize) {
PyErr_SetString(PyExc_TypeError, "too many data entries");
return NULL;
}

seq = PySequence_Fast(data, must_be_sequence);
if (!seq) {
return NULL;
}
n = PySequence_Fast_GET_SIZE(seq);
}
n = PyObject_Length(data);
if (n > (Py_ssize_t)image->xsize * (Py_ssize_t)image->ysize) {
Py_XDECREF(seq);
PyErr_SetString(PyExc_TypeError, "too many data entries");
return NULL;
}
Expand Down Expand Up @@ -1716,6 +1691,10 @@ _putdata(ImagingObject *self, PyObject *args) {
}
}
} else {
seq = PySequence_Fast(data, must_be_sequence);
if (!seq) {
return NULL;
}
double value;
int bigendian = 0;
if (image->type == IMAGING_TYPE_I16) {
Expand Down Expand Up @@ -1749,6 +1728,10 @@ _putdata(ImagingObject *self, PyObject *args) {
}
} else {
/* 32-bit images */
seq = PySequence_Fast(data, must_be_sequence);
if (!seq) {
return NULL;
}
switch (image->type) {
case IMAGING_TYPE_INT32:
for (i = x = y = 0; i < n; i++) {
Expand Down
Loading