Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add various type annotations #8046

Merged
merged 21 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
10 changes: 8 additions & 2 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
82 changes: 55 additions & 27 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[Any]]: ...


class Image:
"""
This class represents an image object. To create
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 | int | tuple[int, ...],
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 @@ -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,13 @@ 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)
size = cast("tuple[int, int]", tuple(size))

self.load()
if box is None:
box = (0, 0) + self.size
else:
box = tuple(box)
box = cast("tuple[float, float, float, float]", tuple(box))

if self.size == size and box == (0, 0) + self.size:
return self.copy()
Expand Down Expand Up @@ -2252,7 +2269,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 @@ -2271,7 +2292,7 @@ def reduce(self, factor, box=None):
if box is None:
box = (0, 0) + self.size
else:
box = tuple(box)
box = cast("tuple[int, int, int, int]", tuple(box))

if factor == (1, 1) and box == (0, 0) + self.size:
return self.copy()
Expand All @@ -2287,13 +2308,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: int = Resampling.NEAREST,
expand: 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 +2621,12 @@ def tell(self) -> int:
"""
return 0

def thumbnail(self, size, resample=Resampling.BICUBIC, reducing_gap=2.0):
def thumbnail(
self,
size: tuple[int, int],
resample: int = 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 +2687,22 @@ 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, (size[0] * reducing_gap, size[1] * reducing_gap)) # type: ignore[arg-type]
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 +2718,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 @@ -2771,7 +2799,7 @@ def getdata(self):
im.info = self.info.copy()
if method == Transform.MESH:
# list of quads
for box, quad in data:
for box, quad in cast("Sequence[tuple[float, float]]", data):
im.__transformer(
box, self, Transform.QUAD, quad, resample, fillcolor is None
)
Expand Down Expand Up @@ -2929,7 +2957,7 @@ def transform(
self,
size: tuple[int, int],
image: Image,
**options: dict[str, str | int | tuple[int, ...] | list[int]],
**options: dict[str, str | int | tuple[int, ...] | list[int]] | int,
) -> Image:
pass

Expand Down
36 changes: 19 additions & 17 deletions src/PIL/ImageDraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import math
import numbers
import struct
from typing import TYPE_CHECKING, Sequence, cast
from typing import TYPE_CHECKING, AnyStr, Sequence, cast

from . import Image, ImageColor
from ._typing import Coords
Expand Down Expand Up @@ -120,7 +120,9 @@ def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
self.font = ImageFont.load_default()
return self.font

def _getfont(self, font_size: float | None):
def _getfont(
self, font_size: float | None
) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
if font_size is not None:
from . import ImageFont

Expand Down Expand Up @@ -453,13 +455,13 @@ def draw_corners(pieslice) -> None:
right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 1)

def _multiline_check(self, text) -> bool:
split_character = "\n" if isinstance(text, str) else b"\n"
def _multiline_check(self, text: AnyStr) -> bool:
split_character = cast(AnyStr, "\n" if isinstance(text, str) else b"\n")

return split_character in text

def _multiline_split(self, text) -> list[str | bytes]:
split_character = "\n" if isinstance(text, str) else b"\n"
def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
split_character = cast(AnyStr, "\n" if isinstance(text, str) else b"\n")

return text.split(split_character)

Expand All @@ -472,10 +474,10 @@ def _multiline_spacing(self, font, spacing, stroke_width):

def text(
self,
xy,
text,
xy: tuple[float, float],
text: str,
fill=None,
font=None,
font: ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
anchor=None,
spacing=4,
align="left",
Expand Down Expand Up @@ -529,7 +531,7 @@ def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
coord.append(int(xy[i]))
start.append(math.modf(xy[i])[0])
try:
mask, offset = font.getmask2(
mask, offset = font.getmask2( # type: ignore[union-attr,misc]
text,
mode,
direction=direction,
Expand All @@ -545,7 +547,7 @@ def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
coord = [coord[0] + offset[0], coord[1] + offset[1]]
except AttributeError:
try:
mask = font.getmask(
mask = font.getmask( # type: ignore[misc]
text,
mode,
direction,
Expand Down Expand Up @@ -594,7 +596,7 @@ def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:

def multiline_text(
self,
xy,
xy: tuple[float, float],
text,
fill=None,
font=None,
Expand Down Expand Up @@ -627,7 +629,7 @@ def multiline_text(
font = self._getfont(font_size)

widths = []
max_width = 0
max_width: float = 0
lines = self._multiline_split(text)
line_spacing = self._multiline_spacing(font, spacing, stroke_width)
for line in lines:
Expand Down Expand Up @@ -681,15 +683,15 @@ def multiline_text(

def textlength(
self,
text,
font=None,
text: str,
font: ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
direction=None,
features=None,
language=None,
embedded_color=False,
*,
font_size=None,
):
) -> float:
"""Get the length of a given string, in pixels with 1/64 precision."""
if self._multiline_check(text):
msg = "can't measure length of multiline text"
Expand Down Expand Up @@ -781,7 +783,7 @@ def multiline_textbbox(
font = self._getfont(font_size)

widths = []
max_width = 0
max_width: float = 0
lines = self._multiline_split(text)
line_spacing = self._multiline_spacing(font, spacing, stroke_width)
for line in lines:
Expand Down
Loading
Loading