Skip to content

BmpImagePlugin - Unsupported 32bpp DIBs with Alpha #8594

Description

@rozniak

What did you do?

Attempted to read the attached 32bpp DIB with Image.open()

(I've plopped it in a ZIP because GitHub doesn't like me just chucking the DIB itself in here)
sampledib.zip

What did you expect to happen?

The image reads as RGBA

What actually happened?

The image reads as RGB

What are your OS, Python and Pillow versions?

Locally built Pillow from d66c51a

  • OS: Debian 12
  • Python: Pillow 11.1.0.dev0
  • Pillow: Python 3.11.2 (main, Sep 14 2024, 03:00:30) [GCC 12.2.0]
--------------------------------------------------------------------
Pillow 11.1.0.dev0
Python 3.11.2 (main, Sep 14 2024, 03:00:30) [GCC 12.2.0]
--------------------------------------------------------------------
Python executable is /home/rory/source/repos/xfce-winxp-tc/tools/theme/.venv/bin/python3
Environment Python files loaded from /home/rory/source/repos/xfce-winxp-tc/tools/theme/.venv
System Python files loaded from /usr
--------------------------------------------------------------------
Python Pillow modules loaded from /home/rory/source/repos/xfce-winxp-tc/tools/theme/.venv/lib/python3.11/site-packages/PIL
Binary Pillow modules loaded from /home/rory/source/repos/xfce-winxp-tc/tools/theme/.venv/lib/python3.11/site-packages/PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 11.1.0.dev0
--- TKINTER support ok, loaded 8.6
--- FREETYPE2 support ok, loaded 2.12.1
--- LITTLECMS2 support ok, loaded 2.14
--- WEBP support ok, loaded 1.2.4
--- JPEG support ok, compiled for libjpeg-turbo 2.1.5
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.0
--- ZLIB (PNG/ZIP) support ok, loaded 1.2.13
--- LIBTIFF support ok, loaded 4.5.0
*** RAQM (Bidirectional Text) support not installed
*** LIBIMAGEQUANT (Quantization method) support not installed
--- XCB (X protocol) support ok
--------------------------------------------------------------------

Activity

  1. rozniak commented on Dec 13, 2024

    @rozniak
    Author

    Sorry to bother you all with such a niche problem. 😅

    For context, the attached bitmap (DIB) comes from one of the themes in Windows XP - I am trying to look into writing a general tool for converting .MSSTYLES based themes to various Linux toolkits.

    I pinpointed this line of interest in BmpImagePlugin: https://github.com/python-pillow/Pillow/blob/main/src/PIL/BmpImagePlugin.py#L245

    Removing this check for 22 byte header does resolve the issue for me, however it breaks the 32bpp bitmap test case. Unfortunately it seems like the alpha channel behaviour is undocumented, at least I cannot find anything. Both images use the 40-byte BITMAPINFOHEADER, and the only real difference is biSizeImage being set to 0 in the broken bitmap. Not something I think can be relied on to change how the data should be interpreted.

    I don't think this is something easily solvable via parsing the format alone, so I have a fork of Pillow to include BMPA and DIBA as additional formats for explicitly hinting that the alpha channel is to be read.

    I'll submit a corresponding PR, though my solution might be rubbish - I don't use Python all that much. 😝

  2. radarhere commented on Dec 13, 2024

    @radarhere
    Member

    For context, #1125 is the PR that introduced that line of code.

    Can I ask - how do you know the image should be RGBA? Googling, I found https://jumpshare.com/viewer/bmp, and it showed no transparency when given the file.

  3. radarhere commented on Dec 13, 2024

    @radarhere
    Member

    I found https://learn.microsoft.com/en-us/windows/win32/wmdm/-bitmapinfoheader, which describes 32 bitcount as

    The bitmap has a maximum of 2^32 colors. If the biCompression member is BI_RGB, the bmiColors member is NULL. Each DWORD in the bitmap array represents the relative intensities of blue, green, and red, respectively, for a pixel. The high byte in each DWORD is not used.

    So it sounds like RGB is the intention.

  4. rozniak commented on Dec 13, 2024

    @rozniak
    Author

    Can I ask - how do you know the image should be RGBA? Googling, I found https://jumpshare.com/viewer/bmp, and it showed no transparency when given the file.

    Because this DIB comes directly from the resource file luna.msstyles - the theme engine does actually use the alpha channel (they moved to using PNGs in Windows Vista). All themes on Windows XP make use of RGBA bitmaps in the same way.

    Here's a comparison between the current read and what is intended:
    BLUE_RADIOBUTTON25_BMP BLUE_RADIOBUTTON25_BMP

    I know the documentation does not say anything about it, I'm not sure if it was ever formally documented or whether it's just modern MSDN missing stuff. From searching around there's old forum posts and pages on CodeProject (RIP) about these 32bpp bitmaps. I think potentially it is possible to create them in Photoshop, but I'm not sure if that's still the case in newer versions.

  5. radarhere commented on Dec 13, 2024

    @radarhere
    Member

    The question I was trying to ask is - what program are you using to view this file that shows transparency?

    Should I understand your response to mean that you're not viewing them in a program, and your expectation is based on the fact that Windows loads this data and then you see the result in the OS? Not just the transparent corners, but also a transparent inside under some circumstances?

  6. rozniak commented on Dec 13, 2024

    @rozniak
    Author

    You caught me just before I finished writing this update. 😛

    In that post I compared between DIB in Pillow, and DIBA in my branch - so it was based off the expectation of how Windows handles the data. I have just had a look out of curiosity to see if what I read about Photoshop is true, and it does appear to be the case that Photoshop (CS2 here) also recognises the alpha channel:

    image

  7. radarhere commented on Dec 13, 2024

    @radarhere
    Member

    Hmm. I'm concerned about interpreting data that the documentation says to ignore, because that's what third-party software would follow when creating images, and it's possible that what they write to this fourth channel may not actually work as an alpha channel. So it might fix your image, but it might also break others.

    What do you think of this code, that will work with Pillow as-is to read your image as RGBA?

    from PIL import Image, _binary, BmpImagePlugin
    filename = "BLUE_RADIOBUTTON25_BMP.bmp"
    im = Image.open(filename)
    
    if im.format == "DIB" and im.info["compression"] == BmpImagePlugin.BmpImageFile.COMPRESSIONS["RAW"]:
        bits = None
        with open(filename, "rb") as fp:
            header_size = _binary.i32le(fp.read(4))
            if header_size == 40:
                header_data = fp.read(header_size - 4)
                bits = _binary.i16le(header_data, 10)
        if bits == 32:
            im._mode = "RGBA"
            
            args = list(im.tile[0].args)
            args[0] = "BGRA"
            im.tile = [im.tile[0]._replace(args=tuple(args))]
    
    im.save("out.png")
  8. rozniak commented on Dec 13, 2024

    @rozniak
    Author

    Hmm. I'm concerned about interpreting data that the documentation says to ignore, because that's what third-party software would follow when creating images, and it's possible that what they write to this fourth channel may not actually work as an alpha channel. So it might fix your image, but it might also break others.

    This was why I opted to add the extra formats to act as 'hints', rather than trying to guess what the interpretation should be - so it cannot break existing images: rozniak@79683d3

    What do you think of this code, that will work with Pillow as-is to read your image as RGBA?

    It works perfectly. 😄 I mean it's up to you really, I suppose it might not be a very common type of bitmap for people to read so I don't mind working with this snippet in my own project. I did try to keep the change minimal and to avoid breaking any existing images according to spec, depends whether you think it's worth having or not. I suppose there is always this issue thread for future reference otherwise. 😛

  9. radarhere commented on Dec 16, 2024

    @radarhere
    Member

    I've created #8602 as a suggestion - it will retain the current Pillow behaviour by default, but if you run

    from PIL import BmpImagePlugin
    BmpImagePlugin.USE_RAW_ALPHA = True

    first, then the fourth channel will be read.

  10. rozniak commented on Dec 16, 2024

    @rozniak
    Author

    Really appreciate you being so helpful with this problem, I didn't expect much at all considering it's an undocumented use of a niche variant of the file format so I wanted to express my thanks. 😁

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions