Merge pull request #6674 from npjg/main

Added support for reading BMP images with RLE4 compression
This commit is contained in:
Andrew Murray 2022-10-24 17:11:11 +11:00 committed by GitHub
commit 5c9bc6517f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 41 additions and 14 deletions

View File

@ -176,6 +176,11 @@ def test_rle8():
im.load() im.load()
def test_rle4():
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( @pytest.mark.parametrize(
"file_name,length", "file_name,length",
( (

View File

@ -45,9 +45,9 @@ BMP
^^^ ^^^
Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``, 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 or ``RGB`` data. 16-colour images are read as ``P`` images.
is not supported. Support for reading 8-bit run-length encoding was added in Pillow Support for reading 8-bit run-length encoding was added in Pillow 9.1.0.
9.1.0. Support for reading 4-bit run-length encoding was added in Pillow 9.3.0.
Opening Opening
~~~~~~~ ~~~~~~~
@ -56,7 +56,8 @@ The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**compression** **compression**
Set to ``bmp_rle`` if the file is run-length encoded. 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 DDS
^^^ ^^^

View File

@ -211,7 +211,7 @@ class BmpImageFile(ImageFile.ImageFile):
elif file_info["compression"] == self.RAW: elif file_info["compression"] == self.RAW:
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
raw_mode, self.mode = "BGRA", "RGBA" raw_mode, self.mode = "BGRA", "RGBA"
elif file_info["compression"] == self.RLE8: elif file_info["compression"] in (self.RLE8, self.RLE4):
decoder_name = "bmp_rle" decoder_name = "bmp_rle"
else: else:
raise OSError(f"Unsupported BMP compression ({file_info['compression']})") raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
@ -250,16 +250,18 @@ class BmpImageFile(ImageFile.ImageFile):
# ---------------------------- Finally set the tile data for the plugin # ---------------------------- Finally set the tile data for the plugin
self.info["compression"] = file_info["compression"] 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 = [ self.tile = [
( (
decoder_name, decoder_name,
(0, 0, file_info["width"], file_info["height"]), (0, 0, file_info["width"], file_info["height"]),
offset or self.fp.tell(), offset or self.fp.tell(),
( tuple(args),
raw_mode,
((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3),
file_info["direction"],
),
) )
] ]
@ -280,6 +282,7 @@ class BmpRleDecoder(ImageFile.PyDecoder):
_pulls_fd = True _pulls_fd = True
def decode(self, buffer): def decode(self, buffer):
rle4 = self.args[1]
data = bytearray() data = bytearray()
x = 0 x = 0
while len(data) < self.state.xsize * self.state.ysize: while len(data) < self.state.xsize * self.state.ysize:
@ -293,6 +296,15 @@ class BmpRleDecoder(ImageFile.PyDecoder):
if x + num_pixels > self.state.xsize: if x + num_pixels > self.state.xsize:
# Too much data for row # Too much data for row
num_pixels = max(0, self.state.xsize - x) num_pixels = max(0, self.state.xsize - x)
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:
data += byte * num_pixels data += byte * num_pixels
x += num_pixels x += num_pixels
else: else:
@ -314,9 +326,18 @@ class BmpRleDecoder(ImageFile.PyDecoder):
x = len(data) % self.state.xsize x = len(data) % self.state.xsize
else: else:
# absolute mode # absolute mode
bytes_read = self.fd.read(byte[0]) 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 data += bytes_read
if len(bytes_read) < byte[0]: if len(bytes_read) < byte_count:
break break
x += byte[0] x += byte[0]