3939import warnings
4040import zlib
4141from enum import IntEnum
42- from typing import IO , Any
42+ from typing import IO , TYPE_CHECKING , Any , NoReturn
4343
4444from . import Image , ImageChops , ImageFile , ImagePalette , ImageSequence
4545from ._binary import i16be as i16
4848from ._binary import o16be as o16
4949from ._binary import o32be as o32
5050
51+ if TYPE_CHECKING :
52+ from . import _imaging
53+
5154logger = logging .getLogger (__name__ )
5255
5356is_cid = re .compile (rb"\w\w\w\w" ).match
@@ -249,6 +252,9 @@ class iTXt(str):
249252
250253 """
251254
255+ lang : str | bytes | None
256+ tkey : str | bytes | None
257+
252258 @staticmethod
253259 def __new__ (cls , text , lang = None , tkey = None ):
254260 """
@@ -270,10 +276,10 @@ class PngInfo:
270276
271277 """
272278
273- def __init__ (self ):
274- self .chunks = []
279+ def __init__ (self ) -> None :
280+ self .chunks : list [ tuple [ bytes , bytes , bool ]] = []
275281
276- def add (self , cid , data , after_idat = False ):
282+ def add (self , cid : bytes , data : bytes , after_idat : bool = False ) -> None :
277283 """Appends an arbitrary chunk. Use with caution.
278284
279285 :param cid: a byte string, 4 bytes long.
@@ -283,12 +289,16 @@ def add(self, cid, data, after_idat=False):
283289
284290 """
285291
286- chunk = [cid , data ]
287- if after_idat :
288- chunk .append (True )
289- self .chunks .append (tuple (chunk ))
292+ self .chunks .append ((cid , data , after_idat ))
290293
291- def add_itxt (self , key , value , lang = "" , tkey = "" , zip = False ):
294+ def add_itxt (
295+ self ,
296+ key : str | bytes ,
297+ value : str | bytes ,
298+ lang : str | bytes = "" ,
299+ tkey : str | bytes = "" ,
300+ zip : bool = False ,
301+ ) -> None :
292302 """Appends an iTXt chunk.
293303
294304 :param key: latin-1 encodable text key name
@@ -316,7 +326,9 @@ def add_itxt(self, key, value, lang="", tkey="", zip=False):
316326 else :
317327 self .add (b"iTXt" , key + b"\0 \0 \0 " + lang + b"\0 " + tkey + b"\0 " + value )
318328
319- def add_text (self , key , value , zip = False ):
329+ def add_text (
330+ self , key : str | bytes , value : str | bytes | iTXt , zip : bool = False
331+ ) -> None :
320332 """Appends a text chunk.
321333
322334 :param key: latin-1 encodable text key name
@@ -326,7 +338,13 @@ def add_text(self, key, value, zip=False):
326338
327339 """
328340 if isinstance (value , iTXt ):
329- return self .add_itxt (key , value , value .lang , value .tkey , zip = zip )
341+ return self .add_itxt (
342+ key ,
343+ value ,
344+ value .lang if value .lang is not None else b"" ,
345+ value .tkey if value .tkey is not None else b"" ,
346+ zip = zip ,
347+ )
330348
331349 # The tEXt chunk stores latin-1 text
332350 if not isinstance (value , bytes ):
@@ -434,7 +452,7 @@ def chunk_IHDR(self, pos: int, length: int) -> bytes:
434452 raise SyntaxError (msg )
435453 return s
436454
437- def chunk_IDAT (self , pos , length ) :
455+ def chunk_IDAT (self , pos : int , length : int ) -> NoReturn :
438456 # image data
439457 if "bbox" in self .im_info :
440458 tile = [("zip" , self .im_info ["bbox" ], pos , self .im_rawmode )]
@@ -447,7 +465,7 @@ def chunk_IDAT(self, pos, length):
447465 msg = "image data found"
448466 raise EOFError (msg )
449467
450- def chunk_IEND (self , pos , length ) :
468+ def chunk_IEND (self , pos : int , length : int ) -> NoReturn :
451469 msg = "end of PNG image"
452470 raise EOFError (msg )
453471
@@ -821,7 +839,10 @@ def seek(self, frame: int) -> None:
821839 msg = "no more images in APNG file"
822840 raise EOFError (msg ) from e
823841
824- def _seek (self , frame , rewind = False ):
842+ def _seek (self , frame : int , rewind : bool = False ) -> None :
843+ assert self .png is not None
844+
845+ self .dispose : _imaging .ImagingCore | None
825846 if frame == 0 :
826847 if rewind :
827848 self ._fp .seek (self .__rewind )
@@ -906,14 +927,14 @@ def _seek(self, frame, rewind=False):
906927 if self ._prev_im is None and self .dispose_op == Disposal .OP_PREVIOUS :
907928 self .dispose_op = Disposal .OP_BACKGROUND
908929
930+ self .dispose = None
909931 if self .dispose_op == Disposal .OP_PREVIOUS :
910- self .dispose = self ._prev_im .copy ()
911- self .dispose = self ._crop (self .dispose , self .dispose_extent )
932+ if self ._prev_im :
933+ self .dispose = self ._prev_im .copy ()
934+ self .dispose = self ._crop (self .dispose , self .dispose_extent )
912935 elif self .dispose_op == Disposal .OP_BACKGROUND :
913936 self .dispose = Image .core .fill (self .mode , self .size )
914937 self .dispose = self ._crop (self .dispose , self .dispose_extent )
915- else :
916- self .dispose = None
917938
918939 def tell (self ) -> int :
919940 return self .__frame
@@ -1026,7 +1047,7 @@ def _getexif(self) -> dict[str, Any] | None:
10261047 return None
10271048 return self .getexif ()._get_merged_dict ()
10281049
1029- def getexif (self ):
1050+ def getexif (self ) -> Image . Exif :
10301051 if "exif" not in self .info :
10311052 self .load ()
10321053
@@ -1346,7 +1367,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
13461367 chunk (fp , cid , data )
13471368 elif cid [1 :2 ].islower ():
13481369 # Private chunk
1349- after_idat = info_chunk [ 2 : 3 ]
1370+ after_idat = len ( info_chunk ) == 3 and info_chunk [ 2 ]
13501371 if not after_idat :
13511372 chunk (fp , cid , data )
13521373
@@ -1425,7 +1446,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
14251446 cid , data = info_chunk [:2 ]
14261447 if cid [1 :2 ].islower ():
14271448 # Private chunk
1428- after_idat = info_chunk [ 2 : 3 ]
1449+ after_idat = len ( info_chunk ) == 3 and info_chunk [ 2 ]
14291450 if after_idat :
14301451 chunk (fp , cid , data )
14311452
0 commit comments