@@ -301,7 +301,9 @@ def _decode_dib_payload(payload: bytes, entry_width: int, entry_height: int) ->
301301 if header_size < 40 or len (payload ) < header_size :
302302 raise ValueError (f"Unsupported DIB header size { header_size } " )
303303
304- width , raw_height , planes , bit_count , compression , _ , _ , _ , colors_used , _ = struct .unpack_from ("<iiHHIIiiII" , payload , 4 )
304+ width , raw_height , planes , bit_count , compression , image_size , _ , _ , colors_used , _ = struct .unpack_from (
305+ "<iiHHIIiiII" , payload , 4
306+ )
305307 if planes != 1 :
306308 raise ValueError (f"Unsupported DIB plane count { planes } " )
307309 if compression != 0 :
@@ -311,8 +313,7 @@ def _decode_dib_payload(payload: bytes, entry_width: int, entry_height: int) ->
311313
312314 width = abs (width ) or entry_width
313315 absolute_height = abs (raw_height )
314- height = entry_height if absolute_height >= entry_height * 2 else absolute_height
315- if width <= 0 or height <= 0 :
316+ if width <= 0 or absolute_height <= 0 :
316317 raise ValueError ("Invalid DIB cursor dimensions" )
317318
318319 palette : list [tuple [int , int , int ]] = []
@@ -328,9 +329,19 @@ def _decode_dib_payload(payload: bytes, entry_width: int, entry_height: int) ->
328329 palette .append ((r , g , b ))
329330 pixel_offset = header_size + color_table_size
330331 row_stride = ((width * bit_count + 31 ) // 32 ) * 4
332+ mask_stride = ((width + 31 ) // 32 ) * 4
333+ available_data_size = len (payload ) - pixel_offset
334+ declared_data_size = image_size or available_data_size
335+ height = _infer_dib_cursor_height (
336+ absolute_height = absolute_height ,
337+ entry_height = entry_height ,
338+ row_stride = row_stride ,
339+ mask_stride = mask_stride ,
340+ available_data_size = available_data_size ,
341+ declared_data_size = declared_data_size ,
342+ )
331343 xor_size = row_stride * height
332344 mask_offset = pixel_offset + xor_size
333- mask_stride = ((width + 31 ) // 32 ) * 4
334345 if len (payload ) < pixel_offset + xor_size :
335346 raise ValueError ("DIB cursor pixel data is truncated" )
336347
@@ -376,6 +387,52 @@ def _decode_dib_payload(payload: bytes, entry_width: int, entry_height: int) ->
376387 return Image .frombytes ("RGBA" , (width , height ), bytes (rgba ))
377388
378389
390+ def _infer_dib_cursor_height (
391+ * ,
392+ absolute_height : int ,
393+ entry_height : int ,
394+ row_stride : int ,
395+ mask_stride : int ,
396+ available_data_size : int ,
397+ declared_data_size : int ,
398+ ) -> int :
399+ """Infer the real bitmap height for cursor DIB payloads.
400+
401+ CUR directory dimensions are limited to a byte and can disagree with the
402+ embedded DIB header. Cursor DIBs normally store XOR and AND bitmaps, so the
403+ header height is twice the visible image height.
404+ """
405+
406+ def exact_with_mask (height : int ) -> bool :
407+ return row_stride * height + mask_stride * height == declared_data_size
408+
409+ def exact_without_mask (height : int ) -> bool :
410+ return row_stride * height == declared_data_size
411+
412+ def fits_with_mask (height : int ) -> bool :
413+ return row_stride * height + mask_stride * height <= available_data_size
414+
415+ def fits_without_mask (height : int ) -> bool :
416+ return row_stride * height <= available_data_size
417+
418+ candidate_heights : list [int ] = []
419+ if absolute_height % 2 == 0 :
420+ candidate_heights .append (absolute_height // 2 )
421+ candidate_heights .append (absolute_height )
422+ if entry_height > 0 :
423+ candidate_heights .append (entry_height )
424+
425+ for height in candidate_heights :
426+ if height > 0 and (exact_with_mask (height ) or exact_without_mask (height )):
427+ return height
428+
429+ for height in candidate_heights :
430+ if height > 0 and (fits_with_mask (height ) or fits_without_mask (height )):
431+ return height
432+
433+ raise ValueError ("DIB cursor pixel data is truncated" )
434+
435+
379436def _decode_indexed_dib_pixel (payload : bytes , row_start : int , x : int , bit_count : int ) -> int :
380437 if bit_count == 8 :
381438 return payload [row_start + x ]
0 commit comments