0

I know there's a similar question here - How can I change the brightness of an image in pygame? - but the answer only explains how to increase the brightness, without referring to how to decrease the brightness. I want to create an animation where an image increases in brightness, but then decreases in the same manner. I've tried using this code:

running = True
brightness = 1
brightstep = 1
while running:
    colour = (brightness, brightness, brightness)
    canvas.fill((255, 255, 255))
    canvas.blit(bg, (0, 0))
    canvas.blit(img, (0, 0))
    if brightness == 51:
        brightstep = -1
        brightness = 50
    elif brightstep == 1:
        brightness += brightstep
        img.fill(colour, special_flags=pygame.BLEND_RGB_ADD)
    elif brightstep == -1:
        brightness += brightstep
        img.fill(colour, special_flags=pygame.BLEND_RGB_SUB)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    pygame.display.update()

The images have already been loaded so that's not causing the error. I know it's something to do with the special_flags when I'm decreasing the brightness.

5
  • maybe you should replace ADD with SUB in first place, and replace SUB with ADD in another place. Commented Apr 19 at 18:12
  • or maybe you should keep original image, duplicate it in every loop and blend colour with duplicate image - to blend new colour always on original image (not on image changed in previous loop). Commented Apr 19 at 18:17
  • if you would work with RGBA (with transparency) then you could draw black rectangle with different transparency and this could also decrease image behind this rectangle. Commented Apr 19 at 18:25
  • you may also use black Surface with different transparency using .set_alpha() Commented Apr 19 at 19:18
  • Okay, I have updated my answer below to demonstrate a working solution. Hope this is useful for you! Commented Apr 20 at 3:04

1 Answer 1

6

Alright, updates!

The Problem

The problem is that you are performing a destructive modification to your images. You can't go backwards.

In simple terms, you can only add increasing whiteness to a color before that color is pure white (or close to it). Once you have a pixel that is that washed out, there is no way to get back to what the color originally was -- that information is lost!

The correct way to manage brightness is not to touch the original image data at all. Use another solid-color image over top of the original image to mix in color.

Pure PyGame Method

Here is an example of how to do that in pure PyGame. It uses a couple of images that I snagged off of the internet.

background.jpg background.jpg Beggin, by Sonja Cristoph @BlenderArtists.org
Resized to 600×328 pixels

sprite.png
sprite.png
AI-generated image snagged from freepik.com
Resized to 200×219 pixels

Now for the code! Wave the sprite (get it?) around and see her turn from shade to pixie (get it?). If you hold the mouse button down you keep her mood stable.

import pygame, sys
from pygame.locals import *
from pygame.math import Vector2

pygame.init()
pygame.mouse.set_visible( False )

# Load images
background    = pygame.image.load( "background.jpg" );
sprite        = pygame.image.load( "sprite.png" )
sprite_offset = Vector2( sprite.get_width() / 2, sprite.get_height() / 2 )

# Create main window
display       = pygame.display.set_mode( background.get_size() )

# Shade zones
edge_width    = 25
dark_min      =                           edge_width
dark_max      = display.get_width() / 2 - edge_width
light_min     = display.get_width() / 2 + edge_width
light_max     = display.get_width()     - edge_width

# The image we actually display
shaded_sprite = sprite


def darken( percent ):
    # A simple linear function looks good here
    L = int( 255 * percent )
    shaded_sprite.fill( (L,L,L), special_flags=pygame.BLEND_RGB_MULT )

    
def lighten( percent ):
    # A decelerator function gets us a little more color before total whiteness
    # (Exponent found by a little experimentation)
    L = int( 255 - 255 * (1 - percent) ** .5 )
    shaded_sprite.fill( (L,L,L), special_flags=pygame.BLEND_RGB_ADD )


# Main loop
while True:

    # If (Window closed) or (Esc key pressed)
    for event in pygame.event.get():
        if ((event.type == QUIT)
        or ((event.type == KEYDOWN) and (event.key == 27))):
            pygame.quit()
            sys.exit()

    # Update the sprite's shade (unless the mouse button is held down)
    if not pygame.mouse.get_pressed()[0]:
        x = pygame.mouse.get_pos()[0]
        shaded_sprite = sprite.copy() 
        if   x < dark_min:  darken ( 0 )
        elif x < dark_max:  darken ( (x - dark_min) / (dark_max - dark_min) )
        elif x < light_min: pass
        elif x < light_max: lighten( (x - light_min) / (light_max - light_min) )
        else:               lighten( 1 )

    # Draw everything
    display.blit( background, (0, 0) )
    display.blit( shaded_sprite, pygame.mouse.get_pos() - sprite_offset )
    pygame.display.update()

The trick is to:

  1. Create a copy of the original image, to which we
  2. Apply a quick transform by blitting a color on top of it.

To darken the image a simple linear multiplication works just fine. You see her eyes to the very end.

To lighten the image a simple linear addition washes things out too quickly, so I used an interpolation function called a decelerator, which has the form f(t, n) := 1 - (1 - t)**n. That gives us a nicer curve that goes slowly toward 1.0 until near the very end, where it quickly rises to 1.0. I found the value n=0.5 by a little experimentation. This produces the same effect as the darken — you see her eyes until the very end. Try dragging back and forth to explore that aspect of it.

Notice the PyGame blit flags:

  • Multiply for darken
  • Add for lighten
  • RGB (no alpha) to leave the sprite’s alpha alone

Caveats

This is the pure PyGame (CPU-bound) method for doing it. The consequence is that this is relatively slow, meaning that you should avoid doing this to large numbers of sprites at once.

Notice also how the code is designed to update the shaded sprite only as needed. (The program kind of does it anyway, unless you hold the mouse button down, but the design is to make it only necessary to update iff the shade changes.)

If you wish to do more than this, or to just be blindingly fast anyway because you profiled it and discovered that this is a bottleneck, you may wish to consider a solution using the pygame_shader module, which allows pygame to use GPU-bound OpenGL stuff.

In particular, you could use a GLES shader fragment to do this kind of stuff, and even more awesome things — all without bogging down your CPU with a bazillion sprite manipulations every frame.

Sign up to request clarification or add additional context in comments.

3 Comments

I think this idea is correct - OP should work always on original image because it has all original information about colors.
Thanks for the answer, I think your idea is correct and it makes complete sense, so thanks for the answer. If you want, you can add further details, it would really help. Thanks anyways.
This is a good answer. To save CPU (at the expense of memory) I would pre-generate all/some-of the differing brightness sprites. Then it's just a matter of blitting the requite one to the screen, which you're doing anyway. This might be memory prohibitive, but you'll have to judge the trade-off.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.