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

Method to test if image has transparency/alpha #7384

Closed
chinery opened this issue Sep 8, 2023 · 8 comments · Fixed by #7420
Closed

Method to test if image has transparency/alpha #7384

chinery opened this issue Sep 8, 2023 · 8 comments · Fixed by #7420

Comments

@chinery
Copy link

chinery commented Sep 8, 2023

[2023/09/11 edit] adding a tl;dr:

  1. Online sources do not agree on a single method to check for either transparency (actual transparent pixels) or the presence of an alpha channel (whether or not it is opaque), and for all the examples I've found, it's easy to craft examples on which they fail. It would be useful to have an authoritative correct solution from someone who really understands Pillow/images that handles all the edge cases. Or in lieu of that, we could crowd-source one here.
  2. Given the frequency of the question and the widespread incorrect code, it seems like it'd be useful to add this feature to Pillow itself.

Original post:

I found myself wanting to check if an image had an alpha channel/transparency (I'm allowing for a distinction between those, perhaps there isn't a meaningful one). I was trying to convert some optimised images that didn't contain any transparency, so wanted to do something different in that case.

Checking for this is slightly tricky though, and I've seen incorrect logic posted elsewhere, e.g. this stackoverflow post references this stackoverflow post which states to use:

img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info)

But palette-mode images can store their transparency in at least two ways, in img.info, or the palette itself might be RGBA. If you run the following

img = Image.new("RGBA", (100, 100), (128, 128, 128, 128)).quantize()
print(img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info)) # False

the result is False, but clearly it has alpha values in the palette:

print(img.palette.mode) # 'RGBA'
print(img.palette.colors) # {(128, 128, 128, 128): 0, (0, 0, 0, 0): 1}

Would it be possible to add something like img.has_alpha() or img.has_transparency() to help here?

I'm not familiar enough with Pillow's image formats to say whether checking the mode of the palette in addition to the checks above would be exhaustive. Perhaps someone can clarify? Is it sufficient to check img.palette.mode == 'RGBA'? .quantize() does not seem to like an LA image, but if I manually set the palette mode and colors, it works with show(). Then there are other esoteric modes with alpha channels. So perhaps something like this will work?

alpha_modes = {"RGBA", "RGBa", "LA", "La", "PA"}
return img.mode in alpha_modes or (
    img.mode == "P"
    and ("transparency" in img.info or img.palette.mode in alpha_modes)
)

but I very easily could have missed something due to inexperience with the unusual image formats.

(Happy to have a go at the implementation if that would be of assistance!)

@radarhere
Copy link
Member

A side comment that I'm not sure how to phrase - you mention two StackOverflow posts, a 2016 StackOverflow post referencing a StackOverflow post from 2009. If the problem is that old, I have to imagine the awkward solutions are also well-ingrained in the behaviour of users by now, and that whatever happens here, posts from 2009 (which is actually before Pillow even existed) will still be there, offering a different way of doing things. This is just part of dealing with a library that has been around for a long time.

Given that you can have RGBA images that are fully solid, the method you're describing is sort of not describing whether an image has transparency, but whether it might have transparency. I presume you're not interested in a method that checks each pixel to see if it has transparency or not, because you correctly think that would be slower?

@radarhere radarhere changed the title Feature request: method to test if image has transparency/alpha Method to test if image has transparency/alpha Sep 8, 2023
@chinery
Copy link
Author

chinery commented Sep 8, 2023

Thanks for the comment! Yeah I agree about the legacy here. But it's a bit of "the best time to act was in 2009, the second best time to act is now" sort of situation IMO. My point was that those well-ingrained solutions are actually incorrect, hence the huge benefit in a library-approved correct method (as opposed to just leaving people to use a commonly accepted workaround, which would be more okay if it was correct). I found those Stack Overflow posts through searching for sensible terms, and it's not uncommon to see a post on SO get a new solution many years later when a library gets updated, and that solution slowly gets upvoted.

Regarding solid RGBA images – that's why I left the door open for a semantic difference between "has alpha" and "has transparency". When it comes to my own use case, I was interested in not just removing transparency but removing the alpha channel (even if it was opaque). Though I can see utility in both features, and while checking for transparency would be slower, I'm not sure if it would be too slow to be useful. I figured it was something that could come out in a discussion, anyway.

For clarity on terms: we're agreed that alpha does not imply transparency, does transparency imply alpha? Are there any ways to achieve transparency in Pillow that do not use an alpha channel? Genuine question as to whether this is a subset or a Venn diagram...!

@chinery
Copy link
Author

chinery commented Sep 8, 2023

A colleague just sent me this post, which seems like a nicely well-researched has_transparency method (unlike the one in my post which was not well-researched, and just supposed to be has_alpha)

https://stackoverflow.com/a/58567453/5506429

though an answer by another author seems to check for different cases again

https://stackoverflow.com/a/76268100/5506429

@chinery
Copy link
Author

chinery commented Sep 8, 2023

Though both of those answers rely on transparency being a key in img.info, which is not the case when you quantize an RGBA image

img = Image.new("RGBA", (100, 100), (128, 128, 128, 128)).quantize()
print(img.info) # {}

perhaps that's actually a bug in .quantize() or .new() but seems all of these have some issues.

@radarhere
Copy link
Member

Though both of those answers rely on transparency being a key in img.info, which is not the case when you quantize an RGBA image

img = Image.new("RGBA", (100, 100), (128, 128, 128, 128)).quantize()
print(img.info) # {}

perhaps that's actually a bug in .quantize() or .new() but seems all of these have some issues.

If the behaviour of quantize() is not to keep any transparency, then we probably don't want to change that default behaviour and defy existing user's expectations. Perhaps another argument could be added to enable transparency to stay around, but this seems to be getting away from your original request.

Regarding solid RGBA images – that's why I left the door open for a semantic difference between "has alpha" and "has transparency".

Speaking for myself, I wouldn't have naturally understood a difference between those two method names.

Are there any ways to achieve transparency in Pillow that do not use an alpha channel?

I think you've already posted an example of this, that P mode images can have a transparency index?

When it comes to my own use case, I was interested in not just removing transparency but removing the alpha channel (even if it was opaque)

So this is the same as https://stackoverflow.com/questions/35859140/remove-transparency-alpha-from-any-image-using-pil/35859141#35859141. Would it be simpler to cut out the middleman of checking for transparency, and just have a method that removes transparency?

@chinery
Copy link
Author

chinery commented Sep 9, 2023

If the behaviour of quantize() is not to keep any transparency, then we probably don't want to change that default behaviour and defy existing user's expectations.

Sorry, quantize() does retain transparency. That code sample I posted puts the alpha values into the palette. If you save it as a png and reload it, then it comes back in .info['transparency'].

Are there any ways to achieve transparency in Pillow that do not use an alpha channel?

I think you've already posted an example of this, that P mode images can have a transparency index?

Good point :)

So this is the same as https://stackoverflow.com/questions/35859140/remove-transparency-alpha-from-any-image-using-pil/35859141#35859141. Would it be simpler to cut out the middleman of checking for transparency, and just have a method that removes transparency?

It does seem that removing transparency is the most common request here and I'd definitely make use of such a method, but I think it'd be complicated.

I specifically wanted to do something like:

if has_alpha:
    composite with a specific colour background
    remove_alpha

but in that case, after alpha_composite all the alpha values are 255 so for remove_alpha I was happy to do .convert("RGB"). For a more fully-fledged remove_transparency method, it'd presumably need to have an option to add a background, and also retain the original mode?

I do also have a use case where I just want to check for particular images that actually have transparent pixels – not to remove the transparency, but to determine an output format (since jpg doesn't support it for example). The examples from SO handle every file I've seen so far, I just can't help but notice I can construct an image where they'd fail.

@radarhere
Copy link
Member

Ok, I've created PR #7420. See what you think, and let us know if that resolves this. I've put it in draft mode because I would like to try and address this in one go.

@chinery
Copy link
Author

chinery commented Sep 26, 2023

I've commented on the PR, it looks great, thanks.

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

Successfully merging a pull request may close this issue.

2 participants