diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 3fc50ced227..5a09eaaf645 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -202,19 +202,10 @@ def test_rle4() -> None: assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12) -def test_rle4_absolute_odd() -> None: - # An RLE4 absolute run with an odd number of pixels is packed into - # ceil(count / 2) bytes, the final nibble being padding. Build a 3x1 - # image whose single row is one absolute run of 3 pixels (indices 1, 2, 3). - palette = b"\x00" * 4 - rle = ( - b"\x00\x03" # absolute mode, 3 pixels - b"\x12\x30" # nibbles 1, 2, 3 and a padding nibble - b"\x00\x01" # end of bitmap - ) +def encode_rle4(width: int, palette: bytes, rle: bytes) -> io.BytesIO: header = ( o32(40) # header size - + o32(3) # width + + o32(width) # width + o32(1) # height + o16(1) # planes + o16(4) # bits per pixel @@ -225,7 +216,7 @@ def test_rle4_absolute_odd() -> None: + o32(0) # important colors ) offset = 14 + len(header) + len(palette) - data = ( + return io.BytesIO( b"BM" + o32(offset + len(rle)) # file size + o32(0) # reserved @@ -235,7 +226,33 @@ def test_rle4_absolute_odd() -> None: + rle ) - with Image.open(io.BytesIO(data)) as im: + +def test_rle4_black() -> None: + rle = ( + b"\x00\x03" # absolute mode, 3 pixels + b"\x00" # nibbles 0 and 0 + b"\x00\x01" # end of bitmap + ) + b = encode_rle4(2, b"\x00" * 4, rle) + + with Image.open(b) as im: + assert im.mode == "1" + assert [im.getpixel((x, 0)) for x in range(im.width)] == [0, 0] + + +def test_rle4_absolute_odd() -> None: + # An RLE4 absolute run with an odd number of pixels is packed into + # ceil(count / 2) bytes, the final nibble being padding. Build a 3x1 + # image whose single row is one absolute run of 3 pixels (indices 1, 2, 3). + palette = b"\x01" * 4 + rle = ( + b"\x00\x03" # absolute mode, 3 pixels + b"\x12\x30" # nibbles 1, 2, 3 and a padding nibble + b"\x00\x01" # end of bitmap + ) + b = encode_rle4(3, palette, rle) + + with Image.open(b) as im: assert [im.getpixel((x, 0)) for x in range(im.width)] == [1, 2, 3] diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 907931b926e..896c03e562e 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -280,7 +280,7 @@ def _bitmap(self, header: int = 0, offset: int = 0) -> None: # ------- If all colors are gray, white or black, ditch palette if grayscale: - self._mode = "1" if file_info["colors"] == 2 else "L" + self._mode = "1" if file_info["colors"] <= 2 else "L" raw_mode = self.mode else: self._mode = "P" @@ -390,7 +390,7 @@ def decode(self, buffer: Image.DecoderInput) -> tuple[int, int]: # align to 16-bit word boundary if self.fd.tell() % 2 != 0: self.fd.seek(1, os.SEEK_CUR) - rawmode = "L" if self.mode == "L" else "P" + rawmode = "L" if self.mode in {"1", "L"} else "P" self.set_as_raw(bytes(data), rawmode, (0, self.args[-1])) return -1, 0 diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index 203bcac2ca9..a515682093d 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1564,6 +1564,7 @@ static struct { {IMAGING_MODE_1, IMAGING_RAWMODE_1_R, 1, unpack1R}, {IMAGING_MODE_1, IMAGING_RAWMODE_1_IR, 1, unpack1IR}, {IMAGING_MODE_1, IMAGING_RAWMODE_1_8, 8, unpack18}, + {IMAGING_MODE_1, IMAGING_RAWMODE_L, 8, unpack18}, /* grayscale */ {IMAGING_MODE_L, IMAGING_RAWMODE_L_2, 2, unpackL2},