From 4915f19b1370c17036cb3f4c3cbe12bbf23bc43c Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Tue, 2 Apr 2024 17:45:06 +0200 Subject: [PATCH 1/7] fromarray: add type hints --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index baef0aa112e..0dfccd9b030 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3069,7 +3069,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): return frombytes(mode, size, data, decoder_name, args) -def fromarray(obj, mode=None): +def fromarray(obj: "numpy.typing.ArrayLike", mode: Optional[str] = None) -> Image: """ Creates an image memory from an object exporting the array interface (using the buffer protocol):: From 8e47a6f2c82b696f6b96bb8fff350a937ea195b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:46:25 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 0dfccd9b030..a2723756fef 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3069,7 +3069,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): return frombytes(mode, size, data, decoder_name, args) -def fromarray(obj: "numpy.typing.ArrayLike", mode: Optional[str] = None) -> Image: +def fromarray(obj: numpy.typing.ArrayLike, mode: Optional[str] = None) -> Image: """ Creates an image memory from an object exporting the array interface (using the buffer protocol):: From 37ed8c337de3617bb0b17b721c0cef08e502dc3b Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Tue, 2 Apr 2024 17:53:11 +0200 Subject: [PATCH 3/7] Try type comment --- src/PIL/Image.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index a2723756fef..c932f1932ac 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -41,7 +41,7 @@ from collections.abc import Callable, MutableMapping from enum import IntEnum from types import ModuleType -from typing import IO, TYPE_CHECKING, Any +from typing import IO, Optional, TYPE_CHECKING, Any # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0. @@ -3069,7 +3069,10 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): return frombytes(mode, size, data, decoder_name, args) -def fromarray(obj: numpy.typing.ArrayLike, mode: Optional[str] = None) -> Image: +def fromarray( + obj, # type: numpy.typing.ArrayLike + mode: Optional[str] = None +) -> Image: """ Creates an image memory from an object exporting the array interface (using the buffer protocol):: From 8a63980e393e0ffc9fc9ae4337d97b5bbfb95524 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:53:39 +0000 Subject: [PATCH 4/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/Image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c932f1932ac..a4043c620d0 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -41,7 +41,7 @@ from collections.abc import Callable, MutableMapping from enum import IntEnum from types import ModuleType -from typing import IO, Optional, TYPE_CHECKING, Any +from typing import IO, TYPE_CHECKING, Any, Optional # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0. @@ -3071,7 +3071,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): def fromarray( obj, # type: numpy.typing.ArrayLike - mode: Optional[str] = None + mode: Optional[str] = None, ) -> Image: """ Creates an image memory from an object exporting the array interface From 5d19151cd31951754dc284db7434c4e6e7fbc240 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Tue, 2 Apr 2024 17:56:52 +0200 Subject: [PATCH 5/7] Python 3.10+ --- src/PIL/Image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index a4043c620d0..245fadd6b3f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -41,7 +41,7 @@ from collections.abc import Callable, MutableMapping from enum import IntEnum from types import ModuleType -from typing import IO, TYPE_CHECKING, Any, Optional +from typing import IO, TYPE_CHECKING, Any # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0. @@ -3071,7 +3071,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): def fromarray( obj, # type: numpy.typing.ArrayLike - mode: Optional[str] = None, + mode: str | None = None, ) -> Image: """ Creates an image memory from an object exporting the array interface From e85a84baa7ebee1b458cabbeb23b8d497c735175 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 3 Apr 2024 20:00:40 +1100 Subject: [PATCH 6/7] Added SupportsArrayInterface --- Tests/test_image_array.py | 10 ++++++++++ docs/reference/Image.rst | 2 ++ src/PIL/Image.py | 22 +++++++++++++++------- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index cf85ee4fa1c..342bd8654e1 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -91,6 +91,16 @@ def test(mode: str) -> tuple[str, tuple[int, int], bool]: Image.fromarray(wrapped) +def test_fromarray_strides_without_tobytes() -> None: + class Wrapper: + def __init__(self, arr_params: dict[str, Any]) -> None: + self.__array_interface__ = arr_params + + with pytest.raises(ValueError): + wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)}) + Image.fromarray(wrapped, "L") + + def test_fromarray_palette() -> None: # Arrange i = im.convert("L") diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 4281b182ce7..0d9b4d93d77 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -78,6 +78,8 @@ Constructing images ^^^^^^^^^^^^^^^^^^^ .. autofunction:: new +.. autoclass:: SupportsArrayInterface + :show-inheritance: .. autofunction:: fromarray .. autofunction:: frombytes .. autofunction:: frombuffer diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 245fadd6b3f..7e68ee3cb40 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -41,7 +41,7 @@ from collections.abc import Callable, MutableMapping from enum import IntEnum from types import ModuleType -from typing import IO, TYPE_CHECKING, Any +from typing import IO, TYPE_CHECKING, Any, Protocol # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0. @@ -3013,7 +3013,7 @@ def frombytes(mode, size, data, decoder_name="raw", *args) -> Image: return im -def frombuffer(mode, size, data, decoder_name="raw", *args): +def frombuffer(mode, size, data, decoder_name="raw", *args) -> Image: """ Creates an image memory referencing pixel data in a byte buffer. @@ -3069,10 +3069,15 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): return frombytes(mode, size, data, decoder_name, args) -def fromarray( - obj, # type: numpy.typing.ArrayLike - mode: str | None = None, -) -> Image: +class SupportsArrayInterface(Protocol): + """ + An object that has an ``__array_interface__`` dictionary. + """ + + __array_interface__: dict[str, Any] + + +def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image: """ Creates an image memory from an object exporting the array interface (using the buffer protocol):: @@ -3151,8 +3156,11 @@ def fromarray( if strides is not None: if hasattr(obj, "tobytes"): obj = obj.tobytes() - else: + elif hasattr(obj, "tostring"): obj = obj.tostring() + else: + msg = "'strides' requires either tobytes() or tostring()" + raise ValueError(msg) return frombuffer(mode, size, obj, "raw", rawmode, 0, 1) From c655dc0c6b6257ffecb7fa8464ac51d19347e651 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Tue, 16 Apr 2024 17:53:48 +0200 Subject: [PATCH 7/7] Use a property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- src/PIL/Image.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b64133cbc36..c65cf38500a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3079,7 +3079,9 @@ class SupportsArrayInterface(Protocol): An object that has an ``__array_interface__`` dictionary. """ - __array_interface__: dict[str, Any] + @property + def __array_interface__(self) -> dict[str, Any]: + raise NotImplementedError() def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image: