Eternal Ambiguity
Eternal Ambiguity

Reputation: 115

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?

Upvotes: 1

Views: 784

Answers (2)

Party-with-Programming
Party-with-Programming

Reputation: 301

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

Upvotes: 1

Rabbid76
Rabbid76

Reputation: 211277

pygame.Surface.blit() does not replace the pixels in the target surface with the source surface. It mixes (blends) the pixels of the surfaces. Actually you are blending the new surface with the old previous surface every frame. This means that the area of the surface appears uniformly colored within milliseconds. You have to clear the screen in every frame:

while True:
    # [...]

    base_screen.fill((0, 0, 0))
    base_screen.blit(surface, (0,0))
    pygame.display.flip()

Minimal example:

import pygame

pygame.init()

width, height = 200, 200
base_screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()

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])

for x in range(board_size[0]):
    for y in range(board_size[1]):
        a = surface.get_at((x, y))
        a[3] = round(y / board_size[1] * 255)
        surface.set_at((x, y), a)

run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
                
    base_screen.fill((100, 100, 100))
    base_screen.blit(surface, surface.get_rect(center = base_screen.get_rect().center))
    pygame.display.flip()

Upvotes: 0

Related Questions