Combined BMP RLE decoders

This commit is contained in:
Andrew Murray 2022-10-22 19:54:54 +11:00
parent 78430b9549
commit 6c8234bef3

View File

@ -211,10 +211,8 @@ 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_rle8" decoder_name = "bmp_rle"
elif file_info["compression"] == self.RLE4:
decoder_name = "bmp_rle4"
else: else:
raise OSError(f"Unsupported BMP compression ({file_info['compression']})") raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
@ -252,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"],
),
) )
] ]
@ -278,10 +278,11 @@ class BmpImageFile(ImageFile.ImageFile):
self._bitmap(offset=offset) self._bitmap(offset=offset)
class BmpRle8Decoder(ImageFile.PyDecoder): 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:
@ -295,9 +296,19 @@ class BmpRle8Decoder(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)
data += byte * num_pixels 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
x += num_pixels x += num_pixels
else: else:
# absolute mode
if byte[0] == 0: if byte[0] == 0:
# end of line # end of line
while len(data) % self.state.xsize != 0: while len(data) % self.state.xsize != 0:
@ -315,73 +326,18 @@ class BmpRle8Decoder(ImageFile.PyDecoder):
data += b"\x00" * (right + up * self.state.xsize) data += b"\x00" * (right + up * self.state.xsize)
x = len(data) % self.state.xsize x = len(data) % self.state.xsize
else: else:
# absolute mode if rle4:
bytes_read = self.fd.read(byte[0]) # 2 pixels per byte
data += bytes_read byte_count = byte[0] // 2
if len(bytes_read) < byte[0]: bytes_read = self.fd.read(byte_count)
break for byte_read in bytes_read:
x += byte[0] data += o8(byte_read >> 4)
data += o8(byte_read & 0x0F)
# 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: else:
data += second_pixel byte_count = byte[0]
x += num_pixels bytes_read = self.fd.read(byte_count)
else: data += bytes_read
if byte[0] == 0: if len(bytes_read) < byte_count:
# 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 break
x += byte[0] x += byte[0]
@ -498,8 +454,7 @@ Image.register_extension(BmpImageFile.format, ".bmp")
Image.register_mime(BmpImageFile.format, "image/bmp") Image.register_mime(BmpImageFile.format, "image/bmp")
Image.register_decoder("bmp_rle8", BmpRle8Decoder) Image.register_decoder("bmp_rle", BmpRleDecoder)
Image.register_decoder("bmp_rle4", BmpRle4Decoder)
Image.register_open(DibImageFile.format, DibImageFile, _dib_accept) Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
Image.register_save(DibImageFile.format, _dib_save) Image.register_save(DibImageFile.format, _dib_save)