Add various type annotations

This commit is contained in:
Sebastian Rittau 2024-05-07 14:30:34 +02:00
parent 93ca52fbe0
commit c92f59d758
4 changed files with 104 additions and 41 deletions

View File

@ -41,7 +41,7 @@ import warnings
from collections.abc import Callable, MutableMapping from collections.abc import Callable, MutableMapping
from enum import IntEnum from enum import IntEnum
from types import ModuleType from types import ModuleType
from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, cast from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, cast, overload
# VERSION was removed in Pillow 6.0.0. # VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION was removed in Pillow 9.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0.
@ -481,6 +481,8 @@ def _getscaleoffset(expr):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Implementation wrapper # Implementation wrapper
class _GetDataTransform(Protocol):
def getdata(self) -> tuple[Transform, Sequence[int]]: ...
class Image: class Image:
""" """
@ -1687,7 +1689,7 @@ class Image:
return self.im.entropy(extrema) return self.im.entropy(extrema)
return self.im.entropy() 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 Pastes another image into this image. The box argument is either
a 2-tuple giving the upper left corner, a 4-tuple defining the a 2-tuple giving the upper left corner, a 4-tuple defining the
@ -2122,7 +2124,7 @@ class Image:
min(self.size[1], math.ceil(box[3] + support_y)), 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: Resampling | None = None, box: tuple[float, float, float, float] | None = None, reducing_gap: float | None = None) -> Image:
""" """
Returns a resized copy of this image. Returns a resized copy of this image.
@ -2228,7 +2230,7 @@ class Image:
return self._new(self.im.resize(size, resample, box)) 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. Returns a copy of the image reduced ``factor`` times.
If the size of the image is not dividable by ``factor``, If the size of the image is not dividable by ``factor``,
@ -2263,13 +2265,13 @@ class Image:
def rotate( def rotate(
self, self,
angle, angle: float,
resample=Resampling.NEAREST, resample: Resampling = Resampling.NEAREST,
expand=0, expand: bool = False,
center=None, center: tuple[int, int] | None = None,
translate=None, translate: tuple[int, int] | None = None,
fillcolor=None, fillcolor: float | tuple[float, ...] | str | None = None,
): ) -> Image:
""" """
Returns a rotated copy of this image. This method returns a Returns a rotated copy of this image. This method returns a
copy of this image, rotated the given number of degrees counter copy of this image, rotated the given number of degrees counter
@ -2576,7 +2578,7 @@ class Image:
""" """
return 0 return 0
def thumbnail(self, size, resample=Resampling.BICUBIC, reducing_gap=2.0): def thumbnail(self, size: tuple[int, int], resample: Resampling = Resampling.BICUBIC, reducing_gap: float = 2.0) -> None:
""" """
Make this image into a thumbnail. This method modifies the Make this image into a thumbnail. This method modifies the
image to contain a thumbnail version of itself, no larger than image to contain a thumbnail version of itself, no larger than
@ -2664,14 +2666,34 @@ class Image:
# FIXME: the different transform methods need further explanation # FIXME: the different transform methods need further explanation
# instead of bloating the method docs, add a separate chapter. # instead of bloating the method docs, add a separate chapter.
@overload
def transform( def transform(
self, self,
size, size: tuple[int, int],
method, method: Transform | ImageTransformHandler,
data=None, data: Sequence[int],
resample=Resampling.NEAREST, resample: Resampling = Resampling.NEAREST,
fill=1, fill: int = 1,
fillcolor=None, fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image: ...
@overload
def transform(
self,
size: tuple[int, int],
method: _GetDataTransform,
data: None = None,
resample: Resampling = Resampling.NEAREST,
fill: int = 1,
fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image: ...
def transform(
self,
size: tuple[int, int],
method: Transform | ImageTransformHandler | _GetDataTransform,
data: Sequence[int] | None = None,
resample: Resampling = Resampling.NEAREST,
fill: int = 1,
fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image: ) -> Image:
""" """
Transforms this image. This method creates a new image with the Transforms this image. This method creates a new image with the

View File

@ -34,10 +34,11 @@ from __future__ import annotations
import math import math
import numbers import numbers
import struct import struct
from typing import Sequence, cast from typing import AnyStr, Sequence, cast
from . import Image, ImageColor from . import Image, ImageColor
from ._typing import Coords from ._typing import Coords
from .ImageFont import FreeTypeFont, ImageFont
""" """
A simple 2D drawing interface for PIL images. A simple 2D drawing interface for PIL images.
@ -92,7 +93,7 @@ class ImageDraw:
self.fontmode = "L" # aliasing is okay for other modes self.fontmode = "L" # aliasing is okay for other modes
self.fill = False self.fill = False
def getfont(self): def getfont(self) -> FreeTypeFont | ImageFont:
""" """
Get the current default font. Get the current default font.
@ -450,12 +451,12 @@ class ImageDraw:
right[3] -= r + 1 right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 1) self.draw.draw_rectangle(right, ink, 1)
def _multiline_check(self, text) -> bool: def _multiline_check(self, text: str | bytes) -> bool:
split_character = "\n" if isinstance(text, str) else b"\n" split_character = "\n" if isinstance(text, str) else b"\n"
return split_character in text return split_character in text
def _multiline_split(self, text) -> list[str | bytes]: def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
split_character = "\n" if isinstance(text, str) else b"\n" split_character = "\n" if isinstance(text, str) else b"\n"
return text.split(split_character) return text.split(split_character)
@ -469,7 +470,7 @@ class ImageDraw:
def text( def text(
self, self,
xy, xy: tuple[int, int],
text, text,
fill=None, fill=None,
font=None, font=None,
@ -591,7 +592,7 @@ class ImageDraw:
def multiline_text( def multiline_text(
self, self,
xy, xy: tuple[int, int],
text, text,
fill=None, fill=None,
font=None, font=None,
@ -678,15 +679,15 @@ class ImageDraw:
def textlength( def textlength(
self, self,
text, text: str,
font=None, font: FreeTypeFont | ImageFont | None = None,
direction=None, direction=None,
features=None, features=None,
language=None, language=None,
embedded_color=False, embedded_color=False,
*, *,
font_size=None, font_size=None,
): ) -> float:
"""Get the length of a given string, in pixels with 1/64 precision.""" """Get the length of a given string, in pixels with 1/64 precision."""
if self._multiline_check(text): if self._multiline_check(text):
msg = "can't measure length of multiline text" msg = "can't measure length of multiline text"

View File

@ -33,12 +33,15 @@ import sys
import warnings import warnings
from enum import IntEnum from enum import IntEnum
from io import BytesIO from io import BytesIO
from typing import BinaryIO from typing import TYPE_CHECKING, BinaryIO
from . import Image from . import Image
from ._typing import StrOrBytesPath from ._typing import StrOrBytesPath
from ._util import is_directory, is_path from ._util import is_directory, is_path
if TYPE_CHECKING:
from _imagingft import Font
class Layout(IntEnum): class Layout(IntEnum):
BASIC = 0 BASIC = 0
@ -56,7 +59,7 @@ except ImportError as ex:
core = DeferredError.new(ex) core = DeferredError.new(ex)
def _string_length_check(text): def _string_length_check(text: str | bytes) -> None:
if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH: if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH:
msg = "too many characters in string" msg = "too many characters in string"
raise ValueError(msg) raise ValueError(msg)
@ -81,7 +84,9 @@ def _string_length_check(text):
class ImageFont: class ImageFont:
"""PIL font wrapper""" """PIL font wrapper"""
def _load_pilfont(self, filename): font: Font
def _load_pilfont(self, filename: str) -> None:
with open(filename, "rb") as fp: with open(filename, "rb") as fp:
image = None image = None
for ext in (".png", ".gif", ".pbm"): for ext in (".png", ".gif", ".pbm"):
@ -153,7 +158,7 @@ class ImageFont:
Image._decompression_bomb_check(self.font.getsize(text)) Image._decompression_bomb_check(self.font.getsize(text))
return self.font.getmask(text, mode) return self.font.getmask(text, mode)
def getbbox(self, text, *args, **kwargs): def getbbox(self, text: str, *args: object, **kwargs: object) -> tuple[int, int, int, int]:
""" """
Returns bounding box (in pixels) of given text. Returns bounding box (in pixels) of given text.
@ -171,7 +176,7 @@ class ImageFont:
width, height = self.font.getsize(text) width, height = self.font.getsize(text)
return 0, 0, width, height return 0, 0, width, height
def getlength(self, text, *args, **kwargs): def getlength(self, text: str, *args: object, **kwargs: object) -> int:
""" """
Returns length (in pixels) of given text. Returns length (in pixels) of given text.
This is the amount by which following text should be offset. This is the amount by which following text should be offset.
@ -254,7 +259,7 @@ class FreeTypeFont:
path, size, index, encoding, layout_engine = state path, size, index, encoding, layout_engine = state
self.__init__(path, size, index, encoding, layout_engine) self.__init__(path, size, index, encoding, layout_engine)
def getname(self): def getname(self) -> tuple[str, str]:
""" """
:return: A tuple of the font family (e.g. Helvetica) and the font style :return: A tuple of the font family (e.g. Helvetica) and the font style
(e.g. Bold) (e.g. Bold)
@ -269,7 +274,7 @@ class FreeTypeFont:
""" """
return self.font.ascent, self.font.descent return self.font.ascent, self.font.descent
def getlength(self, text, mode="", direction=None, features=None, language=None): def getlength(self, text: str, mode="", direction=None, features=None, language=None) -> float:
""" """
Returns length (in pixels with 1/64 precision) of given text when rendered Returns length (in pixels with 1/64 precision) of given text when rendered
in font with provided direction, features, and language. in font with provided direction, features, and language.
@ -343,14 +348,14 @@ class FreeTypeFont:
def getbbox( def getbbox(
self, self,
text, text: str,
mode="", mode="",
direction=None, direction=None,
features=None, features=None,
language=None, language=None,
stroke_width=0, stroke_width=0,
anchor=None, anchor=None,
): ) -> tuple[int, int, int, int]:
""" """
Returns bounding box (in pixels) of given text relative to given anchor Returns bounding box (in pixels) of given text relative to given anchor
when rendered in font with provided direction, features, and language. when rendered in font with provided direction, features, and language.
@ -725,7 +730,7 @@ class TransposedFont:
return self.font.getlength(text, *args, **kwargs) return self.font.getlength(text, *args, **kwargs)
def load(filename): def load(filename: str) -> ImageFont:
""" """
Load a font file. This function loads a font object from the given Load a font file. This function loads a font object from the given
bitmap font file, and returns the corresponding font object. bitmap font file, and returns the corresponding font object.
@ -739,7 +744,7 @@ def load(filename):
return f return f
def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): def truetype(font: StrOrBytesPath | BinaryIO | None = None, size: float = 10, index: int = 0, encoding: str = "", layout_engine: Layout | None = None) -> FreeTypeFont:
""" """
Load a TrueType or OpenType font from a file or file-like object, Load a TrueType or OpenType font from a file or file-like object,
and create a font object. and create a font object.
@ -800,7 +805,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
:exception ValueError: If the font size is not greater than zero. :exception ValueError: If the font size is not greater than zero.
""" """
def freetype(font): def freetype(font: StrOrBytesPath | BinaryIO | None) -> FreeTypeFont:
return FreeTypeFont(font, size, index, encoding, layout_engine) return FreeTypeFont(font, size, index, encoding, layout_engine)
try: try:
@ -850,7 +855,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
raise raise
def load_path(filename): def load_path(filename: str | bytes) -> ImageFont:
""" """
Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a
bitmap font along the Python path. bitmap font along the Python path.

View File

@ -1,3 +1,38 @@
from typing import Any from typing import Any, TypedDict
class _Axis(TypedDict):
minimum: int | None
default: int | None
maximum: int | None
name: str | None
class Font:
@property
def family(self) -> str | None: ...
@property
def style(self) -> str | None: ...
@property
def ascent(self) -> int: ...
@property
def descent(self) -> int: ...
@property
def height(self) -> int: ...
@property
def x_ppem(self) -> int: ...
@property
def y_ppem(self) -> int: ...
@property
def glyphs(self) -> int: ...
def render(self, string: str, fill, mode = ..., dir = ..., features = ..., lang = ..., stroke_width = ..., anchor = ..., foreground_ink_long = ..., x_start = ..., y_start = ..., /) -> tuple[Any, tuple[int, int]]: ...
def getsize(self, string: str, mode = ..., dir = ..., features = ..., lang = ..., anchor = ..., /) -> tuple[tuple[int, int], tuple[int, int]]: ...
def getlength(self, string: str, mode = ..., dir = ..., features = ..., lang = ..., /) -> int: ...
def getvarnames(self) -> list[str]: ...
def getvaraxes(self) -> list[_Axis]: ...
def setvarname(self, instance_index: int, /) -> None: ...
def setvaraxes(self, axes: list[float], /) -> None: ...
def getfont(filename: str | bytes | bytearray, size, index = ..., encoding = ..., font_bytes = ..., layout_engine = ...) -> Font: ...
def __getattr__(name: str) -> Any: ... def __getattr__(name: str) -> Any: ...