Search code examples
pythonpygamealpha-transparency

Modify alpha of Surface pixels directly in pygame


I have a Surface in PyGame. I want to modify the alpha values of pixels directly. I've tried doing it with the various methods that access the alpha values, but they don't seem to work.

from screeninfo import get_monitors
import pygame, os, numpy.random, pygame.surfarray

pygame.init()
FPS = 60
CLOCK = pygame.time.Clock()
monitor_info = get_monitors()
x = 0
y = 0
width = monitor_info[0].width
height = monitor_info[0].height
if len(monitor_info) > 1:
    if monitor_info[0].x < 0:
        x = 0
    else:
        x = monitor_info[0].width
    width = monitor_info[1].width
    height = monitor_info[1].height

os.environ['SDL_VIDEO_WINDOW_POS'] = "{},0".format(x)
pygame.display.init()
pygame.mouse.set_visible(False)
screen_info = pygame.display.Info()
screen_size = width, height
base_screen = pygame.display.set_mode(screen_size, pygame.NOFRAME)
base_screen.fill([100, 100, 100])
board_size = (int(min(width, height)*0.75), int(min(width, height)*0.75))
surface = pygame.Surface(board_size, pygame.SRCALPHA)
surface.fill([255, 255, 255, 255])
base_screen.blit(surface, (0,0))
pygame.display.flip()
pixels = numpy.random.uniform(low=0, high=255, size=(board_size[0], board_size[1], 3))
transparency = numpy.random.uniform(low=0, high=255, size=board_size).astype('uint8')
while True:
    events = pygame.event.get()
    ms = CLOCK.tick(FPS)
    print('\r             {}                  '.format(ms), end='')
    # pygame.surfarray.blit_array(surface, pixels)
    aa = pygame.surfarray.pixels_alpha(surface)
    aa = numpy.random.uniform(low=0, high=255, size=board_size).astype('uint8')
    del aa
    # for i in range(board_size[0]):
    #     for j in range(board_size[1]):
    #         a = surface.get_at((i,j))
    #         a[3] = 0
    #         surface.set_at((i,j), a)
    base_screen.blit(surface, (0,0))
    pygame.display.flip()

I've tried both things in the loop (pixels_array, and get_at/set_at) but neither works--the image just stays white (if I set the initial alpha to 0, it stays transparent). Does anyone know how to set per-pixel alpha values for a Surface?


Solution

  • I found your problem!! The reason why you can't see alpha is:

    a) you first set surface alpha to 255, surface.fill([255, 255, 255, 255])

    b) I believe aa = pygame.surfarray.pixels_alpha(surface) aa = numpy.random.uniform(low=0, high=255, size=board_size).astype('uint8') aren't working, however pygame.surfarray.blit_array(surface, pixels) do work (produce colours) but I don't think they have any actual Alpha.

    c) you need to fill the base_screen and THEN blit you surface. Such a common mistake but this is the main the problem.

    And finally, Tim Robert's comment about the for loop, will definitely get you your alpha!

    Here is it re-written to work (without screeninfo as I don't have that library currently):

    import pygame, os, numpy.random, pygame.surfarray
    from random import randint
    
    pygame.init()
    FPS = 60
    CLOCK = pygame.time.Clock()
    x = 50
    y = 50
    width = 500
    height = 500
    
    os.environ['SDL_VIDEO_WINDOW_POS'] = "{},0".format(x)
    #pygame.display.init(), don't actually need this
    pygame.mouse.set_visible(False)
    screen_info = pygame.display.Info()
    screen_size = width, height
    base_screen = pygame.display.set_mode(screen_size, pygame.NOFRAME)
    base_screen.fill([100, 100, 100])
    board_size = (int(min(width, height)*0.75), int(min(width, height)*0.75))
    surface = pygame.Surface(board_size, pygame.SRCALPHA)
    surface.fill([255, 0, 0]) ###could also be surface.fill([255,0,0,255]) to set the whole surface's alpha straight up if you didn't want to change each pixel later
    
    while True:
        #just so you can quit this program
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                raise SystemExit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_q:
                    pygame.quit()
                    raise SystemExit()
                    
        ms = CLOCK.tick(FPS)
    
        for i in range(board_size[0]):
             for j in range(board_size[1]):
                 a = surface.get_at((i,j))
                 a[3] = randint(0, 128)#OR SET TO REQUIRED ALPHA VALUE
                 surface.set_at((i,j), a)
    
        ##################################################
        base_screen.fill([100, 100, 100]) #NEED TO DO THIS
        ##################################################
    
        base_screen.blit(surface, (0,0))
        pygame.display.flip()
    

    (by the way, I used red as the second surface colour as I thought it would stand out better)

    Edit

    As Eternal Ambiguity stated in the comments, here is the much, much faster version, in place of the for loops:

    aa = pygame.surfarray.pixels_alpha(surface)
    aa[:] = numpy.random.uniform(low=0, high=255, size=board_size).astype('uint8')
    del aa