Pygame - is there any way to only blit or update i

2020-01-29 08:35发布

问题:

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.

回答1:

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:

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.

^^^ You probably can't 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:

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:

Now I want to use this in a game!



回答2:

So if I understand correctly, the question was - can one use bit-masks for blitting. The answer is yes and no. AFAIK, there is no support for 1-bit masks in pygame, that means, you must use 32-bpp RGBA (SRCALPHA flag) for source surface to blit with transparency and in this case you use 8-bit mask = 256 grades of transparency per pixel and this is described in previous answer. The other option is to use 'colorkey' blitting. In this case it is more similar to 1-bit mask and is used with standard 24-bpp RGB surfaces. You just set what color in source surface you do not want to write to destination. See 'Surface.set_colorkey' in documentation. The problem with colorkey method however is that you can accidentally have some pixels in source, wich have same color as colorkey, but which you still want to draw. So you must be careful. I personally use RGBA surfaces for masking, it is more powerful, but almost always I don't need 8-bits, 1-bit mask would be enough. So it seems to me kind of strange that there is no support for per bit-mask blitting. The same is with buffer flipping - you can describe the areas of the screen buffer to update with a list of rectangles only. More flexible and straightforward would be to use bit masks, but afaik that is not possible too. Probably converting a bit mask to a list of rectangles is the way, but I didn't try it. Third options is not to use pygame's surface and use Numpy for your data, and then use array blitting:

pygame.surfarray.blit_array(Destination_surface, my_array)

Then you are free to make anything you want with you data, but that is another story.