diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index fa8c11d5a..37770498a 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -6,7 +6,7 @@ from pathlib import Path import pytest -from PIL import IcoImagePlugin, Image, ImageDraw +from PIL import IcoImagePlugin, Image, ImageDraw, ImageFile from .helper import assert_image_equal, assert_image_equal_tofile, hopper @@ -241,3 +241,29 @@ def test_draw_reloaded(tmp_path: Path) -> None: with Image.open(outfile) as im: assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico") + + +def test_truncated_mask() -> None: + # 1 bpp + with open("Tests/images/hopper_mask.ico", "rb") as fp: + data = fp.read() + + ImageFile.LOAD_TRUNCATED_IMAGES = True + data = data[:-3] + + try: + with Image.open(io.BytesIO(data)) as im: + with Image.open("Tests/images/hopper_mask.png") as expected: + assert im.mode == "1" + + # 32 bpp + output = io.BytesIO() + expected = hopper("RGBA") + expected.save(output, "ico", bitmap_format="bmp") + + data = output.getvalue()[:-1] + + with Image.open(io.BytesIO(data)) as im: + assert im.mode == "RGB" + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 086c87b1a..f331caad7 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -235,13 +235,19 @@ class IcoFile: alpha_bytes = self.buf.read(im.size[0] * im.size[1] * 4)[3::4] # convert to an 8bpp grayscale image - mask = Image.frombuffer( - "L", # 8bpp - im.size, # (w, h) - alpha_bytes, # source chars - "raw", # raw decoder - ("L", 0, -1), # 8bpp inverted, unpadded, reversed - ) + try: + mask = Image.frombuffer( + "L", # 8bpp + im.size, # (w, h) + alpha_bytes, # source chars + "raw", # raw decoder + ("L", 0, -1), # 8bpp inverted, unpadded, reversed + ) + except ValueError: + if ImageFile.LOAD_TRUNCATED_IMAGES: + mask = None + else: + raise else: # get AND image from end of bitmap w = im.size[0] @@ -259,19 +265,26 @@ class IcoFile: mask_data = self.buf.read(total_bytes) # convert raw data to image - mask = Image.frombuffer( - "1", # 1 bpp - im.size, # (w, h) - mask_data, # source chars - "raw", # raw decoder - ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed - ) + try: + mask = Image.frombuffer( + "1", # 1 bpp + im.size, # (w, h) + mask_data, # source chars + "raw", # raw decoder + ("1;I", int(w / 8), -1), # 1bpp inverted, padded, reversed + ) + except ValueError: + if ImageFile.LOAD_TRUNCATED_IMAGES: + mask = None + else: + raise # now we have two images, im is XOR image and mask is AND image # apply mask image as alpha channel - im = im.convert("RGBA") - im.putalpha(mask) + if mask: + im = im.convert("RGBA") + im.putalpha(mask) return im