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

Added type hints #8262

Merged
merged 1 commit into from
Jul 25, 2024
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
3 changes: 2 additions & 1 deletion Tests/test_font_pcf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import os
from pathlib import Path
from typing import AnyStr

import pytest

Expand Down Expand Up @@ -92,7 +93,7 @@ def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None:


def _test_high_characters(
request: pytest.FixtureRequest, tmp_path: Path, message: str | bytes
request: pytest.FixtureRequest, tmp_path: Path, message: AnyStr
) -> None:
tempname = save_font(request, tmp_path)
font = ImageFont.load(tempname)
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_imagefont.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,14 +717,14 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:

font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
_check_text(font, "Tests/images/variation_adobe.png", 11)
for name in ["Bold", b"Bold"]:
for name in ("Bold", b"Bold"):
font.set_variation_by_name(name)
assert font.getname()[1] == "Bold"
_check_text(font, "Tests/images/variation_adobe_name.png", 16)

font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
_check_text(font, "Tests/images/variation_tiny.png", 40)
for name in ["200", b"200"]:
for name in ("200", b"200"):
font.set_variation_by_name(name)
assert font.getname()[1] == "200"
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/ImageFont.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,11 @@ Constants
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
raise a :py:exc:`ValueError` if the number of characters is over this limit. The
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.

Dictionaries
------------

.. autoclass:: Axis
:members:
:undoc-members:
:show-inheritance:
4 changes: 4 additions & 0 deletions docs/reference/internal_modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,7 @@ on some Python versions.

An internal interface module previously known as :mod:`~PIL._imaging`,
implemented in :file:`_imaging.c`.

.. py:class:: ImagingCore

A representation of the image data.
2 changes: 0 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,4 @@ exclude = [
'^Tests/oss-fuzz/fuzz_font.py$',
'^Tests/oss-fuzz/fuzz_pillow.py$',
'^Tests/test_qt_image_qapplication.py$',
'^Tests/test_font_pcf_charsets.py$',
'^Tests/test_font_pcf.py$',
]
68 changes: 41 additions & 27 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ class Quantize(IntEnum):
# Registries

if TYPE_CHECKING:
from xml.etree.ElementTree import Element

from . import ImageFile, ImagePalette
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
ID: list[str] = []
Expand All @@ -241,9 +243,9 @@ class Quantize(IntEnum):
_ENDIAN = "<" if sys.byteorder == "little" else ">"


def _conv_type_shape(im):
def _conv_type_shape(im: Image) -> tuple[tuple[int, ...], str]:
m = ImageMode.getmode(im.mode)
shape = (im.height, im.width)
shape: tuple[int, ...] = (im.height, im.width)
extra = len(m.bands)
if extra != 1:
shape += (extra,)
Expand Down Expand Up @@ -470,10 +472,10 @@ def __init__(self, scale, offset) -> None:
self.scale = scale
self.offset = offset

def __neg__(self):
def __neg__(self) -> _E:
return _E(-self.scale, -self.offset)

def __add__(self, other):
def __add__(self, other) -> _E:
if isinstance(other, _E):
return _E(self.scale + other.scale, self.offset + other.offset)
return _E(self.scale, self.offset + other)
Expand All @@ -486,14 +488,14 @@ def __sub__(self, other):
def __rsub__(self, other):
return other + -self

def __mul__(self, other):
def __mul__(self, other) -> _E:
if isinstance(other, _E):
return NotImplemented
return _E(self.scale * other, self.offset * other)

__rmul__ = __mul__

def __truediv__(self, other):
def __truediv__(self, other) -> _E:
if isinstance(other, _E):
return NotImplemented
return _E(self.scale / other, self.offset / other)
Expand Down Expand Up @@ -718,9 +720,9 @@ def _repr_jpeg_(self) -> bytes | None:
return self._repr_image("JPEG")

@property
def __array_interface__(self):
def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]:
# numpy array interface support
new = {"version": 3}
new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3}
try:
if self.mode == "1":
# Binary images need to be extended from bits to bytes
Expand Down Expand Up @@ -1418,7 +1420,7 @@ def getcolors(
return out
return self.im.getcolors(maxcolors)

def getdata(self, band: int | None = None):
def getdata(self, band: int | None = None) -> core.ImagingCore:
"""
Returns the contents of this image as a sequence object
containing pixel values. The sequence object is flattened, so
Expand Down Expand Up @@ -1467,8 +1469,8 @@ def getxmp(self) -> dict[str, Any]:
def get_name(tag: str) -> str:
return re.sub("^{[^}]+}", "", tag)

def get_value(element):
value = {get_name(k): v for k, v in element.attrib.items()}
def get_value(element: Element) -> str | dict[str, Any] | None:
value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()}
children = list(element)
if children:
for child in children:
Expand Down Expand Up @@ -1712,7 +1714,7 @@ def histogram(self, mask: Image | None = None, extrema=None) -> list[int]:
return self.im.histogram(extrema)
return self.im.histogram()

def entropy(self, mask=None, extrema=None):
def entropy(self, mask: Image | None = None, extrema=None):
"""
Calculates and returns the entropy for the image.

Expand Down Expand Up @@ -1996,7 +1998,7 @@ def putalpha(self, alpha: Image | int) -> None:

def putdata(
self,
data: Sequence[float] | Sequence[Sequence[int]] | NumpyArray,
data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray,
scale: float = 1.0,
offset: float = 0.0,
) -> None:
Expand Down Expand Up @@ -2184,7 +2186,12 @@ def remap_palette(

return m_im

def _get_safe_box(self, size, resample, box):
def _get_safe_box(
self,
size: tuple[int, int],
resample: Resampling,
box: tuple[float, float, float, float],
) -> tuple[int, int, int, int]:
"""Expands the box so it includes adjacent pixels
that may be used by resampling with the given resampling filter.
"""
Expand Down Expand Up @@ -2294,7 +2301,7 @@ def resize(
factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1
factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
if factor_x > 1 or factor_y > 1:
reduce_box = self._get_safe_box(size, resample, box)
reduce_box = self._get_safe_box(size, cast(Resampling, resample), box)
factor = (factor_x, factor_y)
self = (
self.reduce(factor, box=reduce_box)
Expand Down Expand Up @@ -2430,7 +2437,7 @@ def rotate(
0.0,
]

def transform(x, y, matrix):
def transform(x: float, y: float, matrix: list[float]) -> tuple[float, float]:
(a, b, c, d, e, f) = matrix
return a * x + b * y + c, d * x + e * y + f

Expand All @@ -2445,9 +2452,9 @@ def transform(x, y, matrix):
xx = []
yy = []
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
x, y = transform(x, y, matrix)
xx.append(x)
yy.append(y)
transformed_x, transformed_y = transform(x, y, matrix)
xx.append(transformed_x)
yy.append(transformed_y)
nw = math.ceil(max(xx)) - math.floor(min(xx))
nh = math.ceil(max(yy)) - math.floor(min(yy))

Expand Down Expand Up @@ -2705,7 +2712,7 @@ def thumbnail(
provided_size = tuple(map(math.floor, size))

def preserve_aspect_ratio() -> tuple[int, int] | None:
def round_aspect(number, key):
def round_aspect(number: float, key: Callable[[int], float]) -> int:
return max(min(math.floor(number), math.ceil(number), key=key), 1)

x, y = provided_size
Expand Down Expand Up @@ -2849,7 +2856,13 @@ def getdata(self):
return im

def __transformer(
self, box, image, method, data, resample=Resampling.NEAREST, fill=1
self,
box: tuple[int, int, int, int],
image: Image,
method,
data,
resample: int = Resampling.NEAREST,
fill: bool = True,
):
w = box[2] - box[0]
h = box[3] - box[1]
Expand Down Expand Up @@ -2899,11 +2912,12 @@ def __transformer(
Resampling.BICUBIC,
):
if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
msg = {
unusable: dict[int, str] = {
Resampling.BOX: "Image.Resampling.BOX",
Resampling.HAMMING: "Image.Resampling.HAMMING",
Resampling.LANCZOS: "Image.Resampling.LANCZOS",
}[resample] + f" ({resample}) cannot be used."
}
msg = unusable[resample] + f" ({resample}) cannot be used."
else:
msg = f"Unknown resampling filter ({resample})."

Expand Down Expand Up @@ -3843,7 +3857,7 @@ class Exif(_ExifBase):
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
"""

endian = None
endian: str | None = None
bigtiff = False
_loaded = False

Expand Down Expand Up @@ -3892,7 +3906,7 @@ def _get_head(self) -> bytes:
head += b"\x00\x00\x00\x00"
return head

def load(self, data):
def load(self, data: bytes) -> None:
# Extract EXIF information. This is highly experimental,
# and is likely to be replaced with something better in a future
# version.
Expand All @@ -3911,7 +3925,7 @@ def load(self, data):
self._info = None
return

self.fp = io.BytesIO(data)
self.fp: IO[bytes] = io.BytesIO(data)
self.head = self.fp.read(8)
# process dictionary
from . import TiffImagePlugin
Expand All @@ -3921,7 +3935,7 @@ def load(self, data):
self.fp.seek(self._info.next)
self._info.load(self.fp)

def load_from_fp(self, fp, offset=None):
def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None:
self._loaded_exif = None
self._data.clear()
self._hidden_data.clear()
Expand Down
Loading
Loading