Skip to content

Commit

Permalink
Merge pull request python-pillow#8263 from radarhere/type_hint
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk authored Jul 26, 2024
2 parents 7bd2895 + 046285a commit 8f62fbd
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 41 deletions.
1 change: 1 addition & 0 deletions Tests/test_file_iptc.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def test_getiptcinfo_fotostation() -> None:
iptc = IptcImagePlugin.getiptcinfo(im)

# Assert
assert iptc is not None
for tag in iptc.keys():
if tag[0] == 240:
return
Expand Down
14 changes: 7 additions & 7 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,40 +468,40 @@ def _getencoder(


class _E:
def __init__(self, scale, offset) -> None:
def __init__(self, scale: float, offset: float) -> None:
self.scale = scale
self.offset = offset

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

def __add__(self, other) -> _E:
def __add__(self, other: _E | float) -> _E:
if isinstance(other, _E):
return _E(self.scale + other.scale, self.offset + other.offset)
return _E(self.scale, self.offset + other)

__radd__ = __add__

def __sub__(self, other):
def __sub__(self, other: _E | float) -> _E:
return self + -other

def __rsub__(self, other):
def __rsub__(self, other: _E | float) -> _E:
return other + -self

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

__rmul__ = __mul__

def __truediv__(self, other) -> _E:
def __truediv__(self, other: _E | float) -> _E:
if isinstance(other, _E):
return NotImplemented
return _E(self.scale / other, self.offset / other)


def _getscaleoffset(expr):
def _getscaleoffset(expr) -> tuple[float, float]:
a = expr(_E(1, 0))
return (a.scale, a.offset) if isinstance(a, _E) else (0, a)

Expand Down
49 changes: 28 additions & 21 deletions src/PIL/ImageDraw2.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"""
from __future__ import annotations

from typing import BinaryIO
from typing import AnyStr, BinaryIO

from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
from ._typing import StrOrBytesPath
from ._typing import Coords, StrOrBytesPath


class Pen:
Expand Down Expand Up @@ -74,12 +74,14 @@ def __init__(
image = Image.new(image, size, color)
self.draw = ImageDraw.Draw(image)
self.image = image
self.transform = None
self.transform: tuple[float, float, float, float, float, float] | None = None

def flush(self) -> Image.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
outline = fill = None
width = 1
Expand All @@ -95,20 +97,21 @@ def render(self, op, xy, pen, brush=None):
fill = pen.color
# handle transformation
if self.transform:
xy = ImagePath.Path(xy)
xy.transform(self.transform)
path = ImagePath.Path(xy)
path.transform(self.transform)
xy = path
# render the item
if op == "line":
self.draw.line(xy, fill=outline, width=width)
else:
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."""
(xoffset, yoffset) = offset
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
angles, inside the given bounding box.
Expand All @@ -117,7 +120,7 @@ def arc(self, 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
with a straight line.
Expand All @@ -126,23 +129,23 @@ def chord(self, 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.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse`
"""
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.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line`
"""
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
center of the bounding box.
Expand All @@ -151,7 +154,7 @@ def pieslice(self, 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.
Expand All @@ -164,26 +167,29 @@ def polygon(self, xy, *options):
"""
self.render("polygon", xy, *options)

def rectangle(self, xy, *options):
def rectangle(self, xy: Coords, *options) -> None:
"""
Draws a rectangle.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle`
"""
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.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
"""
if self.transform:
xy = ImagePath.Path(xy)
xy.transform(self.transform)
path = ImagePath.Path(xy)
path.transform(self.transform)
xy = path
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.
Expand All @@ -192,11 +198,12 @@ def textbbox(self, xy, text, font):
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
"""
if self.transform:
xy = ImagePath.Path(xy)
xy.transform(self.transform)
path = ImagePath.Path(xy)
path.transform(self.transform)
xy = path
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.
This is the amount by which following text should be offset.
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/ImageQt.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
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."""
# use qRgb to pack the colors, and then turn the resulting long
# into a negative integer with the same bitpattern.
Expand Down
4 changes: 3 additions & 1 deletion src/PIL/IptcImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ def load(self) -> Image.core.PixelAccess | None:
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.
Expand Down
24 changes: 13 additions & 11 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,8 +584,10 @@ def __init__(
self.tagtype: dict[int, int] = {}
""" Dictionary of tag types """
self.reset()
(self.next,) = (
self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:])
self.next = (
self._unpack("Q", ifh[8:])[0]
if self._bigtiff
else self._unpack("L", ifh[4:])[0]
)
self._legacy_api = False

Expand Down Expand Up @@ -643,7 +645,7 @@ def __contains__(self, tag: object) -> bool:
def __setitem__(self, tag: int, value) -> None:
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)

info = TiffTags.lookup(tag, self.group)
Expand Down Expand Up @@ -731,7 +733,7 @@ def __delitem__(self, tag: int) -> None:
def __iter__(self):
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)

def _pack(self, fmt: str, *values):
Expand All @@ -755,11 +757,11 @@ def _pack(self, fmt: str, *values):
)

@_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

@_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):
data = int(data)
if isinstance(data, int):
Expand All @@ -773,7 +775,7 @@ def load_string(self, data: bytes, legacy_api: bool = True) -> str:
return data.decode("latin-1", "replace")

@_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
if isinstance(value, int):
value = str(value)
Expand All @@ -782,7 +784,7 @@ def write_string(self, value) -> bytes:
return value + b"\0"

@_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)

def combine(a, b):
Expand All @@ -797,19 +799,19 @@ def write_rational(self, *values) -> bytes:
)

@_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

@_register_writer(7)
def write_undefined(self, value) -> bytes:
def write_undefined(self, value: bytes | int | IFDRational) -> bytes:
if isinstance(value, IFDRational):
value = int(value)
if isinstance(value, int):
value = str(value).encode("ascii", "replace")
return value

@_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)

def combine(a, b):
Expand Down

0 comments on commit 8f62fbd

Please sign in to comment.