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.
Alright, updates!
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.
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
Beggin, by Sonja Cristoph @BlenderArtists.org
Resized to 600×328 pixels
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:
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:
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.