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

BmpImagePlugin - Unsupported 32bpp DIBs with Alpha #8594

Open
rozniak opened this issue Dec 13, 2024 · 10 comments · May be fixed by #8602
Open

BmpImagePlugin - Unsupported 32bpp DIBs with Alpha #8594

rozniak opened this issue Dec 13, 2024 · 10 comments · May be fixed by #8602
Labels

Comments

@rozniak
Copy link

rozniak commented Dec 13, 2024

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
--------------------------------------------------------------------
@rozniak
Copy link
Author

rozniak commented Dec 13, 2024

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. 😝

@radarhere radarhere added the BMP label Dec 13, 2024
@radarhere
Copy link
Member

radarhere commented Dec 13, 2024

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.

@radarhere
Copy link
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.

@rozniak
Copy link
Author

rozniak commented Dec 13, 2024

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.

@radarhere
Copy link
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?

@rozniak
Copy link
Author

rozniak commented Dec 13, 2024

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

@radarhere
Copy link
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")

@rozniak
Copy link
Author

rozniak commented Dec 13, 2024

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. 😛

@radarhere
Copy link
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.

@rozniak
Copy link
Author

rozniak commented Dec 16, 2024

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
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants