2

I have this code on python that I use as a Bloom effect on my game, but I've noticed that it has this weird black shadow around the particles

Effect in White Background Effect in Grey  Background

The effect looks like this ^

as you can see, there is some shadows on the particles, on white background it's obvious, while in grey is subtle

I would like to know if there is a way to remove/reduce this weird effect

This is the code for the bloom (where the shadows are coming from):

def Bloom(canvas: pygame.Surface):
    size = canvas.get_size()
    canvas_color = pygame.surfarray.array2d(canvas)
    canvas_rgba = canvas_color.view(dtype=np.uint8).reshape((*canvas_color.shape, 4))

    newCanvas = pygame.Surface(size, pygame.SRCALPHA)


    cv2.blur(canvas_rgba, ksize=(9, 9), dst=canvas_rgba)

    
    pygame.surfarray.blit_array(newCanvas, canvas_color)

    return newCanvas

Any help would be great :3

Edit: Removed the image with black background (no reason to be there)

Nath
  • 69
  • 6

1 Answers1

2

The problem isn't with the "bloom" algorithm, but with the way you blit the surface to the target.
Not just the alpha channel is blurred, also the color channels are blurred. Therefore you have to blit the Surface with the special flag BLEND_PREMULTIPLIED:

bloom_surf = Bloom(source_surf)
screen.blit(bloom_surf, (x, y), pygame.BLEND_PREMULTIPLIED)

It is also possible to cancel this effect in the Bloom function by dividing the color channels by the alpha channel:

r' = r / a
g' = g / a
b' = b / a
a' = a

Each color channel is coded in a byte in the range [0, 255]. That makes the conversion a little trickier:

def BloomNoPremultipliedAlpha(canvas: pygame.Surface):
    size = canvas.get_size()
    canvas_color = pygame.surfarray.array2d(canvas)
    canvas_rgba = canvas_color.view(dtype=np.uint8).reshape((*canvas_color.shape, 4))
    newCanvas = pygame.Surface(size, pygame.SRCALPHA)
    cv2.blur(canvas_rgba, ksize=(25, 25), dst=canvas_rgba)

    canvas_rgba[:,:,0:3] = canvas_rgba[:,:,0:3] * 255.0 / canvas_rgba[:,:,[3,3,3]]

    pygame.surfarray.blit_array(newCanvas, canvas_color)
    return newCanvas

See the following minimal example that demonstrates the difference:


left: Bloom; center: Bloom + BLEND_PREMULTIPLIED; right: BloomNoPremultipliedAlpha

import pygame, cv2 
import numpy as np

def Bloom(canvas: pygame.Surface):
    size = canvas.get_size()
    canvas_color = pygame.surfarray.array2d(canvas)
    canvas_rgba = canvas_color.view(dtype=np.uint8).reshape((*canvas_color.shape, 4))
    newCanvas = pygame.Surface(size, pygame.SRCALPHA)
    cv2.blur(canvas_rgba, ksize=(25, 25), dst=canvas_rgba)
    pygame.surfarray.blit_array(newCanvas, canvas_color)
    return newCanvas

def BloomNoPremultipliedAlpha(canvas: pygame.Surface):
    size = canvas.get_size()
    canvas_color = pygame.surfarray.array2d(canvas)
    canvas_rgba = canvas_color.view(dtype=np.uint8).reshape((*canvas_color.shape, 4))
    newCanvas = pygame.Surface(size, pygame.SRCALPHA)
    cv2.blur(canvas_rgba, ksize=(25, 25), dst=canvas_rgba)

    canvas_rgba[:,:,0:3] = canvas_rgba[:,:,0:3] * 255.0 / canvas_rgba[:,:,[3,3,3]]

    pygame.surfarray.blit_array(newCanvas, canvas_color)
    return newCanvas

pygame.init()
window = pygame.display.set_mode((800, 300))
clock = pygame.time.Clock()

background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 100, *window.get_size(), (160, 160, 160), (96, 96, 96)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
[pygame.draw.rect(background, color, rect) for rect, color in tiles]

surface = pygame.Surface((250, 250), pygame.SRCALPHA)
pygame.draw.circle(surface, (255, 255, 255), surface.get_rect().center, 100)
surface1 = Bloom(surface)
surface2 = Bloom(surface)
surface3 = BloomNoPremultipliedAlpha(surface)

run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False 

    window.blit(background, (0, 0))
    window.blit(surface1, surface1.get_rect(center = (150, 150)))
    window.blit(surface2, surface2.get_rect(center = (400, 150)), special_flags = pygame.BLEND_PREMULTIPLIED)
    window.blit(surface3, surface1.get_rect(center = (650, 150)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
exit()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • 1
    that works great, thanks again, you helped me so much that I might put your name on the credits of my game :v – Nath Nov 16 '21 at 14:46