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

Problem with saving image from clipboard #1293

Closed
sallyruthstruik opened this issue Jun 20, 2015 · 52 comments · Fixed by #1813
Closed

Problem with saving image from clipboard #1293

sallyruthstruik opened this issue Jun 20, 2015 · 52 comments · Fixed by #1813
Labels
Bug Any unexpected behavior, until confirmed feature. Windows

Comments

@sallyruthstruik
Copy link

Hello,

I have Windows 8 x64 system and installed Pillow version 2.8.2
I'm trying to grab image from clipboard. I press "Alt+PrtSc" and then call grabclipboard function, and get such error:

ImageGrab.grabclipboard()
---------------------------------------------------------------------------
IOError                                   Traceback (most recent call last)
<ipython-input-2-c8274e888e6c> in <module>()
      1 from PIL import ImageGrab
      2 
----> 3 ImageGrab.grabclipboard()

C:\Anaconda\lib\site-packages\PIL\ImageGrab.pyc in grabclipboard()
     49         from PIL import BmpImagePlugin
     50         import io
---> 51         return BmpImagePlugin.DibImageFile(io.BytesIO(data))
     52     return data

C:\Anaconda\lib\site-packages\PIL\ImageFile.pyc in __init__(self, fp, filename)
     95 
     96         try:
---> 97             self._open()
     98         except IndexError as v:  # end of data
     99             if Image.DEBUG > 1:

C:\Anaconda\lib\site-packages\PIL\BmpImagePlugin.pyc in _open(self)
    204 
    205     def _open(self):
--> 206         self._bitmap()
    207 
    208 #

C:\Anaconda\lib\site-packages\PIL\BmpImagePlugin.pyc in _bitmap(self, header, offset)
    145                     raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])]
    146                 else:
--> 147                     raise IOError("Unsupported BMP bitfields layout")
    148             else:
    149                 raise IOError("Unsupported BMP bitfields layout")

IOError: Unsupported BMP bitfields layout

But if I call ImageGrab.grab function, screenshot(of whole screen) is saved properly.

@sallyruthstruik
Copy link
Author

UPD It's version problem, when I installed Pillow version 2.6.0 everything worked ok

@hugovk
Copy link
Member

hugovk commented Jun 22, 2015

Reproducible with Pilllow 2.8.0, 2.8.1, 2.8.2.
Not reproducible with Pillow 2.6.0, 2.7.0.

E:\stufftodelete>cat test.py
from PIL import ImageGrab
ImageGrab.grabclipboard()

E:\stufftodelete>pip freeze | grep illow
Pillow==2.8.2

E:\stufftodelete>python test.py
Traceback (most recent call last):
  File "test.py", line 2, in <module>
    ImageGrab.grabclipboard()
  File "C:\Python27\lib\site-packages\PIL\ImageGrab.py", line 51, in grabclipboard
    return BmpImagePlugin.DibImageFile(io.BytesIO(data))
  File "C:\Python27\lib\site-packages\PIL\ImageFile.py", line 97, in __init__
    self._open()
  File "C:\Python27\lib\site-packages\PIL\BmpImagePlugin.py", line 206, in _open
    self._bitmap()
  File "C:\Python27\lib\site-packages\PIL\BmpImagePlugin.py", line 147, in _bitmap
    raise IOError("Unsupported BMP bitfields layout")
IOError: Unsupported BMP bitfields layout

E:\stufftodelete>pip install pillow==2.8.1
Collecting pillow==2.8.1
  Using cached Pillow-2.8.1-cp27-none-win_amd64.whl
Installing collected packages: pillow
  Found existing installation: Pillow 2.8.2
    Uninstalling Pillow-2.8.2:
      Successfully uninstalled Pillow-2.8.2
Successfully installed pillow-2.8.1

E:\stufftodelete>python test.py
Traceback (most recent call last):
  File "test.py", line 2, in <module>
    ImageGrab.grabclipboard()
  File "C:\Python27\lib\site-packages\PIL\ImageGrab.py", line 51, in grabclipboard
    return BmpImagePlugin.DibImageFile(io.BytesIO(data))
  File "C:\Python27\lib\site-packages\PIL\ImageFile.py", line 97, in __init__
    self._open()
  File "C:\Python27\lib\site-packages\PIL\BmpImagePlugin.py", line 206, in _open
    self._bitmap()
  File "C:\Python27\lib\site-packages\PIL\BmpImagePlugin.py", line 147, in _bitmap
    raise IOError("Unsupported BMP bitfields layout")
IOError: Unsupported BMP bitfields layout

E:\stufftodelete>pip install pillow==2.8.0
Collecting pillow==2.8.0
  Downloading Pillow-2.8.0-cp27-none-win_amd64.whl (1.3MB)
    100% |################################| 1.3MB 386kB/s
Installing collected packages: pillow
  Found existing installation: Pillow 2.8.1
    Uninstalling Pillow-2.8.1:
      Successfully uninstalled Pillow-2.8.1
Successfully installed pillow-2.8.0

E:\stufftodelete>python test.py
Traceback (most recent call last):
  File "test.py", line 2, in <module>
    ImageGrab.grabclipboard()
  File "C:\Python27\lib\site-packages\PIL\ImageGrab.py", line 51, in grabclipboard
    return BmpImagePlugin.DibImageFile(io.BytesIO(data))
  File "C:\Python27\lib\site-packages\PIL\ImageFile.py", line 97, in __init__
    self._open()
  File "C:\Python27\lib\site-packages\PIL\BmpImagePlugin.py", line 206, in _open
    self._bitmap()
  File "C:\Python27\lib\site-packages\PIL\BmpImagePlugin.py", line 147, in _bitmap
    raise IOError("Unsupported BMP bitfields layout")
IOError: Unsupported BMP bitfields layout

E:\stufftodelete>pip install pillow==2.7.0
Collecting pillow==2.7.0
  Downloading Pillow-2.7.0-cp27-none-win_amd64.whl (1.3MB)
    100% |################################| 1.3MB 393kB/s
Installing collected packages: pillow
  Found existing installation: Pillow 2.8.0
    Uninstalling Pillow-2.8.0:
      Successfully uninstalled Pillow-2.8.0
Successfully installed pillow-2.7.0

E:\stufftodelete>python test.py

E:\stufftodelete>pip install pillow==2.6.0
Collecting pillow==2.6.0
  Downloading Pillow-2.6.0-cp27-none-win_amd64.whl (1.3MB)
    100% |################################| 1.3MB 405kB/s
Installing collected packages: pillow
  Found existing installation: Pillow 2.7.0
    Uninstalling Pillow-2.7.0:
      Successfully uninstalled Pillow-2.7.0
Successfully installed pillow-2.6.0

E:\stufftodelete>python test.py

@hugovk
Copy link
Member

hugovk commented Jun 22, 2015

Pillow 2.7.0 was released on 1st January this year, and 2.8.0 was released on 1st April.

PR #1125 may have caused it. Ping @artscoop.

It'd be great to get this fixed for 2.9.0, due out on 1st July.

The good news is we now have a Windows-based CI to run unit tests, so if it's possible to simulate Alt+PrtSc (or just PrtSc, which also triggers it) on AppVeyor then this should definitely be unit-tested.

@hugovk hugovk added Windows Bug Any unexpected behavior, until confirmed feature. labels Jun 22, 2015
@radarhere
Copy link
Member

Running OS X, I can reproduce the error using one of the files in the test suite - Tests/images/bmp/g/rgb32bf.bmp

Interestingly though, it's listed as unsupported.

@wiredfool
Copy link
Member

Unfortunately, the ImageGrab functions don't work on the windows CI, likely because there's something screwy with the graphics context on a headless vm.

@hugovk
Copy link
Member

hugovk commented Jun 25, 2015

Notes:

This can be partially fixed by adding this elif near here:

                elif file_info['bits'] == 32 and file_info['rgb_mask'] == (0xff0000, 0xff00, 0xff):
                    pass
                else:
                    raise IOError("Unsupported BMP bitfields layout")
            else:
                raise IOError("Unsupported BMP bitfields layout")

That will return an image, but the resulting image is truncated. It can be shown or saved only if LOAD_TRUNCATED_IMAGES is true, but the image is one pixel shifted left and missing the top line.

This is because, for example, self.tile is:
[('raw', (0, 0, 3360, 1050), 56L, ('BGRX', 13440, -1))]
instead of:
[('raw', (0, 0, 3360, 1050), 52L, ('BGRX', 13440, -1))]

That third value in both cases is offset or self.fp.tell(), the problem with 2.8.0 is that an extra read had been made to load in a file_info['a_mask'], which doesn't make sense for this format, and putting the file pointer at the wrong place.

We should only be reading that alpha value when needed.

@techtonik
Copy link
Contributor

The regression appears with 3.0.0 too. 2.7.0 works ok.

@techtonik
Copy link
Contributor

So, any news on that?

@artscoop
Copy link
Contributor

Ok I understand what is going on.
I rewrote part of the BMP loading process.
And I made sure it worked on supported bitmap files, fixed bogus behaviour from previous versions, but it did not have to work with unsupported files.
However @radarhere , the cause of the error you've been experiencing with this file may be... let's say, different.

@artscoop
Copy link
Contributor

Now I remember: the file rgb32bf.bmp is unsupported because it has a bitmask not supported by Pillow. Supported masks for 32-bit true color images have classic boundaries, with RGB channels in RGB or BGR order.
Here we have an esoteric RGB/A mask (the image is read correctly by XNView though), which has no matching dialect in PIL definitions for unpacking pixels.
Therefore I have been able to load this image, but with wrong colors (for instance, we have a problem with B and G channels)

@techtonik
Copy link
Contributor

This bug can be closed. This works with 3.2.0:

>>> from PIL import ImageGrab
>>> ImageGrab.grabclipboard()
>>>

@techtonik
Copy link
Contributor

It can be renamed "ImageGrab.grabclipboard() problem on Windows", because there are other issues there.

@hugovk
Copy link
Member

hugovk commented Apr 6, 2016

This bug is still present: I can reproduce it as described in the original report with Pillow 3.2.0 (Python 2.7, Windows 7).

@techtonik
Copy link
Contributor

@hugovk can you confirm it with the following actions?

1 open mspaint
2 alt+prtscr
3 run the code

from PIL import ImageGrab
im = ImageGrab.grabclipboard()
if im is None:
  print('Error: No image data in clipboard')

Python 2.7, 32-bit Vista, Pillow 3.2.0 works.

@hugovk
Copy link
Member

hugovk commented Apr 6, 2016

@techtonik

  • If I alt+prtscr the cmd.exe window, I get IOError: Unsupported BMP bitfields layout (like the original report).
  • If I alt+prtscr the Paint window, I get an im (e.g. <PIL.BmpImagePlugin.DibImageFile image mode=RGBA size=886x754 at 0x2A7FE10>) but im.show() gives IOError: image file is truncated (3540 bytes not processed) (like 3.2.0 DIB saving on Windows fails "image file is truncated" error #1810).

@techtonik
Copy link
Contributor

@hugovk yep, the same here, so looks like this issue is only about console window.

@wiredfool
Copy link
Member

What is the bitfield layout? If it's sane, there's no reason that we can't support it.

@techtonik
Copy link
Contributor

@wiredfool any ideas where to look for it? Let me update the Traceback for 3.2.0 first.

Traceback (most recent call last):
  File "code.py", line 2, in <module>
    im = ImageGrab.grabclipboard()
  File "C:\Python27\lib\site-packages\PIL\ImageGrab.py", line 60, in grabclipboard
    return BmpImagePlugin.DibImageFile(io.BytesIO(data))
  File "C:\Python27\lib\site-packages\PIL\ImageFile.py", line 97, in __init__
    self._open()
  File "C:\Python27\lib\site-packages\PIL\BmpImagePlugin.py", line 211, in _open
    self._bitmap()
  File "C:\Python27\lib\site-packages\PIL\BmpImagePlugin.py", line 152, in _bitmap
    raise IOError("Unsupported BMP bitfields layout")
IOError: Unsupported BMP bitfields layout

@techtonik
Copy link
Contributor

Printing file_info contents just before raise:

{
    'direction': -1,
    'r_mask': 16711680,
    'compression': 3,
    'pixels_per_meter': (0, 0),
    'palette_padding': 4,
    'g_mask': 65280,
    'a_mask': 6908265,
    'rgba_mask': (16711680, 65280, 255, 6908265),
    'header_size': 40,
    'height': 736,
    'width': 1066,
    'colors': 4294967296L,
    'b_mask': 255,
    'rgb_mask': (16711680, 65280, 255),
    'planes': 1,
    'y_flip': False,
    'bits': 32,
    'data_size': 3138304,
}

@wiredfool
Copy link
Member

ok, that's a WTF.

The alpha mask is 0x696969, so it's a BRG[WTF] image??

@techtonik
Copy link
Contributor

Screenshot of a console window:

image

@wiredfool
Copy link
Member

Try this:

diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py
index e398445..f7cc203 100644
--- a/PIL/BmpImagePlugin.py
+++ b/PIL/BmpImagePlugin.py
@@ -142,6 +142,10 @@ class BmpImageFile(ImageFile.ImageFile):
                 (16, (0xf800, 0x7e0, 0x1f)): "BGR;16",
                 (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"
             }
+            # work around really strange alpha reported in windows screenshot bmps.
+            if file_info['bits'] == 32 and file_info['a_mask'] == 0x696969:
+                file_info['bits'] = 24
+                
             if file_info['bits'] in SUPPORTED:
                 if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
                     raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])]

@techtonik
Copy link
Contributor

The mask of the image is:

(0xff0000, 0xff00, 0xff, 0x696969)

while supported ones for 32 bits are:

(0xff0000, 0xff00, 0xff, 0x0)
(0xff0000, 0xff00, 0xff, 0xff000000)
(0x0, 0x0, 0x0, 0x0)

I can't find any references of that magic number. Maybe Pillow gets that number wrong?

@wiredfool
Copy link
Member

That magic number makes no sense at all from a masking point of view.

@wiredfool
Copy link
Member

From https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx , in the section on the 40 byte headers, which is the type that we're using here:

BI_BITFIELDS Specifies that the bitmap is not compressed and that the color table consists of three DWORD color masks that specify the red, green, and blue components, respectively, of each pixel. This is valid when used with 16- and 32-bpp bitmaps.

So, when we have a 40 byte header, we can't read the 4 components of the mask.

@wiredfool
Copy link
Member

Ok, I think this patch should do it, where the previous one would only work if the first pixel color was 0x696969.

diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py
index e398445..a3e39c1 100644
--- a/PIL/BmpImagePlugin.py
+++ b/PIL/BmpImagePlugin.py
@@ -109,7 +109,11 @@ class BmpImageFile(ImageFile.ImageFile):
                         for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
                             file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
                     else:
-                        for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']:
+                        # 40 byte headers only have the three components in the bitfields masks,
+                        # ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx
+                        # See also https://github.com/python-pillow/Pillow/issues/1293
+                        file_info['a_mask'] = 0x0
+                        for mask in ['r_mask', 'g_mask', 'b_mask']:
                             file_info[mask] = i32(read(4))
                     file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'])
                     file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask'])

@wiredfool
Copy link
Member

Hmm. Looks like there is an alpha channel involved, and it's inverted -- e.g. 00 is solid and FF is transparent.

wiredfool added a commit to wiredfool/Pillow that referenced this issue Apr 6, 2016
@techtonik
Copy link
Contributor

This patch works too.

wiredfool added a commit to wiredfool/Pillow that referenced this issue Apr 6, 2016
The DIB image format uses the 40 byte BMP header, followed by 3 masks
for RGB channels. We were reading 4 masks, consuming the first pixel
of the image.

Mostly fixes issue python-pillow#1293. Remaining issue: alpha channel is actually a
transparency mask.
@techtonik
Copy link
Contributor

I can confirm this is fixed for me.

@jenstroeger
Copy link

jenstroeger commented Apr 14, 2016

I haven't built this from source. When will the DIB/DMP fix make it into the Python module and become available through pip?

Interestingly, Pillow 3.0.0 works fine on Mac/MacPorts, yet Pillow 3.2.0 fails on Linux with the above issue. BMP does not have a dependency to a particular host library, does it?

The current traceback for Pillow 3.2.0 on my Linux host is as follows, where the input byte array imgbytes is a DIB/BMP image:

Traceback (most recent call last):
...
  File "/var/www/.../lib64/python3.4/site-packages/srv-0.1-py3.4.egg/srv/utils/images.py", line 64, in convert_image
    img = Image.open(io.BytesIO(imgbytes))
  File "/var/www/.../lib64/python3.4/site-packages/PIL/Image.py", line 2299, in open
    im = _open_core(fp, filename, prefix)
  File "/var/www/..../lib64/python3.4/site-packages/PIL/Image.py", line 2289, in _open_core
    im = factory(fp, filename)
  File "/var/www/.../lib64/python3.4/site-packages/PIL/ImageFile.py", line 97, in __init__
    self._open()
  File "/var/www/.../lib64/python3.4/site-packages/PIL/BmpImagePlugin.py", line 199, in _open
    self._bitmap(offset=offset)
  File "/var/www/.../lib64/python3.4/site-packages/PIL/BmpImagePlugin.py", line 152, in _bitmap
    raise IOError("Unsupported BMP bitfields layout")
OSError: Unsupported BMP bitfields layout

Applying change wiredfool@e84e029 I can confirm it works. So I guess I'll just wait for the next release cycle.

@hugovk
Copy link
Member

hugovk commented Apr 14, 2016

Releases are quarterly; next one is due for 1st July.

@wiredfool
Copy link
Member

Note that the current fix isn't actually correct, I believe that it's misinterpreting some active mask as transparency.

@jenstroeger
Copy link

jenstroeger commented Apr 14, 2016

Ah. I noticed black backgrounds in bmp to png converted images, perhaps that'd explain them... 🤓

@wiredfool
Copy link
Member

I need to dig a bit more about the actual definition of what a DIB file is, and what the data in the spot that would normally be an alpha channel actually corresponds to. I think that the fix is actually correct for the RGB channels, for what that's worth.

@wiredfool
Copy link
Member

BMP does not depend on any external library. (This particular version is essentially a header + raw image bytes)

@jenstroeger
Copy link

jenstroeger commented Apr 14, 2016

Glad to hear that there are no external dependencies for BMP. I noticed the above problem because it seems that DIB is the internal bitmap representation for graphic shapes in OpenOffice (see XBitmap here).

Microsoft has an overview of DIBs here and a more historical piece here, and the actual “Bitmap Reference” is here. I hope this helps.

@wiredfool
Copy link
Member

I've seen most of those links. What none of them are saying is the interpretation of the extra field of an RGBQuad, other than 'reserved'. There's possibly something newer that's done something documented with it, or we need to simply ignore it and just use the 'RGB' section of the image.

@wiredfool wiredfool reopened this Apr 15, 2016
@techtonik
Copy link
Contributor

@wiredfool lets close this ticket and move the discussion to #1811. Because saved .bmp is fine. There might be a bug in conversion routines.

@hugovk hugovk closed this as completed Apr 18, 2016
@wiredfool wiredfool reopened this Apr 19, 2016
@wiredfool
Copy link
Member

#1293 (comment)

@techtonik
Copy link
Contributor

@wiredfool "IOError: Unsupported BMP bitfields layout" problem is fixed, and it becomes hard to follow this ticket.

wiredfool added a commit to wiredfool/Pillow that referenced this issue Apr 19, 2016
The DIB image format uses the 40 byte BMP header, followed by 3 masks
for RGB channels. We were reading 4 masks, consuming the first pixel
of the image.

Mostly fixes issue python-pillow#1293. Remaining issue: alpha channel is actually a
transparency mask.
@wiredfool
Copy link
Member

Respinning this with #1847.

Fahad-Alsaidi pushed a commit to Fahad-Alsaidi/Pillow that referenced this issue Dec 13, 2016
The DIB image format uses the 40 byte BMP header, followed by 3 masks
for RGB channels. We were reading 4 masks, consuming the first pixel
of the image.

Mostly fixes issue python-pillow#1293. Remaining issue: alpha channel is actually a
transparency mask.
Fahad-Alsaidi pushed a commit to Fahad-Alsaidi/Pillow that referenced this issue Dec 13, 2016
The DIB image format uses the 40 byte BMP header, followed by 3 masks
for RGB channels. We were reading 4 masks, consuming the first pixel
of the image.

Mostly fixes issue python-pillow#1293. Remaining issue: alpha channel is actually a
transparency mask.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Any unexpected behavior, until confirmed feature. Windows
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants