Closed iamjackg closed 1 month ago
Sounds like you need to use premultiplied alpha blending.
On Mon, 15 Apr 2024, 02:26 Jack Gaino, @.***> wrote:
Environment:
You can get some of this info from the text that pops up in the console when you run a pygame program.
- Operating system (e.g. Windows, Linux(Debian), Linux(Ubuntu), Mac): Linux(Ubuntu)
- Python version (e.g. 3.11.1, 3.8.5) : 3.10
- SDL version (e.g. SDL 2.0.12): 2.0.11
- pygame-ce version (e.g. 2.4.0.dev4, 2.1.3): 2.4.1
- Relevant hardware (e.g. if reporting a bug about a controller, tell us the brand & name of it):
Current behavior:
I feel like there is some fundamental aspect of alpha blending that I'm not understanding here.
I'm doing the following in Pygame to create a blurred drop shadow:
- draw a white object
- draw a black version of it on another surface
- apply gaussian blur to the black version
- blit the dark version and then the white version to a new surface
So far, so good. Now I blit this on top of a surface filled with a semi-transparent color, and then I blit that on top of the background. When I run this, however, the alpha of the shadow seems to pull the semi-transparent surface with it and makes the shadow brighter instead of black.
In my real project, I can't blit the two surfaces independently on the background: I need to return a single merged surface.
Screenshots
This is what the "wrong" drop shadow looks like.
image.png (view on web) https://github.com/pygame-community/pygame-ce/assets/19474350/1ba99e65-4387-4c17-a372-556fff6c42f7
As soon as I make the semi-transparent surface completely transparent, the issue disappears, and the shadow is dark again.
image.png (view on web) https://github.com/pygame-community/pygame-ce/assets/19474350/bda34bf7-e8d2-4ecc-9fa1-90cd642934e4
I have tried playing around with all the special_flags to change the blending mode when blitting, but no combination seems to help, or perhaps I just don't understand what I'm supposed to use.
Test code
Here is code to fully reproduce the above:
import pygame as pg
pg.init()screen = pg.display.set_mode((400, 400))clock = pg.time.Clock() black_circle_surface = pg.Surface((100, 100), pg.SRCALPHA)pg.draw.circle(black_circle_surface, (0, 0, 0), (50, 50), 25) white_circle_surface = pg.Surface((100, 100), pg.SRCALPHA)pg.draw.circle(white_circle_surface, (255, 255, 255), (50, 50), 25) circle_and_shadow_surface = pg.Surface((100, 100), pg.SRCALPHA)circle_and_shadow_surface.blit( pg.transform.gaussian_blur(black_circle_surface, radius=10), (0, 0) )circle_and_shadow_surface.blit(white_circle_surface, (0, 0)) semi_transparent_surface = pg.Surface((200, 200), pg.SRCALPHA)semi_transparent_surface.fill( (255, 255, 255, 1) # change the 1 to a 0 here to make the issue disappear )semi_transparent_surface.blit( circle_and_shadow_surface, (50, 50), ) running = Truewhile running: for event in pg.event.get(): if event.type == pg.QUIT: running = False
screen.fill((20, 70, 80)) screen.blit(semi_transparent_surface, (100, 100)) pg.display.update() clock.tick(60)
— Reply to this email directly, view it on GitHub https://github.com/pygame-community/pygame-ce/issues/2808, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADGDGGQUABOIWAXAXXIYOGTY5MUEPAVCNFSM6AAAAABGGNRNT2VHI2DSMVQWIX3LMV43ASLTON2WKOZSGI2DENBZG42TANA . You are receiving this because you are subscribed to this thread.Message ID: @.***>
I did try that, but couldn't find the specific magic combination to get it to work right. I got very similar (broken) results. Would you be so kind to adjust the demo code I posted to correctly use premultiplied blending?
I did try that, but couldn't find the specific magic combination to get it to work right. I got very similar (broken) results. Would you be so kind to adjust the demo code I posted to correctly use premultiplied blending?
Sure, I added a bunch of comments to try and help you understand a bit more where I was bothering to pre-multiply or not. In the average application it is safe just to pre-multiply everything and always blit with premultiplication, but as it happens here you have some alpha'd black and solid white pixels which are not affected by an alpha pre-multiplication operation (multiply a 0 colour by anything and it is still 0, multiply 255 by 1 and it is still 255):
Anyway here is the working program:
import pygame
import pygame as pg
pg.init()
screen = pg.display.set_mode((400, 400))
clock = pg.time.Clock()
black_circle_surface = pg.Surface((100, 100), pg.SRCALPHA)
pg.draw.circle(black_circle_surface, (0, 0, 0), (50, 50), 25)
black_circle_surface = pg.transform.gaussian_blur(
black_circle_surface, radius=10
) # no need to pre-multiply as colour is zeros will not change whatever we multiply it by
white_circle_surface = pg.Surface((100, 100), pg.SRCALPHA)
white_circle_surface.fill(
(0, 0, 0, 0)
) # no need to pre-multiply, alpha is zero and colour is zero
pg.draw.circle(
white_circle_surface, (255, 255, 255), (50, 50), 25
) # no need to pre-multiply alpha and colour are the same (either 0 or 255)
circle_and_shadow_surface = pg.Surface((100, 100), pg.SRCALPHA)
circle_and_shadow_surface.fill((0, 0, 0, 0)) # no need to pre-multiply alpha is zero
circle_and_shadow_surface.blit(black_circle_surface, (0, 0))
circle_and_shadow_surface.blit(white_circle_surface, (0, 0))
semi_transparent_surface = pg.Surface((200, 200), pg.SRCALPHA)
semi_transparent_surface.fill(
(255, 255, 255, 1) # change the 1 to a 0 here to make the issue disappear
)
semi_transparent_surface = (
semi_transparent_surface.convert_alpha().premul_alpha()
) # need to pre-multiply RGB values will all be changed to 1
semi_transparent_surface.blit(
circle_and_shadow_surface, (50, 50), special_flags=pygame.BLEND_PREMULTIPLIED
)
running = True
while running:
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
screen.fill((20, 70, 80))
screen.blit(
semi_transparent_surface, (100, 100), special_flags=pygame.BLEND_PREMULTIPLIED
)
pg.display.update()
clock.tick(60)
This is the normal behaviour of the standard 'straight' alpha (the pygame default) and the superior 'premultiplied' alpha blending.
The main advantage of 'straight' alpha is that it is easier to understand and easy to dynamically alter the alpha value - otherwise it sucks.
Here is what the premultiplied alpha version looks like if we dial the semi-transparent surface up to 50 alpha:
I also wrote a tutorial on premultiplied alpha blending here:
Thank you so much: you've been incredibly helpful. I originally asked this on Stack Overflow, and the only comment I got was "this seems to be a bug with pygame-ce -- you should report it there."
If you have a stack overflow account, feel free to copy your answer here and I'll accept it: https://stackoverflow.com/questions/78322770/why-are-my-blurred-drop-shadows-lightening-instead-of-darkening-a-semi-transpare
If you don't, I'd love to copy it there with your permission, giving full credit to you.
Copy away - I don't have a stack overflow account.
On Tue, 16 Apr 2024, 00:44 Jack Gaino, @.***> wrote:
Thank you so much: you've been incredibly helpful. I originally asked this on Stack Overflow, and the only comment I got was "this seems to be a bug with pygame-ce -- you should report it there."
If you have a stack overflow account, feel free to copy your answer here and I'll accept it: https://stackoverflow.com/questions/78322770/why-are-my-blurred-drop-shadows-lightening-instead-of-darkening-a-semi-transpare
If you don't, I'd love to copy it there with your permission and giving full credit to you.
— Reply to this email directly, view it on GitHub https://github.com/pygame-community/pygame-ce/issues/2808#issuecomment-2057992532, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADGDGGT7I266Z5XOASUV7VDY5RQ4FAVCNFSM6AAAAABGGNRNT2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANJXHE4TENJTGI . You are receiving this because you commented.Message ID: @.***>
@iamjackg could you copy over the answer to stack overflow? I'd like to fully resolve this issue so it can be closed in a good state.
Done! Sorry about the delay, got sidetracked by other things.
Environment:
You can get some of this info from the text that pops up in the console when you run a pygame program.
Current behavior:
I feel like there is some fundamental aspect of alpha blending that I'm not understanding here.
I'm doing the following in Pygame to create a blurred drop shadow:
So far, so good. Now I blit this on top of a surface filled with a semi-transparent color, and then I blit that on top of the background. When I run this, however, the alpha of the shadow seems to pull the semi-transparent surface with it and makes the shadow brighter instead of black.
In my real project, I can't blit the two surfaces independently on the background: I need to return a single merged surface.
Screenshots
This is what the "wrong" drop shadow looks like.
As soon as I make the semi-transparent surface completely transparent, the issue disappears, and the shadow is dark again.
I have tried playing around with all the
special_flags
to change the blending mode when blitting, but no combination seems to help, or perhaps I just don't understand what I'm supposed to use.Test code
Here is code to fully reproduce the above: