Merge pull request #8263 from radarhere/type_hint

This commit is contained in:
Hugo van Kemenade 2024-07-26 11:06:38 +03:00 committed by GitHub
commit 8f62fbdf44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 53 additions and 41 deletions

View File

@ -57,6 +57,7 @@ def test_getiptcinfo_fotostation() -> None:
iptc = IptcImagePlugin.getiptcinfo(im) iptc = IptcImagePlugin.getiptcinfo(im)
# Assert # Assert
assert iptc is not None
for tag in iptc.keys(): for tag in iptc.keys():
if tag[0] == 240: if tag[0] == 240:
return return

View File

@ -468,40 +468,40 @@ def _getencoder(
class _E: class _E:
def __init__(self, scale, offset) -> None: def __init__(self, scale: float, offset: float) -> None:
self.scale = scale self.scale = scale
self.offset = offset self.offset = offset
def __neg__(self) -> _E: def __neg__(self) -> _E:
return _E(-self.scale, -self.offset) return _E(-self.scale, -self.offset)
def __add__(self, other) -> _E: def __add__(self, other: _E | float) -> _E:
if isinstance(other, _E): if isinstance(other, _E):
return _E(self.scale + other.scale, self.offset + other.offset) return _E(self.scale + other.scale, self.offset + other.offset)
return _E(self.scale, self.offset + other) return _E(self.scale, self.offset + other)
__radd__ = __add__ __radd__ = __add__
def __sub__(self, other): def __sub__(self, other: _E | float) -> _E:
return self + -other return self + -other
def __rsub__(self, other): def __rsub__(self, other: _E | float) -> _E:
return other + -self return other + -self
def __mul__(self, other) -> _E: def __mul__(self, other: _E | float) -> _E:
if isinstance(other, _E): if isinstance(other, _E):
return NotImplemented return NotImplemented
return _E(self.scale * other, self.offset * other) return _E(self.scale * other, self.offset * other)
__rmul__ = __mul__ __rmul__ = __mul__
def __truediv__(self, other) -> _E: def __truediv__(self, other: _E | float) -> _E:
if isinstance(other, _E): if isinstance(other, _E):
return NotImplemented return NotImplemented
return _E(self.scale / other, self.offset / other) return _E(self.scale / other, self.offset / other)
def _getscaleoffset(expr): def _getscaleoffset(expr) -> tuple[float, float]:
a = expr(_E(1, 0)) a = expr(_E(1, 0))
return (a.scale, a.offset) if isinstance(a, _E) else (0, a) return (a.scale, a.offset) if isinstance(a, _E) else (0, a)

View File

@ -24,10 +24,10 @@
""" """
from __future__ import annotations from __future__ import annotations
from typing import BinaryIO from typing import AnyStr, BinaryIO
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
from ._typing import StrOrBytesPath from ._typing import Coords, StrOrBytesPath
class Pen: class Pen:
@ -74,12 +74,14 @@ class Draw:
image = Image.new(image, size, color) image = Image.new(image, size, color)
self.draw = ImageDraw.Draw(image) self.draw = ImageDraw.Draw(image)
self.image = image self.image = image
self.transform = None self.transform: tuple[float, float, float, float, float, float] | None = None
def flush(self) -> Image.Image: def flush(self) -> Image.Image:
return self.image return self.image
def render(self, op, xy, pen, brush=None): def render(
self, op: str, xy: Coords, pen: Pen | Brush, brush: Brush | Pen | None = None
) -> None:
# handle color arguments # handle color arguments
outline = fill = None outline = fill = None
width = 1 width = 1
@ -95,20 +97,21 @@ class Draw:
fill = pen.color fill = pen.color
# handle transformation # handle transformation
if self.transform: if self.transform:
xy = ImagePath.Path(xy) path = ImagePath.Path(xy)
xy.transform(self.transform) path.transform(self.transform)
xy = path
# render the item # render the item
if op == "line": if op == "line":
self.draw.line(xy, fill=outline, width=width) self.draw.line(xy, fill=outline, width=width)
else: else:
getattr(self.draw, op)(xy, fill=fill, outline=outline) getattr(self.draw, op)(xy, fill=fill, outline=outline)
def settransform(self, offset): def settransform(self, offset: tuple[float, float]) -> None:
"""Sets a transformation offset.""" """Sets a transformation offset."""
(xoffset, yoffset) = offset (xoffset, yoffset) = offset
self.transform = (1, 0, xoffset, 0, 1, yoffset) self.transform = (1, 0, xoffset, 0, 1, yoffset)
def arc(self, xy, start, end, *options): def arc(self, xy: Coords, start, end, *options) -> None:
""" """
Draws an arc (a portion of a circle outline) between the start and end Draws an arc (a portion of a circle outline) between the start and end
angles, inside the given bounding box. angles, inside the given bounding box.
@ -117,7 +120,7 @@ class Draw:
""" """
self.render("arc", xy, start, end, *options) self.render("arc", xy, start, end, *options)
def chord(self, xy, start, end, *options): def chord(self, xy: Coords, start, end, *options) -> None:
""" """
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
with a straight line. with a straight line.
@ -126,7 +129,7 @@ class Draw:
""" """
self.render("chord", xy, start, end, *options) self.render("chord", xy, start, end, *options)
def ellipse(self, xy, *options): def ellipse(self, xy: Coords, *options) -> None:
""" """
Draws an ellipse inside the given bounding box. Draws an ellipse inside the given bounding box.
@ -134,7 +137,7 @@ class Draw:
""" """
self.render("ellipse", xy, *options) self.render("ellipse", xy, *options)
def line(self, xy, *options): def line(self, xy: Coords, *options) -> None:
""" """
Draws a line between the coordinates in the ``xy`` list. Draws a line between the coordinates in the ``xy`` list.
@ -142,7 +145,7 @@ class Draw:
""" """
self.render("line", xy, *options) self.render("line", xy, *options)
def pieslice(self, xy, start, end, *options): def pieslice(self, xy: Coords, start, end, *options) -> None:
""" """
Same as arc, but also draws straight lines between the end points and the Same as arc, but also draws straight lines between the end points and the
center of the bounding box. center of the bounding box.
@ -151,7 +154,7 @@ class Draw:
""" """
self.render("pieslice", xy, start, end, *options) self.render("pieslice", xy, start, end, *options)
def polygon(self, xy, *options): def polygon(self, xy: Coords, *options) -> None:
""" """
Draws a polygon. Draws a polygon.
@ -164,7 +167,7 @@ class Draw:
""" """
self.render("polygon", xy, *options) self.render("polygon", xy, *options)
def rectangle(self, xy, *options): def rectangle(self, xy: Coords, *options) -> None:
""" """
Draws a rectangle. Draws a rectangle.
@ -172,18 +175,21 @@ class Draw:
""" """
self.render("rectangle", xy, *options) self.render("rectangle", xy, *options)
def text(self, xy, text, font): def text(self, xy: tuple[float, float], text: AnyStr, font: Font) -> None:
""" """
Draws the string at the given position. Draws the string at the given position.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text` .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
""" """
if self.transform: if self.transform:
xy = ImagePath.Path(xy) path = ImagePath.Path(xy)
xy.transform(self.transform) path.transform(self.transform)
xy = path
self.draw.text(xy, text, font=font.font, fill=font.color) self.draw.text(xy, text, font=font.font, fill=font.color)
def textbbox(self, xy, text, font): def textbbox(
self, xy: tuple[float, float], text: AnyStr, font: Font
) -> tuple[float, float, float, float]:
""" """
Returns bounding box (in pixels) of given text. Returns bounding box (in pixels) of given text.
@ -192,11 +198,12 @@ class Draw:
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox` .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
""" """
if self.transform: if self.transform:
xy = ImagePath.Path(xy) path = ImagePath.Path(xy)
xy.transform(self.transform) path.transform(self.transform)
xy = path
return self.draw.textbbox(xy, text, font=font.font) return self.draw.textbbox(xy, text, font=font.font)
def textlength(self, text, font): def textlength(self, text: AnyStr, font: Font) -> float:
""" """
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.

View File

@ -58,7 +58,7 @@ else:
qt_version = None qt_version = None
def rgb(r, g, b, a=255): def rgb(r: int, g: int, b: int, a: int = 255) -> int:
"""(Internal) Turns an RGB color into a Qt compatible color integer.""" """(Internal) Turns an RGB color into a Qt compatible color integer."""
# use qRgb to pack the colors, and then turn the resulting long # use qRgb to pack the colors, and then turn the resulting long
# into a negative integer with the same bitpattern. # into a negative integer with the same bitpattern.

View File

@ -185,7 +185,9 @@ Image.register_open(IptcImageFile.format, IptcImageFile)
Image.register_extension(IptcImageFile.format, ".iim") Image.register_extension(IptcImageFile.format, ".iim")
def getiptcinfo(im: ImageFile.ImageFile): def getiptcinfo(
im: ImageFile.ImageFile,
) -> dict[tuple[int, int], bytes | list[bytes]] | None:
""" """
Get IPTC information from TIFF, JPEG, or IPTC file. Get IPTC information from TIFF, JPEG, or IPTC file.

View File

@ -584,8 +584,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
self.tagtype: dict[int, int] = {} self.tagtype: dict[int, int] = {}
""" Dictionary of tag types """ """ Dictionary of tag types """
self.reset() self.reset()
(self.next,) = ( self.next = (
self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:]) self._unpack("Q", ifh[8:])[0]
if self._bigtiff
else self._unpack("L", ifh[4:])[0]
) )
self._legacy_api = False self._legacy_api = False
@ -643,7 +645,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
def __setitem__(self, tag: int, value) -> None: def __setitem__(self, tag: int, value) -> None:
self._setitem(tag, value, self.legacy_api) self._setitem(tag, value, self.legacy_api)
def _setitem(self, tag, value, legacy_api) -> None: def _setitem(self, tag: int, value, legacy_api: bool) -> None:
basetypes = (Number, bytes, str) basetypes = (Number, bytes, str)
info = TiffTags.lookup(tag, self.group) info = TiffTags.lookup(tag, self.group)
@ -731,7 +733,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
def __iter__(self): def __iter__(self):
return iter(set(self._tagdata) | set(self._tags_v2)) return iter(set(self._tagdata) | set(self._tags_v2))
def _unpack(self, fmt: str, data): def _unpack(self, fmt: str, data: bytes):
return struct.unpack(self._endian + fmt, data) return struct.unpack(self._endian + fmt, data)
def _pack(self, fmt: str, *values): def _pack(self, fmt: str, *values):
@ -755,11 +757,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
) )
@_register_loader(1, 1) # Basic type, except for the legacy API. @_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data, legacy_api: bool = True): def load_byte(self, data: bytes, legacy_api: bool = True) -> bytes:
return data return data
@_register_writer(1) # Basic type, except for the legacy API. @_register_writer(1) # Basic type, except for the legacy API.
def write_byte(self, data) -> bytes: def write_byte(self, data: bytes | int | IFDRational) -> bytes:
if isinstance(data, IFDRational): if isinstance(data, IFDRational):
data = int(data) data = int(data)
if isinstance(data, int): if isinstance(data, int):
@ -773,7 +775,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return data.decode("latin-1", "replace") return data.decode("latin-1", "replace")
@_register_writer(2) @_register_writer(2)
def write_string(self, value) -> bytes: def write_string(self, value: str | bytes | int) -> bytes:
# remerge of https://github.com/python-pillow/Pillow/pull/1416 # remerge of https://github.com/python-pillow/Pillow/pull/1416
if isinstance(value, int): if isinstance(value, int):
value = str(value) value = str(value)
@ -782,7 +784,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value + b"\0" return value + b"\0"
@_register_loader(5, 8) @_register_loader(5, 8)
def load_rational(self, data, legacy_api=True): def load_rational(self, data, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}L", data) vals = self._unpack(f"{len(data) // 4}L", data)
def combine(a, b): def combine(a, b):
@ -797,11 +799,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
) )
@_register_loader(7, 1) @_register_loader(7, 1)
def load_undefined(self, data, legacy_api: bool = True): def load_undefined(self, data: bytes, legacy_api: bool = True) -> bytes:
return data return data
@_register_writer(7) @_register_writer(7)
def write_undefined(self, value) -> bytes: def write_undefined(self, value: bytes | int | IFDRational) -> bytes:
if isinstance(value, IFDRational): if isinstance(value, IFDRational):
value = int(value) value = int(value)
if isinstance(value, int): if isinstance(value, int):
@ -809,7 +811,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value return value
@_register_loader(10, 8) @_register_loader(10, 8)
def load_signed_rational(self, data, legacy_api: bool = True): def load_signed_rational(self, data: bytes, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}l", data) vals = self._unpack(f"{len(data) // 4}l", data)
def combine(a, b): def combine(a, b):