Skip to content

Commit 70e3e4f

Browse files
committed
BMP: Add 4-bit RLE decoder
1 parent 79b3b00 commit 70e3e4f

File tree

1 file changed

+69
-4
lines changed

1 file changed

+69
-4
lines changed

src/PIL/BmpImagePlugin.py

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,9 @@ def _bitmap(self, header=0, offset=0):
212212
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
213213
raw_mode, self.mode = "BGRA", "RGBA"
214214
elif file_info["compression"] == self.RLE8:
215-
decoder_name = "bmp_rle"
215+
decoder_name = "bmp_rle8"
216+
elif file_info["compression"] == self.RLE4:
217+
decoder_name = "bmp_rle4"
216218
else:
217219
raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
218220

@@ -227,7 +229,7 @@ def _bitmap(self, header=0, offset=0):
227229
palette = read(padding * file_info["colors"])
228230
greyscale = True
229231
indices = (
230-
(0, 255)
232+
(0, file_info["colors"])
231233
if file_info["colors"] == 2
232234
else list(range(file_info["colors"]))
233235
)
@@ -276,7 +278,7 @@ def _open(self):
276278
self._bitmap(offset=offset)
277279

278280

279-
class BmpRleDecoder(ImageFile.PyDecoder):
281+
class BmpRle8Decoder(ImageFile.PyDecoder):
280282
_pulls_fd = True
281283

282284
def decode(self, buffer):
@@ -328,6 +330,68 @@ def decode(self, buffer):
328330
return -1, 0
329331

330332

333+
class BmpRle4Decoder(ImageFile.PyDecoder):
334+
_pulls_fd = True
335+
336+
def decode(self, buffer):
337+
data = bytearray()
338+
x = 0
339+
while len(data) < self.state.xsize * self.state.ysize:
340+
pixels = self.fd.read(1)
341+
byte = self.fd.read(1)
342+
if not pixels or not byte:
343+
break
344+
num_pixels = pixels[0]
345+
if num_pixels:
346+
# encoded mode
347+
if x + num_pixels > self.state.xsize:
348+
# Too much data for row
349+
num_pixels = max(0, self.state.xsize - x)
350+
first_pixel = o8(byte[0] >> 4)
351+
second_pixel = o8(byte[0] & 0x0f)
352+
for index in range(num_pixels):
353+
if index % 2 == 0:
354+
data += first_pixel
355+
else:
356+
data += second_pixel
357+
x += num_pixels
358+
else:
359+
if byte[0] == 0:
360+
# end of line
361+
while len(data) % self.state.xsize != 0:
362+
data += b"\x00"
363+
x = 0
364+
elif byte[0] == 1:
365+
# end of bitmap
366+
break
367+
elif byte[0] == 2:
368+
# delta
369+
bytes_read = self.fd.read(2)
370+
if len(bytes_read) < 2:
371+
break
372+
right, up = self.fd.read(2)
373+
data += b"\x00" * (right + up * self.state.xsize)
374+
x = len(data) % self.state.xsize
375+
else:
376+
# absolute mode (2 pixels per byte)
377+
total_bytes_to_read = byte[0] // 2
378+
bytes_read = self.fd.read(total_bytes_to_read)
379+
for byte_read in bytes_read:
380+
first_pixel = o8(byte_read >> 4)
381+
data += first_pixel
382+
second_pixel = o8(byte_read & 0x0f)
383+
data += second_pixel
384+
if len(bytes_read) < total_bytes_to_read:
385+
break
386+
x += byte[0]
387+
388+
# align to 16-bit word boundary
389+
if self.fd.tell() % 2 != 0:
390+
self.fd.seek(1, os.SEEK_CUR)
391+
rawmode = "L" if self.mode == "L" else "P"
392+
self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1]))
393+
return -1, 0
394+
331395
# =============================================================================
332396
# Image plugin for the DIB format (BMP alias)
333397
# =============================================================================
@@ -433,7 +497,8 @@ def _save(im, fp, filename, bitmap_header=True):
433497

434498
Image.register_mime(BmpImageFile.format, "image/bmp")
435499

436-
Image.register_decoder("bmp_rle", BmpRleDecoder)
500+
Image.register_decoder("bmp_rle8", BmpRle8Decoder)
501+
Image.register_decoder("bmp_rle4", BmpRle4Decoder)
437502

438503
Image.register_open(DibImageFile.format, DibImageFile, _dib_accept)
439504
Image.register_save(DibImageFile.format, _dib_save)

0 commit comments

Comments
 (0)