pythonpygamebit-masks

Pygame - is there any way to only blit or update in a mask


Is there any way in pygame to blit something to the screen inside a mask. Eg: if you had a mask where all the bits were set to 1 except for the topleft corner and a fully black image, without changing the image, could you keep the top left corner (same as the mask) clear? Only updating a mask (rather than a rect) would help to.


Solution

  • Ok, if I'm understanding your question properly, the trick is the BLEND_RGBA_MULT flag.

    I decided to test this for myself because I was curious. I started with this image:

    320x240 leaves

    I made an image with white where I wanted the image to show, and transparency where I wanted it masked. I even gave it varying levels of transparency to see if I could get fuzzy masking.

    Fuzzy circular mask
    

    ^^^ It's difficult to see the image because it's white on transparent, but it's there. You can just right click and download it.

    I loaded the two images, making sure to use convert_alpha():

    background = pygame.image.load("leaves.png").convert_alpha()
    mask = pygame.image.load("mask-fuzzy.png").convert_alpha()
    

    Then to mask the image, I made a copy of the image being masked,

    masked = background.copy()
    

    ...I blitted the mask onto this copy using BLEND_RGBA_MULT,

    masked.blit(mask, (0, 0), None, pygame.BLEND_RGBA_MULT)
    

    ...and I drew it to the screen.

    display.blit(masked, (0, 0))
    

    Sure enough, it worked:

    result

    Here's the complete code I used.

    import pygame
    from pygame.locals import *
    
    pygame.init()
    display = pygame.display.set_mode((320, 240))
    
    background = pygame.image.load("leaves.png").convert_alpha()
    mask = pygame.image.load("mask-fuzzy.png").convert_alpha()
    
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
        # draw
        display.fill(Color(255, 0, 255))
        masked = background.copy()
        masked.blit(mask, (0, 0), None, pygame.BLEND_RGBA_MULT)
        display.blit(masked, (0, 0))
        pygame.display.flip()
    

    If you want a variable mask, you could try manually editing a Surface and using that as your mask.

    Edit: Here's an example of generating a mask by editing a Surface:

    mask = pygame.Surface((320, 240), pygame.SRCALPHA)
    for y in range(0, 240):
        for x in range(0, 320):
            if (x/16 + y/16) % 2 == 0:
                mask.set_at((x, y), Color("white"))
    

    It produces this result:

    checkerboard mask result

    Now I want to use this in a game!