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
SDL 2: Per Pixel Alpha error. #1289
Comments
Hi. Thanks for the issue report! Does your issue have anything to do with this? #1254 (screen defaults to white instead of black bug) Do you have a script that can reproduce the issue? cheers, |
It does have to do with it. I found a workaround by blitting with "special_flags=(pygame.BLEND_RGBA_ADD)" This will work for now. I see it is a known error :-) |
I don't understand the problem here. Can you please post a snippet of code that shows the problem? I'll fix #1254 if that is what causes your problem, but I'd like to have a test case first. |
I am simply blitting an image with transparency onto a transparent surface created with: The transparent image is white but causes a dark overlay |
I thought I knew what you meant, but your comment made me more confused. It's definitely not #1254. So you have an image with semi-transparency. You blit that image to a surface with per-pixel alpha. Then you blit the second surface to the screen. I have trouble reproducing the bug. |
Lets say I blit an image that has only one color: [255, 255, 255, 100]. When blitted over something, this should lighten what is under it. Instead, If I blit it over a background with the color [255, 255, 255, 255], it turns grey. It should be invisible. Does that make more sense? |
This seems to work the same in 1.9.6 and 2.0-dev3 import pygame
pygame.init()
screen=pygame.display.set_mode((200,200))
screen.fill((255,255,255,255))
surf2=pygame.Surface((100,100)).convert_alpha()
surf2.fill((255,255,255,100))
surf3=pygame.Surface((100,100)).convert_alpha()
surf3.fill((0,0,0,100))
screen.blit(surf3, (50,50))
screen.blit(surf2, (0,0))
surf2.blit(surf3, (25,25))
screen.blit(surf2, (100,100))
pygame.display.flip()
input() |
Reproduces for me. Here's a test case without an image file: import pygame
pygame.init()
screen=pygame.display.set_mode((200,200))
screen.fill((255,255,255,255))
pygame.display.flip()
white_transparent_image = pygame.Surface((256, 256), pygame.SRCALPHA)
white_transparent_image.fill((255, 255, 255, 100))
#This returns (255, 255, 255, 100):
print(white_transparent_image.get_at([10, 10]))
surf2=pygame.Surface((256,256), pygame.SRCALPHA)
surf2.blit(white_transparent_image, [0, 0])
# this shows a grey screen:
screen.blit(surf2, (0,0))
# this gives the correct result (white):
#screen.blit(white_transparent_image, (0,0))
print(screen.get_at([10, 10]))
pygame.display.flip()
input() |
So the problem is that you're blending a semi-transparent white image with a black surface ( |
It does seem to produce strange results even if the target surface is transparent: import pygame
pygame.display.init()
pygame.display.set_mode((120, 120))
white_transparent_image = pygame.Surface((256, 256), pygame.SRCALPHA)
white_transparent_image.fill((255, 255, 255, 100))
# This returns (255, 255, 255, 100):
print(white_transparent_image.get_at([10, 10]))
surf2=pygame.Surface((256,256), pygame.SRCALPHA).convert_alpha()
surf2.fill((0,0,0,0))
# This returns (0, 0, 0, 0):
print(surf2.get_at([10, 10]))
# This returns (99, 99, 99, 99):
surf2.blit(white_transparent_image, [0, 0])
print(surf2.get_at([10, 10])) Setting the RGB value to white gives you the desired result (plus a small error): import pygame
pygame.display.init()
pygame.display.set_mode((120, 120))
white_transparent_image = pygame.Surface((256, 256), pygame.SRCALPHA)
white_transparent_image.fill((255, 255, 255, 100))
surf2=pygame.Surface((256,256), pygame.SRCALPHA).convert_alpha()
surf2.fill((255,255,255,0))
# This returns (253, 253, 253, 99):
surf2.blit(white_transparent_image, [0, 0])
print(surf2.get_at([10, 10])) |
What do you think is causing this...? |
It's caused by the blend mode that is used. |
Since that is what should be fixed, should I close this issue or leave it open? |
That explains how the issue was fixed when I changed the blendmode |
This is the minimal example I can find to show the difference. import pygame
surf1 = pygame.Surface((1, 1), pygame.SRCALPHA)
surf1.fill((255, 255, 255, 100))
surf2=pygame.Surface((1, 1), pygame.SRCALPHA)
surf2.blit(surf1, (0, 0))
print("surf1.get_at((0, 0))", surf1.get_at((0, 0)))
print("surf2.get_at((0, 0))", surf2.get_at((0, 0))) |
If you use this code, you will see that the surface is defaulting to black (in pygame2), rather than to white (in 1.9.6 with SDL1). import pygame
surf1 = pygame.Surface((1, 1), pygame.SRCALPHA, 32)
surf1.fill((255, 255, 255, 100))
surf2=pygame.Surface((1, 1), pygame.SRCALPHA, 32)
print("surf2.get_at((0,0))", surf2.get_at((0,0)))
#surf2.fill((255, 255, 255, 255))
surf2.fill((0, 0, 0, 255))
surf2.blit(surf1, (0, 0))
print("surf1.get_at((0, 0))", surf1.get_at((0, 0)))
print("surf2.get_at((0,0))", surf2.get_at((0,0))) |
I pushed a failing test case here: #1372 (works for SDL1) |
From https://wiki.libsdl.org/SDL_BlitSurface :
We see that if the source and dest have a blendmode then they blend. I guess previous behaviour is that they do not blend. From https://wiki.libsdl.org/SDL_SetSurfaceBlendMode :
Perhaps a solution is to check if the src has a blendmode == blend, and if so remove the blend mode temporarily. |
There's another test case that shows blending should be on when set_alpha(int) is used... def test_set_alpha_value(self):
"""surf.set_alpha(x), where x != None, enables blending"""
s = pygame.Surface((1,1), SRCALPHA, 32)
s.fill((0, 255, 0, 128))
s.set_alpha(255)
s2 = pygame.Surface((1,1), SRCALPHA, 32)
s2.fill((255, 0, 0, 255))
s2.blit(s, (0, 0))
self.assertGreater(s2.get_at((0, 0))[0], 0, "the red component should be above 0") |
|
@lordmauve Good question. This test passes with pygame 1.9.6 and pygame 1.9.1, but not pygame 2.0.0.dev7 (Ubuntu 18.04). So it's a case of keeping backwards compatibility. note for self: Sorry, the test_src_alpha_issue_1289 passes when run by itself in 2.0.0.dev7. It's currently marked as skipped, because it crashes when run with other tests.
Here is how it fails when run with other tests...
|
Next step: make a pure C test case that shows the behavior for making post to the libsdl bug tracker and discussion forum. This will also make sure it's not something pygame is doing. |
If the default behaviour for blit is to copy, then we need a separate blend mode to alpha blend correctly. The existing blend flags seem to be more unusual operations like additive or multiplicative blending. Normal alpha blending, onto an RGBA surface, with a non-premultiplied input, is
But this gives premultiplied output. If you want the buffer to end up with non-premultiplied alpha, for another blit operation, then you need to divide
I think the trick is to always do pre-multiplied alpha. Then the blend equations are always
and you can always simple assume premultiplied alpha. |
So working backwards, |
So you shouldn't be able to use the target of a blit as the direct input to another blit - otherwise you'll see an unexpected grey. |
It seems the default is to blend if they are SRCALPHA surfaces. SDL1 has a weird special case where src alpha is 0. |
Is there some plans of supporting ARGB32_Premultiplied format ? |
Was discussing this on discord and made a simple test case demonstrating the situations where it crops up in pygame GUI:
In pygame 2.0.0.dev7 (on windows) it looks like this: While in 1.9.6 it looks like this: I assume down to this issue. @robertpfeiffer mentioned an interest in looking at it on linux to confirm it happens on all platforms. |
I had a bash at implementing the pre-multiplied alpha technique @lordmauve was talking about using the current pygame blend modes. Example:
A lot of extra blits, surfaces and scale there to make it all hang together under the current system. On the upside it does seem to work: That's in SDL2 and SDL1, as far as I can tell it looks the same. So as far as I can see it there are two paths heading forwards:
|
Hmm a bit of digging has uncovered that there is an undocumented blend mode for this already! Here's my example above remade with the secret blendomode; BLEND_PREMULTIPLIED:
So I guess the most important action for this issue is actually to document this blend mode asap. |
I had a go at swapping the Pygame GUI package over to using BLEND_PREMULTIPLIED. It's fairly doable, but the big wall I've run into is that swapping all my blits over to BLEND_PREMULTIPLIED seems to lead to about five times slower performance. Theoretically it should be a tiny bit faster because :
should be one less operation than:
But I guess how it is implemented in pygame versus a standard SDL blit is not even close. Assuming for a moment that a SDL 2 version of BLEND_PREMULTIPLIED could be made five times faster, these are the helper functions I used to port Pygame GUI:
Basically I had to wrap with one of these every time a colour was passed into the UI system (this was basically one place) or an image (a couple of places) from outside. I also had to change every place I rendered a transparent background text surface because that has a white colour on the transparent pixels which is obviously not pre-multiply friendly. I guess if your use case is not performance critical then the BLEND_PREMULTIPLIED flag is still usable.
Perfomance/Appearance Images from Pygame GUI 1.9.6 - Without pre-multiplied alpha blending: 2.0.0.dev6 - Without pre-multiplied alpha blending: 1.9.6 - With pre-multiplied alpha blending: 2.0.0.dev6 - With pre-multiplied alpha blending: You can try out the pre-mul-alpha version of Pygame GUI on this branch: |
Did some investigation into making pre-mul alpha faster by looking at what SDL is doing. From what I can tell for speed (on windows and probably the main desktop platforms) you want an MMX using function doing something like this:
Which is ported to pygame from here: https://hg.libsdl.org/SDL/file/015943013626/src/video/SDL_blit_A.c#l330 I know I've got something slightly wrong because it's not displaying my text boxes, and also segfaults in threading code with another of my text box samples - but it's damn close! Example screenshot using above version of pre-multiplied alpha: Performance is right, most GUI elements look right... |
I also updated the issue about this on the SDL bugzilla tracker here: |
Hi, Can you post your full reproducer for the "Options UI" screenshots you have been posting? :) |
Sure, to get this going you need: pygame faster multiply blending branch: https://github.com/MyreMylar/pygame/tree/faster-pre-mul-alpha-blending pygame_gui pre mul alpha branch: https://github.com/MyreMylar/pygame_gui/tree/premul-alpha And then the actual sample code is just the 'general_ui_test.py' file from the pygame gui examples here: It's a bit of a lot to grab, I could probably make a more condensed stress test for pre-multiply alpha that just made lots of transparent surfaces that blitted onto each other. Basically the code posted in this comment with a loop and some calls to random. The pygame branch has working MMX & SSE2 implementations of pre mul blending in it now. |
I am starting to regret bringing this up. Sorry guys but amazing work so far! :-) |
This issue is sort of related: #864 |
Some hacky progress in #2122 |
Closed thanks to @MyreMylar in #2213 |
python3 tests/surface_test.py -k test_src_alpha_issue_1289
Blend mode formula SDL2: https://hg.libsdl.org/SDL/file/3b03741c0095/include/SDL_blendmode.h#l37
The BLEND_PREMULTIPLIED PRs for reference (the new blitter should be mostly the same as this)
find conditions for the (src_surf+dest_surf+blit opts) to use the new blit.
(see test_src_alpha_issue_1289 to help debug this) inside pgSurface_Blit in surface.c
Images with alpha that are only white blit as grey. I am trying to lighten the screen but it is darkening it instead
Related Docs: https://www.pygame.org/docs/ref/surface.html#pygame.Surface.blit
The text was updated successfully, but these errors were encountered: