Search code examples
pythonpygametexturestransparencymask

Texturing drawn shapes - python pygame


Im currently using the random import to create five random x, y values and taking those values and drawing a polygon with the pygame.draw.polygon () command. If I had a texture square I wanted to apply over top of that shape instead of having just on rgb value what would be the most efficient way to do that? i want to take the generated polygon below and with out hard coding its shape, taking a general texture square and making all that green that new texture as if that shape was cut out of the texture square.

import pygame,random
from pygame import*

height = 480
width = 640
#colors

red = (255,0,0)
green = (0,255,0)
blue = (0,0,255)
white = (255,255,255)
black = (0,0,0)

pygame.init()


points = [ ]

screen = pygame.display.set_mode((width,height))
pygame.display.set_caption("PlayBox")

r = random

for i in range(0,5):
    x = r.randrange(0,640)
    y = r.randrange(0,480)
    points.append([x,y])

running = True
while running == True:
    screen.fill(white)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            break

    pygame.draw.polygon(screen,green,points,0)
    pygame.display.update()      
pygame.display.update()

enter image description here


Solution

  • One option, of course, would be to re-implement the "bucket fill" algorithm yourself, and copy pixels inside the polygon. That would be a lot of work, and wouldget slow done in pure Python - still, it would launch you into the basic foundations of image manipulation http://en.wikipedia.org/wiki/Flood_fill

    Since Pygame already does the heavy lifting, but provides just solid color fills, the way to go is to use pygame's results as a clipping mask to your texture. Unfortunatelly that is probably more difficult than it should. I hope my sample here can be useful for others having the same needs.

    Pygame gives us some primitives to manipulate the color planes in the surfaces, but they are definitely low level. Another thing is that these primitives require numpy to be installed - I am not certain if Window's pyagames installer include it - otherwise people running your project have to be told to install numpy themselves.

    So, teh way to go is: Load your desired texture in a surface (for less headache, one of the same size of the final image), to draw the shape you want to be painted with the texture in a mask surface, with 8bpp (B&W) - which works as a transparency map to the texture - them use pygame's surfarray utilities to blit everything together:

    # coding: utf-8
    
    import random
    
    import pygame
    
    SIZE = 800,600
    
    def tile_texture(texture, size):
        result = pygame.Surface(size, depth=32)
        for x in range(0, size[0], texture.get_width()):
            for y in range(0, size[1], texture.get_height()):
                result.blit(texture,(x,y))
        return result
    
    
    def apply_alpha(texture, mask):
        """
        Image should be  a 24 or 32bit image,
        mask should be an 8 bit image with the alpha
        channel to be applied
        """
        texture = texture.convert_alpha()
        target = pygame.surfarray.pixels_alpha(texture)
        target[:] = pygame.surfarray.array2d(mask)
        # surfarray objets usually lock the Surface. 
        # it is a good idea to dispose of them explicitly
        # as soon as the work is done.
        del target
        return texture
    
    def stamp(image, texture, mask):
        image.blit(apply_alpha(texture, mask), (0,0))
    
    
    def main():
        screen = pygame.display.set_mode(SIZE)
        screen.fill((255,255,255))
        texture = tile_texture(pygame.image.load("texture.png"), SIZE)
        mask = pygame.Surface(SIZE, depth=8)
        # Create sample mask:
        pygame.draw.polygon(mask, 255, 
                            [(random.randrange(SIZE[0]), random.randrange(SIZE[1]) )
                             for _ in range(5)] , 0)
    
        stamp(screen, texture, mask)
        pygame.display.flip()
        while not any(pygame.key.get_pressed()):
            pygame.event.pump()
            pygame.time.delay(30)
    
    
    if __name__ == "__main__":
        pygame.init()
        try:
            main()
        finally:
            pygame.quit()