1616from __future__ import annotations
1717
1818import math
19+ from io import BytesIO
1920
2021from . import Image , ImageFile
2122from ._binary import i16be as i16
4546}
4647
4748
48- def _accept (prefix ) :
49+ def _accept (prefix : bytes ) -> bool :
4950 return prefix [0 :1 ] == b"P" and prefix [1 ] in b"0123456fy"
5051
5152
@@ -57,7 +58,9 @@ class PpmImageFile(ImageFile.ImageFile):
5758 format = "PPM"
5859 format_description = "Pbmplus image"
5960
60- def _read_magic (self ):
61+ def _read_magic (self ) -> bytes :
62+ assert self .fp is not None
63+
6164 magic = b""
6265 # read until whitespace or longest available magic number
6366 for _ in range (6 ):
@@ -67,7 +70,9 @@ def _read_magic(self):
6770 magic += c
6871 return magic
6972
70- def _read_token (self ):
73+ def _read_token (self ) -> bytes :
74+ assert self .fp is not None
75+
7176 token = b""
7277 while len (token ) <= 10 : # read until next whitespace or limit of 10 characters
7378 c = self .fp .read (1 )
@@ -93,7 +98,9 @@ def _read_token(self):
9398 raise ValueError (msg )
9499 return token
95100
96- def _open (self ):
101+ def _open (self ) -> None :
102+ assert self .fp is not None
103+
97104 magic_number = self ._read_magic ()
98105 try :
99106 mode = MODES [magic_number ]
@@ -114,6 +121,8 @@ def _open(self):
114121 decoder_name = "raw"
115122 if magic_number in (b"P1" , b"P2" , b"P3" ):
116123 decoder_name = "ppm_plain"
124+
125+ args : str | tuple [str | int , ...]
117126 if mode == "1" :
118127 args = "1;I"
119128 elif mode == "F" :
@@ -151,16 +160,19 @@ def _open(self):
151160
152161class PpmPlainDecoder (ImageFile .PyDecoder ):
153162 _pulls_fd = True
163+ _comment_spans : bool
164+
165+ def _read_block (self ) -> bytes :
166+ assert self .fd is not None
154167
155- def _read_block (self ):
156168 return self .fd .read (ImageFile .SAFEBLOCK )
157169
158- def _find_comment_end (self , block , start = 0 ) :
170+ def _find_comment_end (self , block : bytes , start : int = 0 ) -> int :
159171 a = block .find (b"\n " , start )
160172 b = block .find (b"\r " , start )
161173 return min (a , b ) if a * b > 0 else max (a , b ) # lowest nonnegative index (or -1)
162174
163- def _ignore_comments (self , block ) :
175+ def _ignore_comments (self , block : bytes ) -> bytes :
164176 if self ._comment_spans :
165177 # Finish current comment
166178 while block :
@@ -194,7 +206,7 @@ def _ignore_comments(self, block):
194206 break
195207 return block
196208
197- def _decode_bitonal (self ):
209+ def _decode_bitonal (self ) -> bytearray :
198210 """
199211 This is a separate method because in the plain PBM format, all data tokens are
200212 exactly one byte, so the inter-token whitespace is optional.
@@ -219,15 +231,15 @@ def _decode_bitonal(self):
219231 invert = bytes .maketrans (b"01" , b"\xFF \x00 " )
220232 return data .translate (invert )
221233
222- def _decode_blocks (self , maxval ) :
234+ def _decode_blocks (self , maxval : int ) -> bytearray :
223235 data = bytearray ()
224236 max_len = 10
225237 out_byte_count = 4 if self .mode == "I" else 1
226238 out_max = 65535 if self .mode == "I" else 255
227239 bands = Image .getmodebands (self .mode )
228240 total_bytes = self .state .xsize * self .state .ysize * bands * out_byte_count
229241
230- half_token = False
242+ half_token = b""
231243 while len (data ) != total_bytes :
232244 block = self ._read_block () # read next block
233245 if not block :
@@ -241,7 +253,7 @@ def _decode_blocks(self, maxval):
241253
242254 if half_token :
243255 block = half_token + block # stitch half_token to new block
244- half_token = False
256+ half_token = b""
245257
246258 tokens = block .split ()
247259
@@ -259,15 +271,15 @@ def _decode_blocks(self, maxval):
259271 raise ValueError (msg )
260272 value = int (token )
261273 if value > maxval :
262- msg = f"Channel value too large for this mode: { value } "
263- raise ValueError (msg )
274+ msg_str = f"Channel value too large for this mode: { value } "
275+ raise ValueError (msg_str )
264276 value = round (value / maxval * out_max )
265277 data += o32 (value ) if self .mode == "I" else o8 (value )
266278 if len (data ) == total_bytes : # finished!
267279 break
268280 return data
269281
270- def decode (self , buffer ) :
282+ def decode (self , buffer : bytes ) -> tuple [ int , int ] :
271283 self ._comment_spans = False
272284 if self .mode == "1" :
273285 data = self ._decode_bitonal ()
@@ -283,7 +295,9 @@ def decode(self, buffer):
283295class PpmDecoder (ImageFile .PyDecoder ):
284296 _pulls_fd = True
285297
286- def decode (self , buffer ):
298+ def decode (self , buffer : bytes ) -> tuple [int , int ]:
299+ assert self .fd is not None
300+
287301 data = bytearray ()
288302 maxval = self .args [- 1 ]
289303 in_byte_count = 1 if maxval < 256 else 2
@@ -310,7 +324,7 @@ def decode(self, buffer):
310324# --------------------------------------------------------------------
311325
312326
313- def _save (im , fp , filename ) :
327+ def _save (im : Image . Image , fp : BytesIO , filename : str ) -> None :
314328 if im .mode == "1" :
315329 rawmode , head = "1;I" , b"P4"
316330 elif im .mode == "L" :
0 commit comments