mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-14 13:46:57 +03:00
Merge pull request #6102 from radarhere/bmp_rle8
This commit is contained in:
commit
a921fcbf75
BIN
Tests/images/hopper_rle8.bmp
Normal file
BIN
Tests/images/hopper_rle8.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
Tests/images/hopper_rle8_row_overflow.bmp
Normal file
BIN
Tests/images/hopper_rle8_row_overflow.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
|
@ -40,6 +40,7 @@ def test_questionable():
|
||||||
"rgb32fakealpha.bmp",
|
"rgb32fakealpha.bmp",
|
||||||
"rgb24largepal.bmp",
|
"rgb24largepal.bmp",
|
||||||
"pal8os2sp.bmp",
|
"pal8os2sp.bmp",
|
||||||
|
"pal8rletrns.bmp",
|
||||||
"rgb32bf-xbgr.bmp",
|
"rgb32bf-xbgr.bmp",
|
||||||
]
|
]
|
||||||
for f in get_files("q"):
|
for f in get_files("q"):
|
||||||
|
|
|
@ -4,7 +4,12 @@ import pytest
|
||||||
|
|
||||||
from PIL import BmpImagePlugin, Image
|
from PIL import BmpImagePlugin, Image
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
from .helper import (
|
||||||
|
assert_image_equal,
|
||||||
|
assert_image_equal_tofile,
|
||||||
|
assert_image_similar_tofile,
|
||||||
|
hopper,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_sanity(tmp_path):
|
def test_sanity(tmp_path):
|
||||||
|
@ -125,6 +130,42 @@ def test_rgba_bitfields():
|
||||||
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
|
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
|
||||||
|
|
||||||
|
|
||||||
|
def test_rle8():
|
||||||
|
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
||||||
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
||||||
|
|
||||||
|
# This test image has been manually hexedited
|
||||||
|
# to have rows with too much data
|
||||||
|
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:
|
||||||
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
||||||
|
|
||||||
|
# Signal end of bitmap before the image is finished
|
||||||
|
with open("Tests/images/bmp/g/pal8rle.bmp", "rb") as fp:
|
||||||
|
data = fp.read(1063) + b"\x01"
|
||||||
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"file_name,length",
|
||||||
|
(
|
||||||
|
# EOF immediately after the header
|
||||||
|
("Tests/images/hopper_rle8.bmp", 1078),
|
||||||
|
# EOF during delta
|
||||||
|
("Tests/images/bmp/q/pal8rletrns.bmp", 3670),
|
||||||
|
# EOF when reading data in absolute mode
|
||||||
|
("Tests/images/bmp/g/pal8rle.bmp", 1064),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_rle8_eof(file_name, length):
|
||||||
|
with open(file_name, "rb") as fp:
|
||||||
|
data = fp.read(length)
|
||||||
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def test_offset():
|
def test_offset():
|
||||||
# This image has been hexedited
|
# This image has been hexedited
|
||||||
# to exclude the palette size from the pixel data offset
|
# to exclude the palette size from the pixel data offset
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
|
@ -167,6 +169,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
raise OSError(f"Unsupported BMP pixel depth ({file_info['bits']})")
|
raise OSError(f"Unsupported BMP pixel depth ({file_info['bits']})")
|
||||||
|
|
||||||
# ---------------- Process BMP with Bitfields compression (not palette)
|
# ---------------- Process BMP with Bitfields compression (not palette)
|
||||||
|
decoder_name = "raw"
|
||||||
if file_info["compression"] == self.BITFIELDS:
|
if file_info["compression"] == self.BITFIELDS:
|
||||||
SUPPORTED = {
|
SUPPORTED = {
|
||||||
32: [
|
32: [
|
||||||
|
@ -208,6 +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:
|
||||||
|
decoder_name = "bmp_rle"
|
||||||
else:
|
else:
|
||||||
raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
|
raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
|
||||||
|
|
||||||
|
@ -247,7 +252,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
self.info["compression"] = file_info["compression"]
|
self.info["compression"] = file_info["compression"]
|
||||||
self.tile = [
|
self.tile = [
|
||||||
(
|
(
|
||||||
"raw",
|
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(),
|
||||||
(
|
(
|
||||||
|
@ -271,6 +276,57 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
self._bitmap(offset=offset)
|
self._bitmap(offset=offset)
|
||||||
|
|
||||||
|
|
||||||
|
class BmpRleDecoder(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)
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
self.set_as_raw(bytes(data), ("P", 0, self.args[-1]))
|
||||||
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Image plugin for the DIB format (BMP alias)
|
# Image plugin for the DIB format (BMP alias)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
@ -372,6 +428,8 @@ Image.register_extension(BmpImageFile.format, ".bmp")
|
||||||
|
|
||||||
Image.register_mime(BmpImageFile.format, "image/bmp")
|
Image.register_mime(BmpImageFile.format, "image/bmp")
|
||||||
|
|
||||||
|
Image.register_decoder("bmp_rle", BmpRleDecoder)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user