Skip to content

Commit

Permalink
Merge pull request #8262 from radarhere/type_hint
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk authored Jul 25, 2024
2 parents 6dd4b3c + 726cdf5 commit 7bd2895
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 158 deletions.
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

0 comments on commit 7bd2895

Please sign in to comment.