Skip to content
Merged
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
4 changes: 2 additions & 2 deletions Tests/test_image_rotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ def test_fastpath_translate() -> None:
def test_center() -> None:
im = hopper()
rotate(im, im.mode, 45, center=(0, 0))
rotate(im, im.mode, 45, translate=(im.size[0] / 2, 0))
rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0] / 2, 0))
rotate(im, im.mode, 45, translate=(im.size[0] // 2, 0))
rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0] // 2, 0))


def test_rotate_no_fill() -> None:
Expand Down
6 changes: 6 additions & 0 deletions docs/handbook/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,12 @@ pixel, the Python Imaging Library provides different resampling *filters*.
.. py:currentmodule:: PIL.Image

.. data:: Resampling.NEAREST
:noindex:

Pick one nearest pixel from the input image. Ignore all other input pixels.

.. data:: Resampling.BOX
:noindex:

Each pixel of source image contributes to one pixel of the
destination image with identical weights.
Expand All @@ -158,13 +160,15 @@ pixel, the Python Imaging Library provides different resampling *filters*.
.. versionadded:: 3.4.0

.. data:: Resampling.BILINEAR
:noindex:

For resize calculate the output pixel value using linear interpolation
on all pixels that may contribute to the output value.
For other transformations linear interpolation over a 2x2 environment
in the input image is used.

.. data:: Resampling.HAMMING
:noindex:

Produces a sharper image than :data:`Resampling.BILINEAR`, doesn't have
dislocations on local level like with :data:`Resampling.BOX`.
Expand All @@ -174,13 +178,15 @@ pixel, the Python Imaging Library provides different resampling *filters*.
.. versionadded:: 3.4.0

.. data:: Resampling.BICUBIC
:noindex:

For resize calculate the output pixel value using cubic interpolation
on all pixels that may contribute to the output value.
For other transformations cubic interpolation over a 4x4 environment
in the input image is used.

.. data:: Resampling.LANCZOS
:noindex:

Calculate the output pixel value using a high-quality Lanczos filter (a
truncated sinc) on all pixels that may contribute to the output value.
Expand Down
11 changes: 8 additions & 3 deletions docs/reference/Image.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ Constructing images
^^^^^^^^^^^^^^^^^^^

.. autofunction:: new
.. autoclass:: SupportsArrayInterface
:show-inheritance:
.. autofunction:: fromarray
.. autofunction:: frombytes
.. autofunction:: frombuffer
Expand Down Expand Up @@ -365,6 +363,14 @@ Classes
.. autoclass:: PIL.Image.ImagePointHandler
.. autoclass:: PIL.Image.ImageTransformHandler

Protocols
---------

.. autoclass:: SupportsArrayInterface
:show-inheritance:
.. autoclass:: SupportsGetData
:show-inheritance:

Constants
---------

Expand Down Expand Up @@ -418,7 +424,6 @@ See :ref:`concept-filters` for details.
.. autoclass:: Resampling
:members:
:undoc-members:
:noindex:

Dither modes
^^^^^^^^^^^^
Expand Down
86 changes: 55 additions & 31 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,12 @@ def _getscaleoffset(expr):
# Implementation wrapper


class SupportsGetData(Protocol):
def getdata(
self,
) -> tuple[Transform, Sequence[int]]: ...


class Image:
"""
This class represents an image object. To create
Expand Down Expand Up @@ -1289,7 +1295,7 @@ def _crop(self, im, box):
return im.crop((x0, y0, x1, y1))

def draft(
self, mode: str, size: tuple[int, int]
self, mode: str | None, size: tuple[int, int]
) -> tuple[str, tuple[int, int, float, float]] | None:
"""
Configures the image file loader so it returns a version of the
Expand Down Expand Up @@ -1711,7 +1717,12 @@ def entropy(self, mask=None, extrema=None):
return self.im.entropy(extrema)
return self.im.entropy()

def paste(self, im, box=None, mask=None) -> None:
def paste(
self,
im: Image | str | float | tuple[float, ...],
box: tuple[int, int, int, int] | tuple[int, int] | None = None,
mask: Image | None = None,
) -> None:
"""
Pastes another image into this image. The box argument is either
a 2-tuple giving the upper left corner, a 4-tuple defining the
Expand Down Expand Up @@ -1739,7 +1750,7 @@ def paste(self, im, box=None, mask=None) -> None:
See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
combine images with respect to their alpha channels.

:param im: Source image or pixel value (integer or tuple).
:param im: Source image or pixel value (integer, float or tuple).
:param box: An optional 4-tuple giving the region to paste into.
If a 2-tuple is used instead, it's treated as the upper left
corner. If omitted or None, the source is pasted into the
Expand Down Expand Up @@ -2146,7 +2157,13 @@ def _get_safe_box(self, size, resample, box):
min(self.size[1], math.ceil(box[3] + support_y)),
)

def resize(self, size, resample=None, box=None, reducing_gap=None) -> Image:
def resize(
self,
size: tuple[int, int],
resample: int | None = None,
box: tuple[float, float, float, float] | None = None,
reducing_gap: float | None = None,
) -> Image:
"""
Returns a resized copy of this image.

Expand Down Expand Up @@ -2211,13 +2228,9 @@ def resize(self, size, resample=None, box=None, reducing_gap=None) -> Image:
msg = "reducing_gap must be 1.0 or greater"
raise ValueError(msg)

size = tuple(size)

self.load()
if box is None:
box = (0, 0) + self.size
else:
box = tuple(box)
Comment on lines -2214 to -2220
Copy link

@lgeiger lgeiger Jul 3, 2024

Choose a reason for hiding this comment

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

@srittau @radarhere This change ended up being a breaking change for me when upgrading from 10.3.0 to 10.4.0 since .resize() previously would accept a numpy array as size. This now fails with an internal error message:

  File "/usr/local/lib/python3.11/site-packages/PIL/Image.py", line 2297, in resize
    if self.size == size and box == (0, 0) + self.size:
       ^^^^^^^^^^^^^^^^^
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

If this change was intentional I think it would be best to raise a nice error message or revert this and relax the input type requirements.

Copy link
Member

Choose a reason for hiding this comment

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

Let's move this conversation to #8195

Copy link
Member

Choose a reason for hiding this comment

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

#8201 has now restored the ability to use a NumPy array.


if self.size == size and box == (0, 0) + self.size:
return self.copy()
Expand Down Expand Up @@ -2252,7 +2265,11 @@ def resize(self, size, resample=None, box=None, reducing_gap=None) -> Image:

return self._new(self.im.resize(size, resample, box))

def reduce(self, factor, box=None):
def reduce(
self,
factor: int | tuple[int, int],
box: tuple[int, int, int, int] | None = None,
) -> Image:
"""
Returns a copy of the image reduced ``factor`` times.
If the size of the image is not dividable by ``factor``,
Expand All @@ -2270,8 +2287,6 @@ def reduce(self, factor, box=None):

if box is None:
box = (0, 0) + self.size
else:
box = tuple(box)

if factor == (1, 1) and box == (0, 0) + self.size:
return self.copy()
Expand All @@ -2287,13 +2302,13 @@ def reduce(self, factor, box=None):

def rotate(
self,
angle,
resample=Resampling.NEAREST,
expand=0,
center=None,
translate=None,
fillcolor=None,
):
angle: float,
resample: Resampling = Resampling.NEAREST,
expand: int | bool = False,
center: tuple[int, int] | None = None,
translate: tuple[int, int] | None = None,
fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image:
"""
Returns a rotated copy of this image. This method returns a
copy of this image, rotated the given number of degrees counter
Expand Down Expand Up @@ -2600,7 +2615,12 @@ def tell(self) -> int:
"""
return 0

def thumbnail(self, size, resample=Resampling.BICUBIC, reducing_gap=2.0):
def thumbnail(
self,
size: tuple[float, float],
resample: Resampling = Resampling.BICUBIC,
reducing_gap: float = 2.0,
) -> None:
"""
Make this image into a thumbnail. This method modifies the
image to contain a thumbnail version of itself, no larger than
Expand Down Expand Up @@ -2661,20 +2681,24 @@ def round_aspect(number, key):

box = None
if reducing_gap is not None:
size = preserve_aspect_ratio()
if size is None:
preserved_size = preserve_aspect_ratio()
if preserved_size is None:
return
size = preserved_size

res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap))
res = self.draft(
None, (int(size[0] * reducing_gap), int(size[1] * reducing_gap))
)
if res is not None:
box = res[1]
if box is None:
self.load()

# load() may have changed the size of the image
size = preserve_aspect_ratio()
if size is None:
preserved_size = preserve_aspect_ratio()
if preserved_size is None:
return
size = preserved_size

if self.size != size:
im = self.resize(size, resample, box=box, reducing_gap=reducing_gap)
Expand All @@ -2690,12 +2714,12 @@ def round_aspect(number, key):
# instead of bloating the method docs, add a separate chapter.
def transform(
self,
size,
method,
data=None,
resample=Resampling.NEAREST,
fill=1,
fillcolor=None,
size: tuple[int, int],
method: Transform | ImageTransformHandler | SupportsGetData,
data: Sequence[Any] | None = None,
resample: int = Resampling.NEAREST,
fill: int = 1,
fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image:
"""
Transforms this image. This method creates a new image with the
Expand Down Expand Up @@ -2929,7 +2953,7 @@ def transform(
self,
size: tuple[int, int],
image: Image,
**options: dict[str, str | int | tuple[int, ...] | list[int]],
**options: Any,
) -> Image:
pass

Expand Down
Loading