Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix error with loading paletted BMP images using Pillow #388

Merged
merged 8 commits into from
Oct 1, 2018
Merged
3 changes: 3 additions & 0 deletions CONTRIBUTORS.txt
Expand Up @@ -49,3 +49,6 @@

- Zulko
Various contribution related to the ffmpeg plugin.

- Addison Elliott
Add support for 8-bit paletted BMPs
2 changes: 1 addition & 1 deletion imageio/core/fetching.py
Expand Up @@ -161,7 +161,7 @@ def _fetch_file(url, file_name, print_destination=True):
for tries in range(4):
try:
# Checking file size and displaying it alongside the download url
remote_file = urlopen(url, timeout=5.)
remote_file = urlopen(url, timeout=5.0)
file_size = int(remote_file.headers["Content-Length"].strip())
size_str = _sizeof_fmt(file_size)
print("Try %i. Download from %s (%s)" % (tries + 1, url, size_str))
Expand Down
8 changes: 6 additions & 2 deletions imageio/plugins/ffmpeg.py
Expand Up @@ -563,12 +563,16 @@ def _load_infos(self):
if self.request._video:
ffmpeg_err = (
"FFMPEG STDERR OUTPUT:\n"
+ self._stderr_catcher.get_text(.1)
+ self._stderr_catcher.get_text(0.1)
+ "\n"
)
if "darwin" in sys.platform:
if "Unknown input format: 'avfoundation'" in ffmpeg_err:
ffmpeg_err += "Try installing FFMPEG using " "home brew to get a version with " "support for cameras."
ffmpeg_err += (
"Try installing FFMPEG using "
"home brew to get a version with "
"support for cameras."
)
raise IndexError(
"No video4linux camera at %s.\n\n%s"
% (self.request._video, ffmpeg_err)
Expand Down
24 changes: 20 additions & 4 deletions imageio/plugins/pillow.py
Expand Up @@ -122,6 +122,13 @@ def _open(self, pilmode=None, as_gray=False):
self._im = factory(self._fp, "")
if hasattr(Image, "_decompression_bomb_check"):
Image._decompression_bomb_check(self._im.size)
# Save the raw mode used by the palette for a BMP because it may not be the number of channels
# When the data is read, imageio hands the palette to PIL to handle and clears the rawmode argument
# However, there is a bug in PIL with handling animated GIFs with a different color palette on each frame.
# This issue is resolved by using the raw palette data but the rawmode information is now lost. So we
# store the raw mode for later use
if self._im.palette and self._im.palette.dirty:
self._im.palette.rawmode_saved = self._im.palette.rawmode
pil_try_read(self._im)
# Store args
self._kwargs = dict(
Expand Down Expand Up @@ -161,6 +168,8 @@ def _get_data(self, index):
while i < index: # some formats need to be read in sequence
i += 1
self._seek(i)
if self._im.palette and self._im.palette.dirty:
self._im.palette.rawmode_saved = self._im.palette.rawmode
self._im.getdata()[0]
im = pil_get_frame(self._im, **self._kwargs)
return im, self._im.info
Expand Down Expand Up @@ -556,14 +565,21 @@ def pil_get_frame(im, is_gray=None, as_gray=None, mode=None, dtype=None):
frame = im.convert("RGBA")
elif im.palette.mode in ("RGB", "RGBA"):
# We can do this ourselves. Pillow seems to sometimes screw
# this up if a multi-gif has a pallete for each frame ...
# this up if a multi-gif has a palette for each frame ...
# Create palette array
p = np.frombuffer(im.palette.getdata()[1], np.uint8)
# Restore the raw mode that was saved to be used to parse the palette
if hasattr(im.palette, "rawmode_saved"):
im.palette.rawmode = im.palette.rawmode_saved
mode = im.palette.rawmode if im.palette.rawmode else im.palette.mode
nchannels = len(mode)
# Shape it.
nchannels = len(im.palette.mode)
p.shape = -1, nchannels
if p.shape[1] == 3:
p = np.column_stack((p, 255 * np.ones(p.shape[0], p.dtype)))
if p.shape[1] == 3 or (p.shape[1] == 4 and mode[-1] == "X"):
p = np.column_stack((p[:, :3], 255 * np.ones(p.shape[0], p.dtype)))
# Swap the axes if the mode is in BGR and not RGB
if mode.startswith("BGR"):
p = p[:, [2, 1, 0]] if p.shape[1] == 3 else p[:, [2, 1, 0, 3]]
# Apply palette
frame_paletted = np.array(im, np.uint8)
try:
Expand Down
10 changes: 10 additions & 0 deletions tests/test_pillow.py
Expand Up @@ -367,6 +367,15 @@ def test_inside_zipfile():
imageio.imread(fname + "/" + name)


def test_bmp():
need_internet()
fname = get_remote_file("images/scribble_P_RGB.bmp", test_dir)

imageio.imread(fname)
imageio.imread(fname, pilmode="RGB")
imageio.imread(fname, pilmode="RGBA")


def test_scipy_imread_compat():
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.misc.imread.html
# https://github.com/scipy/scipy/blob/41a3e69ca3141d8bf996bccb5eca5fc7bbc21a51/scipy/misc/pilutil.py#L111
Expand Down Expand Up @@ -402,4 +411,5 @@ def test_scipy_imread_compat():
# test_inside_zipfile()
# test_png()
# test_animated_gif()
# test_bmp()
run_tests_if_main()