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: :undoc-members:
:show-inheritance: :show-inheritance:
.. autoclass:: PIL.ImageFile.StubHandler()
:members:
:show-inheritance:
.. autoclass:: PIL.ImageFile.StubImageFile() .. autoclass:: PIL.ImageFile.StubImageFile()
:members: :members:
:show-inheritance: :show-inheritance:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1948,7 +1948,9 @@ class Image:
self.im.putband(alpha.im, band) 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 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 values should start at the upper left corner (0, 0), continue to the

View File

@ -28,6 +28,7 @@
# #
from __future__ import annotations from __future__ import annotations
import abc
import io import io
import itertools import itertools
import struct import struct
@ -347,6 +348,15 @@ class ImageFile(Image.Image):
return self.tell() != frame 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): class StubImageFile(ImageFile):
""" """
Base class for stub image loaders. Base class for stub image loaders.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from typing import TYPE_CHECKING
from . import EpsImagePlugin from . import EpsImagePlugin
@ -38,7 +39,7 @@ class PSDraw:
fp = sys.stdout fp = sys.stdout
self.fp = fp 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.)""" """Set up printing of a document. (Write PostScript DSC header.)"""
# FIXME: incomplete # FIXME: incomplete
self.fp.write( self.fp.write(
@ -52,7 +53,7 @@ class PSDraw:
self.fp.write(EDROFF_PS) self.fp.write(EDROFF_PS)
self.fp.write(VDI_PS) self.fp.write(VDI_PS)
self.fp.write(b"%%EndProlog\n") self.fp.write(b"%%EndProlog\n")
self.isofont = {} self.isofont: dict[bytes, int] = {}
def end_document(self) -> None: def end_document(self) -> None:
"""Ends printing. (Write PostScript DSC footer.)""" """Ends printing. (Write PostScript DSC footer.)"""
@ -60,22 +61,24 @@ class PSDraw:
if hasattr(self.fp, "flush"): if hasattr(self.fp, "flush"):
self.fp.flush() self.fp.flush()
def setfont(self, font, size): def setfont(self, font: str, size: int) -> None:
""" """
Selects which font to use. Selects which font to use.
:param font: A PostScript font name :param font: A PostScript font name
:param size: Size in points. :param size: Size in points.
""" """
font = bytes(font, "UTF-8") font_bytes = bytes(font, "UTF-8")
if font not in self.isofont: if font_bytes not in self.isofont:
# reencode font # reencode font
self.fp.write(b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font)) self.fp.write(
self.isofont[font] = 1 b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font_bytes, font_bytes)
)
self.isofont[font_bytes] = 1
# rough # 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 Draws a line between the two points. Coordinates are given in
PostScript point coordinates (72 points per inch, (0, 0) is the lower 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)) 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. Draws a rectangle.
@ -92,18 +95,22 @@ class PSDraw:
""" """
self.fp.write(b"%d %d M 0 %d %d Vr\n" % box) 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 Draws text at the given position. You must use
:py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method. :py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method.
""" """
text = bytes(text, "UTF-8") text_bytes = bytes(text, "UTF-8")
text = b"\\(".join(text.split(b"(")) text_bytes = b"\\(".join(text_bytes.split(b"("))
text = b"\\)".join(text.split(b")")) text_bytes = b"\\)".join(text_bytes.split(b")"))
xy += (text,) self.fp.write(b"%d %d M (%s) S\n" % (xy + (text_bytes,)))
self.fp.write(b"%d %d M (%s) S\n" % xy)
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.""" """Draw a PIL image, centered in the given box."""
# default resolution depends on mode # default resolution depends on mode
if not dpi: 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 # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
# on page 656 # on page 656
def encode_text(s): def encode_text(s: str) -> bytes:
return codecs.BOM_UTF16_BE + s.encode("utf_16_be") return codecs.BOM_UTF16_BE + s.encode("utf_16_be")
@ -103,7 +103,7 @@ class IndirectReference(IndirectReferenceTuple):
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
def __hash__(self): def __hash__(self) -> int:
return hash((self.object_id, self.generation)) return hash((self.object_id, self.generation))
@ -219,7 +219,7 @@ class PdfName:
isinstance(other, PdfName) and other.name == self.name isinstance(other, PdfName) and other.name == self.name
) or other == self.name ) or other == self.name
def __hash__(self): def __hash__(self) -> int:
return hash(self.name) return hash(self.name)
def __repr__(self) -> str: def __repr__(self) -> str:
@ -402,7 +402,7 @@ class PdfParser:
if f: if f:
self.seek_end() self.seek_end()
def __enter__(self): def __enter__(self) -> PdfParser:
return self return self
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
@ -436,7 +436,7 @@ class PdfParser:
def write_comment(self, s): def write_comment(self, s):
self.f.write(f"% {s}\n".encode()) self.f.write(f"% {s}\n".encode())
def write_catalog(self): def write_catalog(self) -> IndirectReference:
self.del_root() self.del_root()
self.root_ref = self.next_object_id(self.f.tell()) self.root_ref = self.next_object_id(self.f.tell())
self.pages_ref = self.next_object_id(0) self.pages_ref = self.next_object_id(0)

View File

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

View File

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

View File

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

View File

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