2
\$\begingroup\$

I have some images of static objects using per-pixel alpha (trees, rocks) for a background. In order to make them look a bit more natural, I want to create some kind of atmospheric fog, which means the farther away the object is, the more bluish tint it has.

I have the RGB color of a fog [108, 119, 135] (which appears to be a color of my image if it is totally far away) and I need to mix this color with my image somehow depending on the object's specified distance (which I'm mapping to percentage of fog).

If I had to mix solid colors, I'd just find the difference between the original color and 100% fog color, multiply this by the distance percentage, and subtract this from the original color. E.g. I have a value 208 in original red channel, and to make it 30% foggy I do 208-(208-108)*0.3 = 178.

But I have no clue how to do this with an image that has per-pixel alpha. Blitting original image and then fog color with solid transparency doesn't work because I'd get a square of color over my image instead of respecting the shape of the image's alpha.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Code:

import sys, pygame

def create_image_surface_with_fog(img,fog_color,fog_rate):
    _,_,w,h = img.get_rect()
    img_color = [(1-fog_rate) * 255] * 3
    fog_color = [x*fog_rate for x in fog_color]
    new_surface = pygame.Surface((w,h), pygame.SRCALPHA, 32)
    new_surface.blit(img,img.get_rect())
    new_surface.fill(img_color,img.get_rect(),special_flags=pygame.BLEND_MULT)
    new_surface.fill(fog_color,img.get_rect(),special_flags=pygame.BLEND_RGB_ADD)
    return new_surface

if __name__ == '__main__':
    pygame.init()
    screen = pygame.display.set_mode((640, 480))

    character_img = pygame.image.load("character.png")
    background_img = pygame.image.load("background.png")

    fog_color = (108, 119, 135)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT: sys.exit()

        screen.fill((0, 0, 0))
        screen.blit(background_img,background_img.get_rect())

        character_surface = create_image_surface_with_fog(character_img,fog_color,0.5)
        screen.blit(character_surface, (0, 0))

        pygame.display.flip()

Result when fog_rate= 0, 0.5, 1:

enter image description here enter image description here enter image description here

Explain:

The smallest unit in which color blend occurs is the pygame.Surface. If you want it to respect the alpha of the original image, you need to draw the original image onto a separate surface. Then draw this surface to the screen surface:

def create_image_surface_with_fog(img,fog_color,fog_rate):
    new_surface = pygame.Surface((w,h), pygame.SRCALPHA, 32)
    ...
    return new_surface
character_surface = create_image_surface_with_fog(character_img,fog_color,1)
screen.blit(character_surface, (0, 0))

For color mixing calculations, the calculation formula can be reorganized to reduce the number of drawings:

208-(208-108)*0.3 = (1-0.3)*208 + 0.3*108

So we only need to draw the original image once, BLEND_MULT once, and BLEND_RGB_ADD once:

new_surface.blit(img,img.get_rect())
new_surface.fill(img_color,img.get_rect(),special_flags=pygame.BLEND_MULT)
new_surface.fill(fog_color,img.get_rect(),special_flags=pygame.BLEND_RGB_ADD)
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .