Skip to content

Commit

Permalink
Fix error with loading paletted BMP images using Pillow (#388)
Browse files Browse the repository at this point in the history
* Create test that fails to load a paletted BMP image in Pillow

* Apply hacky fix to allow loading paletted BMPs

* Swap axes for BGR modes

* Final touches to get it working when PR is made

* Add name to contributors

* Small change

Should force CI to rerun and succeed...

* Remove variable assignments

Not used, caused error in CI

* Reformatted files using black

Believe it is causing issues with getting CI to pass
  • Loading branch information
addisonElliott authored and almarklein committed Oct 1, 2018
1 parent 2d58975 commit 1e08c70
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 7 deletions.
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()

0 comments on commit 1e08c70

Please sign in to comment.