2525import warnings
2626from io import BytesIO
2727from math import ceil , log
28- from typing import IO
28+ from typing import IO , NamedTuple
2929
3030from . import BmpImagePlugin , Image , ImageFile , PngImagePlugin
3131from ._binary import i16le as i16
@@ -119,8 +119,22 @@ def _accept(prefix: bytes) -> bool:
119119 return prefix [:4 ] == _MAGIC
120120
121121
122+ class IconHeader (NamedTuple ):
123+ width : int
124+ height : int
125+ nb_color : int
126+ reserved : int
127+ planes : int
128+ bpp : int
129+ size : int
130+ offset : int
131+ dim : tuple [int , int ]
132+ square : int
133+ color_depth : int
134+
135+
122136class IcoFile :
123- def __init__ (self , buf ) -> None :
137+ def __init__ (self , buf : IO [ bytes ] ) -> None :
124138 """
125139 Parse image from file-like object containing ico file data
126140 """
@@ -141,51 +155,44 @@ def __init__(self, buf) -> None:
141155 for i in range (self .nb_items ):
142156 s = buf .read (16 )
143157
144- icon_header = {
145- "width" : s [0 ],
146- "height" : s [1 ],
147- "nb_color" : s [2 ], # No. of colors in image (0 if >=8bpp)
148- "reserved" : s [3 ],
149- "planes" : i16 (s , 4 ),
150- "bpp" : i16 (s , 6 ),
151- "size" : i32 (s , 8 ),
152- "offset" : i32 (s , 12 ),
153- }
154-
155158 # See Wikipedia
156- for j in ("width" , "height" ):
157- if not icon_header [j ]:
158- icon_header [j ] = 256
159-
160- # See Wikipedia notes about color depth.
161- # We need this just to differ images with equal sizes
162- icon_header ["color_depth" ] = (
163- icon_header ["bpp" ]
164- or (
165- icon_header ["nb_color" ] != 0
166- and ceil (log (icon_header ["nb_color" ], 2 ))
167- )
168- or 256
159+ width = s [0 ] or 256
160+ height = s [1 ] or 256
161+
162+ # No. of colors in image (0 if >=8bpp)
163+ nb_color = s [2 ]
164+ bpp = i16 (s , 6 )
165+ icon_header = IconHeader (
166+ width = width ,
167+ height = height ,
168+ nb_color = nb_color ,
169+ reserved = s [3 ],
170+ planes = i16 (s , 4 ),
171+ bpp = i16 (s , 6 ),
172+ size = i32 (s , 8 ),
173+ offset = i32 (s , 12 ),
174+ dim = (width , height ),
175+ square = width * height ,
176+ # See Wikipedia notes about color depth.
177+ # We need this just to differ images with equal sizes
178+ color_depth = bpp or (nb_color != 0 and ceil (log (nb_color , 2 ))) or 256 ,
169179 )
170180
171- icon_header ["dim" ] = (icon_header ["width" ], icon_header ["height" ])
172- icon_header ["square" ] = icon_header ["width" ] * icon_header ["height" ]
173-
174181 self .entry .append (icon_header )
175182
176- self .entry = sorted (self .entry , key = lambda x : x [ " color_depth" ] )
183+ self .entry = sorted (self .entry , key = lambda x : x . color_depth )
177184 # ICO images are usually squares
178- self .entry = sorted (self .entry , key = lambda x : x [ " square" ] , reverse = True )
185+ self .entry = sorted (self .entry , key = lambda x : x . square , reverse = True )
179186
180187 def sizes (self ) -> set [tuple [int , int ]]:
181188 """
182- Get a list of all available icon sizes and color depths.
189+ Get a set of all available icon sizes and color depths.
183190 """
184- return {(h [ " width" ] , h [ " height" ] ) for h in self .entry }
191+ return {(h . width , h . height ) for h in self .entry }
185192
186193 def getentryindex (self , size : tuple [int , int ], bpp : int | bool = False ) -> int :
187194 for i , h in enumerate (self .entry ):
188- if size == h [ " dim" ] and (bpp is False or bpp == h [ " color_depth" ] ):
195+ if size == h . dim and (bpp is False or bpp == h . color_depth ):
189196 return i
190197 return 0
191198
@@ -202,9 +209,9 @@ def frame(self, idx: int) -> Image.Image:
202209
203210 header = self .entry [idx ]
204211
205- self .buf .seek (header [ " offset" ] )
212+ self .buf .seek (header . offset )
206213 data = self .buf .read (8 )
207- self .buf .seek (header [ " offset" ] )
214+ self .buf .seek (header . offset )
208215
209216 im : Image .Image
210217 if data [:8 ] == PngImagePlugin ._MAGIC :
@@ -222,8 +229,7 @@ def frame(self, idx: int) -> Image.Image:
222229 im .tile [0 ] = d , (0 , 0 ) + im .size , o , a
223230
224231 # figure out where AND mask image starts
225- bpp = header ["bpp" ]
226- if 32 == bpp :
232+ if header .bpp == 32 :
227233 # 32-bit color depth icon image allows semitransparent areas
228234 # PIL's DIB format ignores transparency bits, recover them.
229235 # The DIB is packed in BGRX byte order where X is the alpha
@@ -253,7 +259,7 @@ def frame(self, idx: int) -> Image.Image:
253259 # padded row size * height / bits per char
254260
255261 total_bytes = int ((w * im .size [1 ]) / 8 )
256- and_mask_offset = header [ " offset" ] + header [ " size" ] - total_bytes
262+ and_mask_offset = header . offset + header . size - total_bytes
257263
258264 self .buf .seek (and_mask_offset )
259265 mask_data = self .buf .read (total_bytes )
@@ -307,7 +313,7 @@ class IcoImageFile(ImageFile.ImageFile):
307313 def _open (self ) -> None :
308314 self .ico = IcoFile (self .fp )
309315 self .info ["sizes" ] = self .ico .sizes ()
310- self .size = self .ico .entry [0 ][ " dim" ]
316+ self .size = self .ico .entry [0 ]. dim
311317 self .load ()
312318
313319 @property
0 commit comments