2626
2727from __future__ import annotations
2828
29+ import abc
2930import atexit
3031import builtins
3132import io
4041from collections .abc import Callable , MutableMapping
4142from enum import IntEnum
4243from pathlib import Path
43-
44- try :
45- from defusedxml import ElementTree
46- except ImportError :
47- ElementTree = None
44+ from types import ModuleType
45+ from typing import IO , TYPE_CHECKING , Any
4846
4947# VERSION was removed in Pillow 6.0.0.
5048# PILLOW_VERSION was removed in Pillow 9.0.0.
6058from ._binary import i32le , o32be , o32le
6159from ._util import DeferredError , is_path
6260
61+ ElementTree : ModuleType | None
62+ try :
63+ from defusedxml import ElementTree
64+ except ImportError :
65+ ElementTree = None
66+
6367logger = logging .getLogger (__name__ )
6468
6569
@@ -110,6 +114,7 @@ class DecompressionBombError(Exception):
110114
111115
112116USE_CFFI_ACCESS = False
117+ cffi : ModuleType | None
113118try :
114119 import cffi
115120except ImportError :
@@ -211,14 +216,22 @@ class Quantize(IntEnum):
211216# --------------------------------------------------------------------
212217# Registries
213218
214- ID = []
215- OPEN = {}
216- MIME = {}
217- SAVE = {}
218- SAVE_ALL = {}
219- EXTENSION = {}
220- DECODERS = {}
221- ENCODERS = {}
219+ if TYPE_CHECKING :
220+ from . import ImageFile
221+ ID : list [str ] = []
222+ OPEN : dict [
223+ str ,
224+ tuple [
225+ Callable [[IO [bytes ], str | bytes ], ImageFile .ImageFile ],
226+ Callable [[bytes ], bool ] | None ,
227+ ],
228+ ] = {}
229+ MIME : dict [str , str ] = {}
230+ SAVE : dict [str , Callable [[Image , IO [bytes ], str | bytes ], None ]] = {}
231+ SAVE_ALL : dict [str , Callable [[Image , IO [bytes ], str | bytes ], None ]] = {}
232+ EXTENSION : dict [str , str ] = {}
233+ DECODERS : dict [str , object ] = {}
234+ ENCODERS : dict [str , object ] = {}
222235
223236# --------------------------------------------------------------------
224237# Modes
@@ -2383,12 +2396,12 @@ def save(self, fp, format=None, **params) -> None:
23832396 may have been created, and may contain partial data.
23842397 """
23852398
2386- filename = ""
2399+ filename : str | bytes = ""
23872400 open_fp = False
23882401 if isinstance (fp , Path ):
23892402 filename = str (fp )
23902403 open_fp = True
2391- elif is_path (fp ):
2404+ elif isinstance (fp , ( str , bytes ) ):
23922405 filename = fp
23932406 open_fp = True
23942407 elif fp == sys .stdout :
@@ -2398,7 +2411,7 @@ def save(self, fp, format=None, **params) -> None:
23982411 pass
23992412 if not filename and hasattr (fp , "name" ) and is_path (fp .name ):
24002413 # only set the name for metadata purposes
2401- filename = fp .name
2414+ filename = os . path . realpath ( os . fspath ( fp .name ))
24022415
24032416 # may mutate self!
24042417 self ._ensure_mutable ()
@@ -2409,7 +2422,8 @@ def save(self, fp, format=None, **params) -> None:
24092422
24102423 preinit ()
24112424
2412- ext = os .path .splitext (filename )[1 ].lower ()
2425+ filename_ext = os .path .splitext (filename )[1 ].lower ()
2426+ ext = filename_ext .decode () if isinstance (filename_ext , bytes ) else filename_ext
24132427
24142428 if not format :
24152429 if ext not in EXTENSION :
@@ -2451,7 +2465,7 @@ def save(self, fp, format=None, **params) -> None:
24512465 if open_fp :
24522466 fp .close ()
24532467
2454- def seek (self , frame ) -> Image :
2468+ def seek (self , frame ) -> None :
24552469 """
24562470 Seeks to the given frame in this sequence file. If you seek
24572471 beyond the end of the sequence, the method raises an
@@ -2511,10 +2525,8 @@ def split(self) -> tuple[Image, ...]:
25112525
25122526 self .load ()
25132527 if self .im .bands == 1 :
2514- ims = [self .copy ()]
2515- else :
2516- ims = map (self ._new , self .im .split ())
2517- return tuple (ims )
2528+ return (self .copy (),)
2529+ return tuple (map (self ._new , self .im .split ()))
25182530
25192531 def getchannel (self , channel ):
25202532 """
@@ -2871,7 +2883,14 @@ class ImageTransformHandler:
28712883 (for use with :py:meth:`~PIL.Image.Image.transform`)
28722884 """
28732885
2874- pass
2886+ @abc .abstractmethod
2887+ def transform (
2888+ self ,
2889+ size : tuple [int , int ],
2890+ image : Image ,
2891+ ** options : dict [str , str | int | tuple [int , ...] | list [int ]],
2892+ ) -> Image :
2893+ pass
28752894
28762895
28772896# --------------------------------------------------------------------
@@ -3243,11 +3262,9 @@ def open(fp, mode="r", formats=None) -> Image:
32433262 raise TypeError (msg )
32443263
32453264 exclusive_fp = False
3246- filename = ""
3247- if isinstance (fp , Path ):
3248- filename = str (fp .resolve ())
3249- elif is_path (fp ):
3250- filename = fp
3265+ filename : str | bytes = ""
3266+ if is_path (fp ):
3267+ filename = os .path .realpath (os .fspath (fp ))
32513268
32523269 if filename :
32533270 fp = builtins .open (filename , "rb" )
@@ -3421,7 +3438,11 @@ def merge(mode, bands):
34213438# Plugin registry
34223439
34233440
3424- def register_open (id , factory , accept = None ) -> None :
3441+ def register_open (
3442+ id ,
3443+ factory : Callable [[IO [bytes ], str | bytes ], ImageFile .ImageFile ],
3444+ accept : Callable [[bytes ], bool ] | None = None ,
3445+ ) -> None :
34253446 """
34263447 Register an image file plugin. This function should not be used
34273448 in application code.
@@ -3631,7 +3652,13 @@ def _apply_env_variables(env=None):
36313652atexit .register (core .clear_cache )
36323653
36333654
3634- class Exif (MutableMapping ):
3655+ if TYPE_CHECKING :
3656+ _ExifBase = MutableMapping [int , Any ]
3657+ else :
3658+ _ExifBase = MutableMapping
3659+
3660+
3661+ class Exif (_ExifBase ):
36353662 """
36363663 This class provides read and write access to EXIF image data::
36373664
0 commit comments