Repository navigation
cur file saved as a png has black pixels where it needs to be transparent #9637
Description
Activity
Hi. This has been raised before in #8594. In that issue, the conclusion was that the specification says it should actually be black. However, we added a setting for this. If you set
USE_RAW_ALPHAto True, then you will get the result you expect.from PIL import Image, BmpImagePlugin BmpImagePlugin.USE_RAW_ALPHA = True im = Image.open("./aero_unavail.cur") im.save("aero_unavail.png")

hi, thank you for the help.
why does this happen for some cur files?https://learn.microsoft.com/en-us/windows/win32/wmdm/-bitmapinfoheader 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 the specification says that the fourth channel should not be used. That means that the image should be rendered as RGB, not RGBA, hence the black.
I can't say why some other software has chosen to ignore this part of the specification.
thank you!
sorry to bother again but the issue persists on other files like the one i attached
(attached as txt instead of .cur, again)Your latest file appears to have a different issue. I still need to investigate this further, but it appears that it contains two images, and one is a mask for the other.
If you would like an immediate fix, this code
from PIL import Image, ImageOps, BmpImagePlugin def read(path): BmpImagePlugin.USE_RAW_ALPHA = True im = Image.open(path) if im.mode == "1": out = Image.new("LA", im.size) im._size = im.width, im.height * 2 im.tile = [im.tile[0]._replace(extents=(0, 0)+im.size)] and_mask = im.crop((0, out.height, out.width, im.height)) xor_mask = ImageOps.invert(im.crop((0, 0) + out.size)) out.paste(and_mask, mask=xor_mask) return out else: return im read("./default_arrow.cur").save("default_arrow.png") read("./aero_unavail.cur").save("aero_unavail.png")
by "it has another image which is a mask" you mean the single image in the .cur file has a proper and mask no? if that's not the case when i inspect the file with imagemagick i see only 1 image
it contains two images, and one is a mask for the other.
This sentence was just me describing a technical detail.
If you're not curious about how data is arranged within an image, feel free to completely ignore it.
If you are curious, then I meant that within the image, there is a black-and-white section, and then another black-and-white section. One section actually does render as black-and-white, in the other, the 'white' pixels re the ones that are shown, and the 'black' pixels are the ones that become transparent. It's how the creators of CUR decided to assemble a black/white/transparent image out of only black-and-white data.
In the end, yes, there is only one image that the user should ever see.
i actually somewhat know about the structure of cur files and i guess you are talking about xor, and masks.
plus i have some questions afaik, the and mask is used as the transparency mask but your code says otherwise, why?
and when this cur file is used, is there any xor operation which involves the xor mask or is it just used as pixel data?also a little off-topic but, does bmp's with any bpp store images as and + xor masks or does it work differently
plus i have some questions afaik, the and mask is used as the transparency mask but your code says otherwise, why?
I expect my use of
ImageOps.invert()has things less clear. I would put together an example withlogical_xor(), but that method only accepts 1 mode images, which wouldn't give the transparent/black/white result we want.does bmp's with any bpp store images as and + xor masks or does it work differently
No, I see nothing about AND and XOR masks at https://en.wikipedia.org/wiki/BMP_file_format or in our current BmpImagePlugin. It does use bit masks, but those are filters used to look at the same set of bytes, rather than two different sets of bytes being combined.
this is going a little off-topic but can you tell me a little bit about what the code snippet you sent is actually doing and what those bit masks are for when they are present.
btw this site says that the pixel data is actually xor and and masks atleast when bpp is 1 (it could be wrong idk) https://www.daubnet.com/en/file-format-cur
I've created #9641. Is default_arrow an image that we can include in our test suite, and distribute under the Pillow license?
You're asking for an explanation of how BmpImagePlugin handles bit masks? It was added in #1125.
Bit masks are used as a form of compression
Pillow/src/PIL/BmpImagePlugin.py
Line 222 in 35d6429
(16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
means that applying bitwise AND 0xF800 against the data gives one band's values, 0x7E0 another and 0x1F the last.Or they are simply a way to rearrange bands, as these examples use the full range of each band, just in a different order.
Pillow/src/PIL/BmpImagePlugin.py
Lines 216 to 219 in 35d6429
(32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR", (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA", (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA", (32, (0xFF000000, 0xFF00, 0xFF, 0xFF0000)): "BGAR", Pillow isn't handing any series of masks arbitrarily though. These entries are matching possible mask values to our 'raw modes', which are then used to unpack the data into more common image modes.
https://www.rw-designer.com/cursor-set/windows-xp-complete-edition the file was taken from here, and the license says the author should be asked for permission if it will be used for purposes other than non-commercial personal usage.
thanks for the explanation of bit masks, i understood how they work. i still don't really get how your code works, you and wikipedia said bmps dont have xor and and masks but your code says otherwise and you are using an inverted xor mask where there should be an inverted and mask.
you and wikipedia said bmps dont have xor and and masks but your code says otherwise
BMP and CUR images are not the same. Wikipedia describes CUR as a 'Container for' the BMP format. In Pillow terms, we read the first 4 bytes to determine that it is a CUR file,
https://www.daubnet.com/en/file-format-cur
Reserved | 2 byte | =0
Type | 2 byte | =2the next 2 bytes to find out how many cursors are in the file,
Count | 2 byte | Number of Cursors in this file
and read the header information from the largest cursor, a 'Variant of BMP InfoHeader'. Parsing that part of the header is where Pillow uses code from our BmpImagePlugin.
After that, CUR images have the palette, the XOR mask and then the AND mask.
okay, thanks for the explanation.
you are using an inverted xor mask where there should be an inverted and mask.
You are correct. The fact that 'Pixels are stored bottom-up' confused me, and meant that I had the AND and XOR masks mislabelled - I had found the right visual result, but my variable names were wrong. I've updated my PR.

its a really easy-to-reproduce issue, just run this code with the file i attached in Pillow 12.1.0 :
(the file is provided as a txt file since github doesn't allow cur files to be uploaded, just change the extension.)
aero_unavail.txt