From 70e3e4fb10d6495e01084ef6abe8902ce464d6c7 Mon Sep 17 00:00:00 2001 From: Nathanael Gentry Date: Fri, 21 Oct 2022 19:44:48 -0400 Subject: [PATCH 1/9] BMP: Add 4-bit RLE decoder --- src/PIL/BmpImagePlugin.py | 73 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 1041ab763d2..107eb344ddd 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -212,7 +212,9 @@ def _bitmap(self, header=0, offset=0): if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" elif file_info["compression"] == self.RLE8: - decoder_name = "bmp_rle" + decoder_name = "bmp_rle8" + elif file_info["compression"] == self.RLE4: + decoder_name = "bmp_rle4" else: raise OSError(f"Unsupported BMP compression ({file_info['compression']})") @@ -227,7 +229,7 @@ def _bitmap(self, header=0, offset=0): palette = read(padding * file_info["colors"]) greyscale = True indices = ( - (0, 255) + (0, file_info["colors"]) if file_info["colors"] == 2 else list(range(file_info["colors"])) ) @@ -276,7 +278,7 @@ def _open(self): self._bitmap(offset=offset) -class BmpRleDecoder(ImageFile.PyDecoder): +class BmpRle8Decoder(ImageFile.PyDecoder): _pulls_fd = True def decode(self, buffer): @@ -328,6 +330,68 @@ def decode(self, buffer): return -1, 0 +class BmpRle4Decoder(ImageFile.PyDecoder): + _pulls_fd = True + + def decode(self, buffer): + data = bytearray() + x = 0 + while len(data) < self.state.xsize * self.state.ysize: + pixels = self.fd.read(1) + byte = self.fd.read(1) + if not pixels or not byte: + break + num_pixels = pixels[0] + if num_pixels: + # encoded mode + if x + num_pixels > self.state.xsize: + # Too much data for row + num_pixels = max(0, self.state.xsize - x) + first_pixel = o8(byte[0] >> 4) + second_pixel = o8(byte[0] & 0x0f) + for index in range(num_pixels): + if index % 2 == 0: + data += first_pixel + else: + data += second_pixel + x += num_pixels + else: + if byte[0] == 0: + # end of line + while len(data) % self.state.xsize != 0: + data += b"\x00" + x = 0 + elif byte[0] == 1: + # end of bitmap + break + elif byte[0] == 2: + # delta + bytes_read = self.fd.read(2) + if len(bytes_read) < 2: + break + right, up = self.fd.read(2) + data += b"\x00" * (right + up * self.state.xsize) + x = len(data) % self.state.xsize + else: + # absolute mode (2 pixels per byte) + total_bytes_to_read = byte[0] // 2 + bytes_read = self.fd.read(total_bytes_to_read) + for byte_read in bytes_read: + first_pixel = o8(byte_read >> 4) + data += first_pixel + second_pixel = o8(byte_read & 0x0f) + data += second_pixel + if len(bytes_read) < total_bytes_to_read: + break + x += byte[0] + + # 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" + self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1])) + return -1, 0 + # ============================================================================= # Image plugin for the DIB format (BMP alias) # ============================================================================= @@ -433,7 +497,8 @@ def _save(im, fp, filename, bitmap_header=True): Image.register_mime(BmpImageFile.format, "image/bmp") -Image.register_decoder("bmp_rle", BmpRleDecoder) +Image.register_decoder("bmp_rle8", BmpRle8Decoder) +Image.register_decoder("bmp_rle4", BmpRle4Decoder) Image.register_open(DibImageFile.format, DibImageFile, _dib_accept) Image.register_save(DibImageFile.format, _dib_save) From c2e9c66fcd0020826d708c6cca43c1cabcd8a44d Mon Sep 17 00:00:00 2001 From: Nathanael Gentry Date: Fri, 21 Oct 2022 19:45:05 -0400 Subject: [PATCH 2/9] Add tests for 4-bit RLE decoder --- Tests/images/hopper_4bit.bmp | Bin 0 -> 8310 bytes Tests/images/hopper_rle4.bmp | Bin 0 -> 8140 bytes Tests/test_file_bmp.py | 7 +++++++ 3 files changed, 7 insertions(+) create mode 100644 Tests/images/hopper_4bit.bmp create mode 100644 Tests/images/hopper_rle4.bmp diff --git a/Tests/images/hopper_4bit.bmp b/Tests/images/hopper_4bit.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8a69b349d30c43ec2af8f94991d5adde83184d88 GIT binary patch literal 8310 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 literal 0 HcmV?d00001 diff --git a/Tests/images/hopper_rle4.bmp b/Tests/images/hopper_rle4.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3c53cc1dd38aa9689c62fd3712958397825d5901 GIT binary patch literal 8140 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 j%rvW>i?-RoB->7MuGY80H#@ literal 0 HcmV?d00001 diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index f6860a9a43d..ec852c905a9 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -175,6 +175,13 @@ def test_rle8(): with pytest.raises(ValueError): im.load() +def test_rle4(): + with Image.open("Tests/images/hopper_rle4.bmp") as im: + assert_image_similar_tofile(im, "Tests/images/hopper_4bit.bmp", 12) + + with Image.open("Tests/images/bmp/g/pal4rle.bmp") as im: + assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12) + @pytest.mark.parametrize( "file_name,length", From 455ffff735795462f81d75b3a101ee422ea172fa Mon Sep 17 00:00:00 2001 From: Nathanael Gentry Date: Fri, 21 Oct 2022 19:45:18 -0400 Subject: [PATCH 3/9] Update documentation for 4-bit RLE decoder --- docs/handbook/image-file-formats.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index dc629666c30..e365aad569d 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -45,9 +45,9 @@ BMP ^^^ Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``, -or ``RGB`` data. 16-colour images are read as ``P`` images. 4-bit run-length encoding -is not supported. Support for reading 8-bit run-length encoding was added in Pillow -9.1.0. +or ``RGB`` data. 16-colour images are read as ``P`` images. +Support for reading 8-bit run-length encoding was added in Pillow 9.1.0. +Support for reading 4-bit run-length encoding was added in Pillow 9.3.0. Opening ~~~~~~~ @@ -56,7 +56,8 @@ The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **compression** - Set to ``bmp_rle`` if the file is run-length encoded. + Set to ``bmp_rle8`` if the file is a 256-color run-length encoded image. + Set to ``bmp_rle4`` if the file is a 16-color run-length encoded image. DDS ^^^ From f2dfd0bfb30b5741589fc124227b4052564ed7dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Oct 2022 23:41:26 +0000 Subject: [PATCH 4/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_file_bmp.py | 1 + src/PIL/BmpImagePlugin.py | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index ec852c905a9..e857f881c58 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -175,6 +175,7 @@ def test_rle8(): with pytest.raises(ValueError): im.load() + def test_rle4(): with Image.open("Tests/images/hopper_rle4.bmp") as im: assert_image_similar_tofile(im, "Tests/images/hopper_4bit.bmp", 12) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 107eb344ddd..d1517df2788 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -348,7 +348,7 @@ def decode(self, buffer): # Too much data for row num_pixels = max(0, self.state.xsize - x) first_pixel = o8(byte[0] >> 4) - second_pixel = o8(byte[0] & 0x0f) + second_pixel = o8(byte[0] & 0x0F) for index in range(num_pixels): if index % 2 == 0: data += first_pixel @@ -379,11 +379,11 @@ def decode(self, buffer): for byte_read in bytes_read: first_pixel = o8(byte_read >> 4) data += first_pixel - second_pixel = o8(byte_read & 0x0f) + second_pixel = o8(byte_read & 0x0F) data += second_pixel if len(bytes_read) < total_bytes_to_read: break - x += byte[0] + x += byte[0] # align to 16-bit word boundary if self.fd.tell() % 2 != 0: @@ -392,6 +392,7 @@ def decode(self, buffer): self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1])) return -1, 0 + # ============================================================================= # Image plugin for the DIB format (BMP alias) # ============================================================================= From cc45886bc3821b60ec911f90dfd77381f7a25f1b Mon Sep 17 00:00:00 2001 From: Nathanael Gentry Date: Fri, 21 Oct 2022 20:59:02 -0400 Subject: [PATCH 5/9] Revert unintentional change --- src/PIL/BmpImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index d1517df2788..9d780bccac3 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -229,7 +229,7 @@ def _bitmap(self, header=0, offset=0): palette = read(padding * file_info["colors"]) greyscale = True indices = ( - (0, file_info["colors"]) + (0, 255) if file_info["colors"] == 2 else list(range(file_info["colors"])) ) From 78430b954956313795a2e76c9398264de3316e0e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Oct 2022 18:50:17 +1100 Subject: [PATCH 6/9] Corrected BMP compression setting [ci skip] --- docs/handbook/image-file-formats.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index e365aad569d..1e79db68bcb 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -56,8 +56,8 @@ The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **compression** - Set to ``bmp_rle8`` if the file is a 256-color run-length encoded image. - Set to ``bmp_rle4`` if the file is a 16-color run-length encoded image. + Set to 1 if the file is a 256-color run-length encoded image. + Set to 2 if the file is a 16-color run-length encoded image. DDS ^^^ From 6c8234bef3d4b3c4c1672cea1103aadfa59f0f7c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 22 Oct 2022 19:54:54 +1100 Subject: [PATCH 7/9] Combined BMP RLE decoders --- src/PIL/BmpImagePlugin.py | 113 ++++++++++++-------------------------- 1 file changed, 34 insertions(+), 79 deletions(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 9d780bccac3..1eeab24a7c0 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -211,10 +211,8 @@ def _bitmap(self, header=0, offset=0): elif file_info["compression"] == self.RAW: if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" - elif file_info["compression"] == self.RLE8: - decoder_name = "bmp_rle8" - elif file_info["compression"] == self.RLE4: - decoder_name = "bmp_rle4" + elif file_info["compression"] in (self.RLE8, self.RLE4): + decoder_name = "bmp_rle" else: raise OSError(f"Unsupported BMP compression ({file_info['compression']})") @@ -252,16 +250,18 @@ def _bitmap(self, header=0, offset=0): # ---------------------------- Finally set the tile data for the plugin self.info["compression"] = file_info["compression"] + args = [raw_mode] + if decoder_name == "bmp_rle": + args.append(file_info["compression"] == self.RLE4) + else: + args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) + args.append(file_info["direction"]) self.tile = [ ( decoder_name, (0, 0, file_info["width"], file_info["height"]), offset or self.fp.tell(), - ( - raw_mode, - ((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3), - file_info["direction"], - ), + tuple(args), ) ] @@ -278,10 +278,11 @@ def _open(self): self._bitmap(offset=offset) -class BmpRle8Decoder(ImageFile.PyDecoder): +class BmpRleDecoder(ImageFile.PyDecoder): _pulls_fd = True def decode(self, buffer): + rle4 = self.args[1] data = bytearray() x = 0 while len(data) < self.state.xsize * self.state.ysize: @@ -295,67 +296,19 @@ def decode(self, buffer): if x + num_pixels > self.state.xsize: # Too much data for row num_pixels = max(0, self.state.xsize - x) - data += byte * num_pixels - x += num_pixels - else: - if byte[0] == 0: - # end of line - while len(data) % self.state.xsize != 0: - data += b"\x00" - x = 0 - elif byte[0] == 1: - # end of bitmap - break - elif byte[0] == 2: - # delta - bytes_read = self.fd.read(2) - if len(bytes_read) < 2: - break - right, up = self.fd.read(2) - data += b"\x00" * (right + up * self.state.xsize) - x = len(data) % self.state.xsize + if rle4: + first_pixel = o8(byte[0] >> 4) + second_pixel = o8(byte[0] & 0x0F) + for index in range(num_pixels): + if index % 2 == 0: + data += first_pixel + else: + data += second_pixel else: - # absolute mode - bytes_read = self.fd.read(byte[0]) - data += bytes_read - if len(bytes_read) < byte[0]: - break - x += byte[0] - - # 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" - self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1])) - return -1, 0 - - -class BmpRle4Decoder(ImageFile.PyDecoder): - _pulls_fd = True - - def decode(self, buffer): - data = bytearray() - x = 0 - while len(data) < self.state.xsize * self.state.ysize: - pixels = self.fd.read(1) - byte = self.fd.read(1) - if not pixels or not byte: - break - num_pixels = pixels[0] - if num_pixels: - # encoded mode - if x + num_pixels > self.state.xsize: - # Too much data for row - num_pixels = max(0, self.state.xsize - x) - first_pixel = o8(byte[0] >> 4) - second_pixel = o8(byte[0] & 0x0F) - for index in range(num_pixels): - if index % 2 == 0: - data += first_pixel - else: - data += second_pixel + data += byte * num_pixels x += num_pixels else: + # absolute mode if byte[0] == 0: # end of line while len(data) % self.state.xsize != 0: @@ -373,15 +326,18 @@ def decode(self, buffer): data += b"\x00" * (right + up * self.state.xsize) x = len(data) % self.state.xsize else: - # absolute mode (2 pixels per byte) - total_bytes_to_read = byte[0] // 2 - bytes_read = self.fd.read(total_bytes_to_read) - for byte_read in bytes_read: - first_pixel = o8(byte_read >> 4) - data += first_pixel - second_pixel = o8(byte_read & 0x0F) - data += second_pixel - if len(bytes_read) < total_bytes_to_read: + if rle4: + # 2 pixels per byte + byte_count = byte[0] // 2 + bytes_read = self.fd.read(byte_count) + for byte_read in bytes_read: + data += o8(byte_read >> 4) + data += o8(byte_read & 0x0F) + else: + byte_count = byte[0] + bytes_read = self.fd.read(byte_count) + data += bytes_read + if len(bytes_read) < byte_count: break x += byte[0] @@ -498,8 +454,7 @@ def _save(im, fp, filename, bitmap_header=True): Image.register_mime(BmpImageFile.format, "image/bmp") -Image.register_decoder("bmp_rle8", BmpRle8Decoder) -Image.register_decoder("bmp_rle4", BmpRle4Decoder) +Image.register_decoder("bmp_rle", BmpRleDecoder) Image.register_open(DibImageFile.format, DibImageFile, _dib_accept) Image.register_save(DibImageFile.format, _dib_save) From d092bb7e0ff341871835d2b9cae742228efef919 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Oct 2022 15:36:31 +1100 Subject: [PATCH 8/9] Only use existing test image --- Tests/images/hopper_4bit.bmp | Bin 8310 -> 0 bytes Tests/images/hopper_rle4.bmp | Bin 8140 -> 0 bytes Tests/test_file_bmp.py | 3 --- 3 files changed, 3 deletions(-) delete mode 100644 Tests/images/hopper_4bit.bmp delete mode 100644 Tests/images/hopper_rle4.bmp diff --git a/Tests/images/hopper_4bit.bmp b/Tests/images/hopper_4bit.bmp deleted file mode 100644 index 8a69b349d30c43ec2af8f94991d5adde83184d88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8310 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 diff --git a/Tests/images/hopper_rle4.bmp b/Tests/images/hopper_rle4.bmp deleted file mode 100644 index 3c53cc1dd38aa9689c62fd3712958397825d5901..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8140 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 j%rvW>i?-RoB->7MuGY80H#@ diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index e857f881c58..5f6d523558a 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -177,9 +177,6 @@ def test_rle8(): def test_rle4(): - with Image.open("Tests/images/hopper_rle4.bmp") as im: - assert_image_similar_tofile(im, "Tests/images/hopper_4bit.bmp", 12) - with Image.open("Tests/images/bmp/g/pal4rle.bmp") as im: assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12) From cf46156345d3df5e9fcff3d03e0a67ef78d557fc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 24 Oct 2022 17:07:57 +1100 Subject: [PATCH 9/9] Moved comment back [ci skip] --- src/PIL/BmpImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 1eeab24a7c0..bdf51aa5cf1 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -308,7 +308,6 @@ def decode(self, buffer): data += byte * num_pixels x += num_pixels else: - # absolute mode if byte[0] == 0: # end of line while len(data) % self.state.xsize != 0: @@ -326,6 +325,7 @@ def decode(self, buffer): data += b"\x00" * (right + up * self.state.xsize) x = len(data) % self.state.xsize else: + # absolute mode if rle4: # 2 pixels per byte byte_count = byte[0] // 2