Added type hints

This commit is contained in:
Andrew Murray 2024-05-18 16:06:50 +10:00
parent 8c7be25ad0
commit 8a3a72e51d
21 changed files with 135 additions and 94 deletions

View File

@ -57,6 +57,10 @@ Classes
:undoc-members:
:show-inheritance:
.. autoclass:: PIL.ImageFile.StubHandler()
:members:
:show-inheritance:
.. autoclass:: PIL.ImageFile.StubImageFile()
:members:
:show-inheritance:

View File

@ -55,7 +55,7 @@ class AlphaEncoding(IntEnum):
DXT5 = 7
def unpack_565(i):
def unpack_565(i: int) -> tuple[int, int, int]:
return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3
@ -284,7 +284,8 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
raise OSError(msg) from e
return -1, 0
def _read_blp_header(self):
def _read_blp_header(self) -> None:
assert self.fd is not None
self.fd.seek(4)
(self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
@ -303,10 +304,10 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
def _safe_read(self, length):
def _safe_read(self, length: int) -> bytes:
return ImageFile._safe_read(self.fd, length)
def _read_palette(self):
def _read_palette(self) -> list[tuple[int, int, int, int]]:
ret = []
for i in range(256):
try:
@ -349,29 +350,30 @@ class BLP1Decoder(_BLPBaseDecoder):
msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
raise BLPFormatError(msg)
def _decode_jpeg_stream(self):
def _decode_jpeg_stream(self) -> None:
from .JpegImagePlugin import JpegImageFile
(jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
jpeg_header = self._safe_read(jpeg_header_size)
assert self.fd is not None
self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
data = self._safe_read(self._blp_lengths[0])
data = jpeg_header + data
data = BytesIO(data)
image = JpegImageFile(data)
image = JpegImageFile(BytesIO(data))
Image._decompression_bomb_check(image.size)
if image.mode == "CMYK":
decoder_name, extents, offset, args = image.tile[0]
image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
r, g, b = image.convert("RGB").split()
image = Image.merge("RGB", (b, g, r))
self.set_as_raw(image.tobytes())
reversed_image = Image.merge("RGB", (b, g, r))
self.set_as_raw(reversed_image.tobytes())
class BLP2Decoder(_BLPBaseDecoder):
def _load(self):
def _load(self) -> None:
palette = self._read_palette()
assert self.fd is not None
self.fd.seek(self._blp_offsets[0])
if self._blp_compression == 1:

View File

@ -15,7 +15,7 @@ from . import Image, ImageFile
_handler = None
def register_handler(handler):
def register_handler(handler: ImageFile.StubHandler) -> None:
"""
Install application-specific BUFR image handler.
@ -54,7 +54,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
if loader:
loader.open(self)
def _load(self):
def _load(self) -> ImageFile.StubHandler | None:
return _handler

View File

@ -42,7 +42,7 @@ class DcxImageFile(PcxImageFile):
format_description = "Intel DCX"
_close_exclusive_fp_after_loading = False
def _open(self):
def _open(self) -> None:
# Header
s = self.fp.read(4)
if not _accept(s):
@ -58,7 +58,7 @@ class DcxImageFile(PcxImageFile):
self._offset.append(offset)
self._fp = self.fp
self.frame = None
self.frame = -1
self.n_frames = len(self._offset)
self.is_animated = self.n_frames > 1
self.seek(0)

View File

@ -16,6 +16,7 @@
from __future__ import annotations
import re
from typing import IO
from ._binary import o8
@ -25,8 +26,8 @@ class GimpPaletteFile:
rawmode = "RGB"
def __init__(self, fp):
self.palette = [o8(i) * 3 for i in range(256)]
def __init__(self, fp: IO[bytes]) -> None:
palette = [o8(i) * 3 for i in range(256)]
if fp.readline()[:12] != b"GIMP Palette":
msg = "not a GIMP palette file"
@ -49,9 +50,9 @@ class GimpPaletteFile:
msg = "bad palette entry"
raise ValueError(msg)
self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2])
self.palette = b"".join(self.palette)
self.palette = b"".join(palette)
def getpalette(self) -> tuple[bytes, str]:
return self.palette, self.rawmode

View File

@ -15,7 +15,7 @@ from . import Image, ImageFile
_handler = None
def register_handler(handler):
def register_handler(handler: ImageFile.StubHandler) -> None:
"""
Install application-specific GRIB image handler.
@ -54,7 +54,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
if loader:
loader.open(self)
def _load(self):
def _load(self) -> ImageFile.StubHandler | None:
return _handler

View File

@ -10,12 +10,14 @@
#
from __future__ import annotations
from typing import IO
from . import Image, ImageFile
_handler = None
def register_handler(handler):
def register_handler(handler: ImageFile.StubHandler) -> None:
"""
Install application-specific HDF5 image handler.
@ -54,11 +56,11 @@ class HDF5StubImageFile(ImageFile.StubImageFile):
if loader:
loader.open(self)
def _load(self):
def _load(self) -> ImageFile.StubHandler | None:
return _handler
def _save(im, fp, filename):
def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
if _handler is None or not hasattr(_handler, "save"):
msg = "HDF5 save handler not installed"
raise OSError(msg)

View File

@ -1948,7 +1948,9 @@ class Image:
self.im.putband(alpha.im, band)
def putdata(self, data, scale=1.0, offset=0.0):
def putdata(
self, data: Sequence[float], scale: float = 1.0, offset: float = 0.0
) -> None:
"""
Copies pixel data from a flattened sequence object into the image. The
values should start at the upper left corner (0, 0), continue to the

View File

@ -28,6 +28,7 @@
#
from __future__ import annotations
import abc
import io
import itertools
import struct
@ -347,6 +348,15 @@ class ImageFile(Image.Image):
return self.tell() != frame
class StubHandler:
def open(self, im: StubImageFile) -> None:
pass
@abc.abstractmethod
def load(self, im: StubImageFile) -> Image.Image:
pass
class StubImageFile(ImageFile):
"""
Base class for stub image loaders.

View File

@ -18,6 +18,7 @@ from __future__ import annotations
import abc
import functools
from typing import Sequence
class Filter:
@ -79,7 +80,7 @@ class RankFilter(Filter):
name = "Rank"
def __init__(self, size, rank):
def __init__(self, size: int, rank: int) -> None:
self.size = size
self.rank = rank
@ -101,7 +102,7 @@ class MedianFilter(RankFilter):
name = "Median"
def __init__(self, size=3):
def __init__(self, size: int = 3) -> None:
self.size = size
self.rank = size * size // 2
@ -116,7 +117,7 @@ class MinFilter(RankFilter):
name = "Min"
def __init__(self, size=3):
def __init__(self, size: int = 3) -> None:
self.size = size
self.rank = 0
@ -131,7 +132,7 @@ class MaxFilter(RankFilter):
name = "Max"
def __init__(self, size=3):
def __init__(self, size: int = 3) -> None:
self.size = size
self.rank = size * size - 1
@ -147,7 +148,7 @@ class ModeFilter(Filter):
name = "Mode"
def __init__(self, size=3):
def __init__(self, size: int = 3) -> None:
self.size = size
def filter(self, image):
@ -165,7 +166,7 @@ class GaussianBlur(MultibandFilter):
name = "GaussianBlur"
def __init__(self, radius=2):
def __init__(self, radius: float | Sequence[float] = 2) -> None:
self.radius = radius
def filter(self, image):
@ -228,7 +229,9 @@ class UnsharpMask(MultibandFilter):
name = "UnsharpMask"
def __init__(self, radius=2, percent=150, threshold=3):
def __init__(
self, radius: float = 2, percent: int = 150, threshold: int = 3
) -> None:
self.radius = radius
self.percent = percent
self.threshold = threshold

View File

@ -261,7 +261,7 @@ class FreeTypeFont:
"""
return self.font.family, self.font.style
def getmetrics(self):
def getmetrics(self) -> tuple[int, int]:
"""
:return: A tuple of the font ascent (the distance from the baseline to
the highest outline point) and descent (the distance from the
@ -628,7 +628,7 @@ class FreeTypeFont:
layout_engine=layout_engine or self.layout_engine,
)
def get_variation_names(self):
def get_variation_names(self) -> list[bytes]:
"""
:returns: A list of the named styles in a variation font.
:exception OSError: If the font is not a variation font.

View File

@ -18,7 +18,7 @@
from __future__ import annotations
import array
from typing import Sequence
from typing import IO, Sequence
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
@ -166,7 +166,7 @@ class ImagePalette:
msg = f"unknown color specifier: {repr(color)}"
raise ValueError(msg)
def save(self, fp):
def save(self, fp: str | IO[str]) -> None:
"""Save palette to text file.
.. warning:: This method is experimental.
@ -213,29 +213,29 @@ def make_linear_lut(black, white):
raise NotImplementedError(msg) # FIXME
def make_gamma_lut(exp):
def make_gamma_lut(exp: float) -> list[int]:
return [int(((i / 255.0) ** exp) * 255.0 + 0.5) for i in range(256)]
def negative(mode="RGB"):
def negative(mode: str = "RGB") -> ImagePalette:
palette = list(range(256 * len(mode)))
palette.reverse()
return ImagePalette(mode, [i // len(mode) for i in palette])
def random(mode="RGB"):
def random(mode: str = "RGB") -> ImagePalette:
from random import randint
palette = [randint(0, 255) for _ in range(256 * len(mode))]
return ImagePalette(mode, palette)
def sepia(white="#fff0c0"):
def sepia(white: str = "#fff0c0") -> ImagePalette:
bands = [make_linear_lut(0, band) for band in ImageColor.getrgb(white)]
return ImagePalette("RGB", [bands[i % 3][i // 3] for i in range(256 * 3)])
def wedge(mode="RGB"):
def wedge(mode: str = "RGB") -> ImagePalette:
palette = list(range(256 * len(mode)))
return ImagePalette(mode, [i // len(mode) for i in palette])

View File

@ -28,10 +28,10 @@ class HDC:
methods.
"""
def __init__(self, dc):
def __init__(self, dc: int) -> None:
self.dc = dc
def __int__(self):
def __int__(self) -> int:
return self.dc
@ -42,10 +42,10 @@ class HWND:
methods, instead of a DC.
"""
def __init__(self, wnd):
def __init__(self, wnd: int) -> None:
self.wnd = wnd
def __int__(self):
def __int__(self) -> int:
return self.wnd
@ -149,7 +149,9 @@ class Dib:
result = self.image.query_palette(handle)
return result
def paste(self, im, box=None):
def paste(
self, im: Image.Image, box: tuple[int, int, int, int] | None = None
) -> None:
"""
Paste a PIL image into the bitmap image.
@ -169,16 +171,16 @@ class Dib:
else:
self.image.paste(im.im)
def frombytes(self, buffer):
def frombytes(self, buffer: bytes) -> None:
"""
Load display memory contents from byte data.
:param buffer: A buffer containing display data (usually
data returned from :py:func:`~PIL.ImageWin.Dib.tobytes`)
"""
return self.image.frombytes(buffer)
self.image.frombytes(buffer)
def tobytes(self):
def tobytes(self) -> bytes:
"""
Copy display memory contents to bytes object.
@ -190,7 +192,9 @@ class Dib:
class Window:
"""Create a Window with the given title size."""
def __init__(self, title="PIL", width=None, height=None):
def __init__(
self, title: str = "PIL", width: int | None = None, height: int | None = None
) -> None:
self.hwnd = Image.core.createwindow(
title, self.__dispatcher, width or 0, height or 0
)

View File

@ -42,6 +42,7 @@ import subprocess
import sys
import tempfile
import warnings
from typing import Any
from . import Image, ImageFile
from ._binary import i16be as i16
@ -54,7 +55,7 @@ from .JpegPresets import presets
# Parser
def Skip(self, marker):
def Skip(self: JpegImageFile, marker: int) -> None:
n = i16(self.fp.read(2)) - 2
ImageFile._safe_read(self.fp, n)
@ -191,7 +192,7 @@ def APP(self, marker):
self.info["dpi"] = 72, 72
def COM(self, marker):
def COM(self: JpegImageFile, marker: int) -> None:
#
# Comment marker. Store these in the APP dictionary.
n = i16(self.fp.read(2)) - 2
@ -202,7 +203,7 @@ def COM(self, marker):
self.applist.append(("COM", s))
def SOF(self, marker):
def SOF(self: JpegImageFile, marker: int) -> None:
#
# Start of frame marker. Defines the size and mode of the
# image. JPEG is colour blind, so we use some simple
@ -250,7 +251,7 @@ def SOF(self, marker):
self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2]))
def DQT(self, marker):
def DQT(self: JpegImageFile, marker: int) -> None:
#
# Define quantization table. Note that there might be more
# than one table in each marker.
@ -493,13 +494,13 @@ class JpegImageFile(ImageFile.ImageFile):
self.tile = []
def _getexif(self):
def _getexif(self) -> dict[str, Any] | None:
return _getexif(self)
def _getmp(self):
return _getmp(self)
def getxmp(self):
def getxmp(self) -> dict[str, Any]:
"""
Returns a dictionary containing the XMP tags.
Requires defusedxml to be installed.
@ -515,7 +516,7 @@ class JpegImageFile(ImageFile.ImageFile):
return {}
def _getexif(self):
def _getexif(self) -> dict[str, Any] | None:
if "exif" not in self.info:
return None
return self.getexif()._get_merged_dict()

View File

@ -17,6 +17,7 @@
from __future__ import annotations
import sys
from typing import TYPE_CHECKING
from . import EpsImagePlugin
@ -38,7 +39,7 @@ class PSDraw:
fp = sys.stdout
self.fp = fp
def begin_document(self, id=None):
def begin_document(self, id: str | None = None) -> None:
"""Set up printing of a document. (Write PostScript DSC header.)"""
# FIXME: incomplete
self.fp.write(
@ -52,7 +53,7 @@ class PSDraw:
self.fp.write(EDROFF_PS)
self.fp.write(VDI_PS)
self.fp.write(b"%%EndProlog\n")
self.isofont = {}
self.isofont: dict[bytes, int] = {}
def end_document(self) -> None:
"""Ends printing. (Write PostScript DSC footer.)"""
@ -60,22 +61,24 @@ class PSDraw:
if hasattr(self.fp, "flush"):
self.fp.flush()
def setfont(self, font, size):
def setfont(self, font: str, size: int) -> None:
"""
Selects which font to use.
:param font: A PostScript font name
:param size: Size in points.
"""
font = bytes(font, "UTF-8")
if font not in self.isofont:
font_bytes = bytes(font, "UTF-8")
if font_bytes not in self.isofont:
# reencode font
self.fp.write(b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font))
self.isofont[font] = 1
self.fp.write(
b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font_bytes, font_bytes)
)
self.isofont[font_bytes] = 1
# rough
self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font))
self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font_bytes))
def line(self, xy0, xy1):
def line(self, xy0: tuple[int, int], xy1: tuple[int, int]) -> None:
"""
Draws a line between the two points. Coordinates are given in
PostScript point coordinates (72 points per inch, (0, 0) is the lower
@ -83,7 +86,7 @@ class PSDraw:
"""
self.fp.write(b"%d %d %d %d Vl\n" % (*xy0, *xy1))
def rectangle(self, box):
def rectangle(self, box: tuple[int, int, int, int]) -> None:
"""
Draws a rectangle.
@ -92,18 +95,22 @@ class PSDraw:
"""
self.fp.write(b"%d %d M 0 %d %d Vr\n" % box)
def text(self, xy, text):
def text(self, xy: tuple[int, int], text: str) -> None:
"""
Draws text at the given position. You must use
:py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method.
"""
text = bytes(text, "UTF-8")
text = b"\\(".join(text.split(b"("))
text = b"\\)".join(text.split(b")"))
xy += (text,)
self.fp.write(b"%d %d M (%s) S\n" % xy)
text_bytes = bytes(text, "UTF-8")
text_bytes = b"\\(".join(text_bytes.split(b"("))
text_bytes = b"\\)".join(text_bytes.split(b")"))
self.fp.write(b"%d %d M (%s) S\n" % (xy + (text_bytes,)))
def image(self, box, im, dpi=None):
if TYPE_CHECKING:
from . import Image
def image(
self, box: tuple[int, int, int, int], im: Image.Image, dpi: int | None = None
) -> None:
"""Draw a PIL image, centered in the given box."""
# default resolution depends on mode
if not dpi:

View File

@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any, List, NamedTuple, Union
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
# on page 656
def encode_text(s):
def encode_text(s: str) -> bytes:
return codecs.BOM_UTF16_BE + s.encode("utf_16_be")
@ -103,7 +103,7 @@ class IndirectReference(IndirectReferenceTuple):
def __ne__(self, other):
return not (self == other)
def __hash__(self):
def __hash__(self) -> int:
return hash((self.object_id, self.generation))
@ -219,7 +219,7 @@ class PdfName:
isinstance(other, PdfName) and other.name == self.name
) or other == self.name
def __hash__(self):
def __hash__(self) -> int:
return hash(self.name)
def __repr__(self) -> str:
@ -402,7 +402,7 @@ class PdfParser:
if f:
self.seek_end()
def __enter__(self):
def __enter__(self) -> PdfParser:
return self
def __exit__(self, exc_type, exc_value, traceback):
@ -436,7 +436,7 @@ class PdfParser:
def write_comment(self, s):
self.f.write(f"% {s}\n".encode())
def write_catalog(self):
def write_catalog(self) -> IndirectReference:
self.del_root()
self.root_ref = self.next_object_id(self.f.tell())
self.pages_ref = self.next_object_id(0)

View File

@ -39,7 +39,7 @@ import struct
import warnings
import zlib
from enum import IntEnum
from typing import IO
from typing import IO, Any
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
from ._binary import i16be as i16
@ -1019,7 +1019,7 @@ class PngImageFile(ImageFile.ImageFile):
if self.pyaccess:
self.pyaccess = None
def _getexif(self):
def _getexif(self) -> dict[str, Any] | None:
if "exif" not in self.info:
self.load()
if "exif" not in self.info and "Raw profile type exif" not in self.info:
@ -1032,7 +1032,7 @@ class PngImageFile(ImageFile.ImageFile):
return super().getexif()
def getxmp(self):
def getxmp(self) -> dict[str, Any]:
"""
Returns a dictionary containing the XMP tags.
Requires defusedxml to be installed.
@ -1234,7 +1234,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
seq_num = fdat_chunks.seq_num
def _save_all(im, fp, filename):
def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None:
_save(im, fp, filename, save_all=True)

View File

@ -22,6 +22,7 @@ from __future__ import annotations
import logging
import sys
from typing import TYPE_CHECKING
from ._deprecate import deprecate
@ -48,9 +49,12 @@ except ImportError as ex:
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from . import Image
class PyAccess:
def __init__(self, img, readonly=False):
def __init__(self, img: Image.Image, readonly: bool = False) -> None:
deprecate("PyAccess", 11)
vals = dict(img.im.unsafe_ptrs)
self.readonly = readonly
@ -130,7 +134,7 @@ class PyAccess:
putpixel = __setitem__
getpixel = __getitem__
def check_xy(self, xy):
def check_xy(self, xy: tuple[int, int]) -> tuple[int, int]:
(x, y) = xy
if not (0 <= x < self.xsize and 0 <= y < self.ysize):
msg = "pixel location out of range"
@ -161,7 +165,7 @@ class _PyAccess32_3(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x, y):
def get_pixel(self, x: int, y: int) -> tuple[int, int, int]:
pixel = self.pixels[y][x]
return pixel.r, pixel.g, pixel.b
@ -180,7 +184,7 @@ class _PyAccess32_4(PyAccess):
def _post_init(self, *args, **kwargs):
self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32)
def get_pixel(self, x, y):
def get_pixel(self, x: int, y: int) -> tuple[int, int, int, int]:
pixel = self.pixels[y][x]
return pixel.r, pixel.g, pixel.b, pixel.a

View File

@ -1202,7 +1202,7 @@ class TiffImageFile(ImageFile.ImageFile):
"""Return the current frame number"""
return self.__frame
def getxmp(self):
def getxmp(self) -> dict[str, Any]:
"""
Returns a dictionary containing the XMP tags.
Requires defusedxml to be installed.

View File

@ -1,6 +1,7 @@
from __future__ import annotations
from io import BytesIO
from typing import Any
from . import Image, ImageFile
@ -95,12 +96,12 @@ class WebPImageFile(ImageFile.ImageFile):
# Initialize seek state
self._reset(reset=False)
def _getexif(self):
def _getexif(self) -> dict[str, Any] | None:
if "exif" not in self.info:
return None
return self.getexif()._get_merged_dict()
def getxmp(self):
def getxmp(self) -> dict[str, Any]:
"""
Returns a dictionary containing the XMP tags.
Requires defusedxml to be installed.

View File

@ -28,7 +28,7 @@ from ._binary import si32le as _long
_handler = None
def register_handler(handler):
def register_handler(handler: ImageFile.StubHandler) -> None:
"""
Install application-specific WMF image handler.
@ -41,12 +41,12 @@ def register_handler(handler):
if hasattr(Image.core, "drawwmf"):
# install default handler (windows only)
class WmfHandler:
def open(self, im):
class WmfHandler(ImageFile.StubHandler):
def open(self, im: ImageFile.StubImageFile) -> None:
im._mode = "RGB"
self.bbox = im.info["wmf_bbox"]
def load(self, im):
def load(self, im: ImageFile.StubImageFile) -> Image.Image:
im.fp.seek(0) # rewind
return Image.frombytes(
"RGB",
@ -147,7 +147,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
if loader:
loader.open(self)
def _load(self):
def _load(self) -> ImageFile.StubHandler | None:
return _handler
def load(self, dpi=None):