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
sf::Image::copy() - Fixed the incorrect alpha value being calculated for semi-transparent pixels on fully transparent pixels #1993
Conversation
Thanks for your contribution! I'm not sure if adding a special case is the right way to do this -- if the equation is incorrect, it is likely incorrect for other values, too. Also, this would introduce a non-continuity in the equation. Uint8 alpha = src[3];
dst[0] = static_cast<Uint8>((src[0] * alpha + dst[0] * (255 - alpha)) / 255);
dst[1] = static_cast<Uint8>((src[1] * alpha + dst[1] * (255 - alpha)) / 255);
dst[2] = static_cast<Uint8>((src[2] * alpha + dst[2] * (255 - alpha)) / 255); This is the alpha blending equation Uint8 alpha = src[3];
dst[3] = static_cast<Uint8>(alpha + dst[3] * (255 - alpha) / 255); For the resulting In the case of It seems to me that SFML is following the correct alpha blending formula, but maybe I'm missing something. |
Yes, you are right - I scratched my head for a while and couldn't fault the formula myself. There could be some subtle order of operations bug which I am not seeing however (I do not write a lot of C++ code). In the tests I ran, it only occurred when the destination alpha was zero (a destination alpha of 1 worked perfectly), so my testing only matched the edge case, hence the unsightly And, yes the non-continuity is far from ideal. I don't mind if this PR isn't merged (I have my own subclass of Apologies if a PR was the wrong way to do it, I thought that having a working example was a better option than me try and explain over many paragraphs :-) |
It seems the expected formula is the "over" operator as described here. Uint8 src_alpha = src[3];
Uint8 dst_alpha = dst[3];
Uint8 out_alpha = static_cast<Uint8>(src_alpha + dst_alpha * (255 - src_alpha) / 255);
if(out_alpha)
{
dst[0] = static_cast<Uint8>((src[0] * src_alpha + dst[0] * dst_alpha * (255 - src_alpha) / 255) / out_alpha);
dst[1] = static_cast<Uint8>((src[1] * src_alpha + dst[1] * dst_alpha * (255 - src_alpha) / 255) / out_alpha);
dst[2] = static_cast<Uint8>((src[2] * src_alpha + dst[2] * dst_alpha * (255 - src_alpha) / 255) / out_alpha);
}
dst[3] = out_alpha; |
@pmachapman No worries, glad to discuss this 🙂 If you want the semantics that you describe (copy RGBA if dest A is zero), standard Alpha Blending might not be the right blending equation for you. Maybe instead of operating on
With this: |
As mentioned above, I think the current implementation is correct, in the sense that it just applies standard alpha blending. More sophisticated blending methods should probably go via dedicated Other opinions? Should we keep this open? |
Thank you all for your time and effort looking into this. I agree that there are better blending tools in SFML that should be used instead of this method. My only real argument for opening this PR and suggesting this fix is that the visual error does occur from valid input to the method, and that the fix itself is not a major change. A simple algorithm, such as is currently in the main branch, is always preferred, but it appears the algorithm cannot handle this valid edge case. I of course would love it merged for those reasons, but if this issue happens to others, I am content that it is documented here, and that they can use the |
My opinion is that standard alpha blending is not working well when the background is not opaque. I think either proper blending should be implemented or the documentation should tell that using I looked into GIMP source code and, if I did not completely get lost in the huge code base, the "over" operator (a.k.a. normal blending) seems to be implemented with the following code.
Unfold
|
That sounds like a plan! Maybe the use of the "over" operator should also be mentioned in the |
2c9cc95
to
1110a74
Compare
Thank you @kimci86 for taking the time to investigate and find that all out. I have implemented the "over" operator, and it works brilliantly. |
Codecov Report
@@ Coverage Diff @@
## master #1993 +/- ##
=========================================
- Coverage 6.58% 6.55% -0.03%
=========================================
Files 184 182 -2
Lines 15788 15753 -35
Branches 4162 4080 -82
=========================================
- Hits 1040 1033 -7
+ Misses 14616 14591 -25
+ Partials 132 129 -3
Continue to review full report at Codecov.
|
Great to hear! 🙂 @pmachapman Could you document the new semantics in the Also, @kimci86 do you think this is something worth adding to tests? |
I do not feel it needs to be added right now, especially as there are no unit tests yet for other |
@Bromeon I have updated the documentation as requested, in the header file. Please let me know if there is anywhere else I need to update, or if I have made an error. |
Can you squash the two commits? 🙂 |
919cef8
to
8c5c611
Compare
Might actually be a good idea to fix this already on for 2.6. |
I have rebased this PR to the |
Thanks for this fix! 🥳 |
Description
This PR fixes a bug where the alpha value for a semi-transparent pixel is messed up when placed on top of a transparent pixel. This bug resulting in darker pixels appearing where the transparency should be lighter.
Tasks
How to test this PR?
Run the code below, which is self-contained and has the following graphic encoded as a string literal:
Without the fix, it will look like (zoomed in 5x):
After the fix, it will look like (zoomed in 5x):