pygame-community / pygame-ce

🐍🎮 pygame - Community Edition is a FOSS Python library for multimedia applications (like games). Built on top of the excellent SDL library.
https://pyga.me
930 stars 154 forks source link

Bluring with transparency causes a black outline #2291

Open OlaKenji opened 1 year ago

OlaKenji commented 1 year ago

Hi

Using pygame.transform.gaussian_blur() works nicely but in case there is a transparent edge, it induces a black outline on the blurred surface. This is because the transparent pixels are black (but alpha = 0). See for example:

Screenshot 2023-07-06 at 13 54 39

A way to fix it is to manually "weight" each pixel based on their alpha value. If so, you can get the desired effect:

Screenshot 2023-07-06 at 13 58 00

Perhaps it is a good idea to have the alpha weighting implemented in pygame.transform.gaussian_blur()?

ankith26 commented 1 year ago

Hello and thanks for the bug report.

Can you provide a minimal reproducer and the png of the original image with transparancy (if there are no licensing restrictions)

OlaKenji commented 1 year ago

Of course. I have attached the original png file (does this work?). For reference, I am using 'pygame-ce 2.2.0 (SDL 2.0.22, Python 3.9.12)'

test

And here is the code I used:

import pygame

display = pygame.display.set_mode((1000,1000))
screen = pygame.Surface((300,300))

clock=pygame.time.Clock()
surface = pygame.image.load('test.png').convert_alpha()
surface = pygame.transform.gaussian_blur(surface, 2)

while True:
    screen.fill((255,255,255))
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
            sys.exit()

    screen.blit(surface,(50,100))
    display.blit(pygame.transform.scale(screen,(1000,1000)),(0,0))

    pygame.display.update()
    clock.tick(60)
oddbookworm commented 1 year ago

@yunline I believe you were the implementer of the blur functions. Thoughts?

yunline commented 1 year ago

@yunline I believe you were the implementer of the blur functions. Thoughts?

This problem exists in box_blur() too. I'll try to fix it.

yunline commented 1 year ago

I think this is not a bug of gaussian blur, since premultiply should not be a part of blur algorithm. You can solve this by blitting the transformed surface with pygame.BLEND_PREMULTIPLIED flag. This is the modified code:

import pygame
import sys

display = pygame.display.set_mode((1000,1000))
screen = pygame.Surface((300,300))

clock=pygame.time.Clock()
surface = pygame.image.load('alpha_test.png').convert_alpha()
surface = pygame.transform.gaussian_blur(surface, 2)

while True:
    screen.fill((255,255,255))
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
            sys.exit()

    # Add pygame.BLEND_PREMULTIPLIED here ↓
    screen.blit(surface,(50,100), special_flags=pygame.BLEND_PREMULTIPLIED)
    display.blit(pygame.transform.scale(screen,(1000,1000)),(0,0))

    pygame.display.update()
    clock.tick(60)

image

@OlaKenji Does this solve your problem?

OlaKenji commented 1 year ago

I tried your solutions in my project. It was indeed solved for that particular png. However, I noticed now that I instead got a white outline on some of the png files. Here is an example of the png file that is affected:

stone

I reproduced the white outline artifact using your revised code (I made the BG black to highlight the white artifact). The colour of the outline seem to depend on the png file (maybe it depends on the edge colour of the png file?). Do you (@yunline) get the same problem?

It still feels like each pixel RBG value should be weighted by its alpha value (weight = alpha/255), meaning transparent pixels will not contribute (since weight = 0) while none transparent pixels will not be affected (since weight = 1). Or maybe having a flag in the function to enable this kind of weighting? Just a thought. :)

display = pygame.display.set_mode((1000,1000))
screen = pygame.Surface((300,300))

clock=pygame.time.Clock()
surface = pygame.image.load('stone.png').convert_alpha()
surface = pygame.transform.gaussian_blur(surface, 2)

while True:
    screen.fill((0,0,0))
    for event in pygame.event.get():
        if event.type==pygame.QUIT:
            pygame.quit()
            sys.exit()

    screen.blit(surface,(50,75), special_flags=pygame.BLEND_PREMULTIPLIED)
    display.blit(pygame.transform.scale(screen,(1000,1000)),(0,0))

    pygame.display.update()
    clock.tick(60)#limmit FPS
yunline commented 1 year ago

However, I noticed now that I instead got a white outline on some of the png files.

This may be caused by the global alpha of the png file. I'll try to figure out why. For now you can try to fix like this.

surface = pygame.image.load('stone.png').premul_alpha()
Test code and screenshot ```py import pygame import sys display = pygame.display.set_mode((1000,1000)) screen = pygame.Surface((300,300)) clock=pygame.time.Clock() surface = pygame.image.load('stone.png').premul_alpha()#.convert_alpha() surface = pygame.transform.gaussian_blur(surface, 2) while True: screen.fill((0,0,0)) for event in pygame.event.get(): if event.type==pygame.QUIT: pygame.quit() sys.exit() screen.blit(surface,(50,75), special_flags=pygame.BLEND_PREMULTIPLIED) display.blit(pygame.transform.scale(screen,(1000,1000)),(0,0)) pygame.display.update() clock.tick(60)#limmit FPS ``` ![image](https://github.com/pygame-community/pygame-ce/assets/31395137/c383dbc3-0c67-4160-bf84-4b5a3f4f5b04)
dr0id commented 1 year ago

@yunline maybe this will help you (old pr): https://github.com/pygame/pygame/pull/2968

Especially the link to the stackoverflow entry:

https://stackoverflow.com/questions/35476142/gaussian-blur-handle-with-alpha-transparency/35481968#35481968

In essence if alpha is involved the calculation has to consider it and the weights are different.