And so, I wrote a test function to invert an image in a specific area and shape with the ability to change the location and size. The only problem is that the larger the size of the mask, the slower the mask moves or enlarges. Are there any solutions to speed up the code?
import pygame
fps = pygame.time.Clock()
pygame.init()
pygame.event.set_allowed([pygame.QUIT])
# declare all necessary vars
image = pygame.image.load('...\\image.png')
org_mask_inversion = pygame.image.load('...\\mask_inversion.png')
ix, iy = 0, 0
size_x, size_y = 100, 100
current_ix, current_iy = ix, iy
current_size_x, current_size_y = size_x, size_y
screen = pygame.display.set_mode([960, 720])
mask_inversion = pygame.transform.scale(org_mask_inversion, (size_x, size_y))
image2 = image
# function that inverts color values
def inversion_of_color(r, g, b):
return [255 - r, 255 - g, 255 - b]
# image inversion function
def inversion_of_surface(surface, sx, sy):
# it creates a new surface of the same size as the one specified in the function
inverted_surface = pygame.surface.Surface(mask_inversion.get_size(), pygame.SRCALPHA)
# passes over the entire size of the mask
for x in range(mask_inversion.get_width()):
for y in range(mask_inversion.get_height()):
# takes the color of the mask pixel
m_color = mask_inversion.get_at((x, y))
# checks if the pixel is green color
if m_color.g == 255:
# checks if the mask extends beyond the screen area
if 0 <= sx + x <= screen.get_width() - 1 and 0 <= sy + y <= screen.get_height() - 1:
# takes the color of a surface pixel
color = surface.get_at((x + sx, y + sy))
# inverts the pixel color and inserts the inverted pixel into the created surface
inverted_color = inversion_of_color(color.r, color.g, color.b)
inverted_surface.set_at((x, y), inverted_color)
return inverted_surface
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
fps.tick(60)
keys = pygame.key.get_pressed()
pygame.display.set_caption(f'Inverted Image {round(fps.get_fps())} x:{ix} y:{iy}')
if keys[pygame.K_LEFT]:
ix -= 6
elif keys[pygame.K_RIGHT]:
ix += 6
if keys[pygame.K_UP]:
iy -= 6
elif keys[pygame.K_DOWN]:
iy += 6
if keys[pygame.K_a]:
size_x -= 6
size_y -= 6
mask_inversion = pygame.transform.scale(org_mask_inversion, (size_x, size_y))
elif keys[pygame.K_d]:
size_x += 6
size_y += 6
mask_inversion = pygame.transform.scale(org_mask_inversion, (size_x, size_y))
# checks if the size or location of the mask has changed
if current_ix != ix or current_iy != iy or current_size_x != size_y or current_size_x != size_x:
image2 = inversion_of_surface(image, ix, iy)
current_ix, current_iy = ix, iy
current_size_x, current_size_y = size_x, size_y
screen.blit(image, (0, 0))
screen.blit(image2, (ix, iy))
pygame.display.update()
pygame.quit()
Here's the image of the mask itself enter image description here
I tried different ways to cache the result, I tried to use numpy arrays, and tried to check the already changed pixels, but all to no avail.
You can achieve what you want with Pygame functions alone and without for
loops:
pygame.mask.from_surface
and pygame.mask.Mask.to_surface
to prepare the mask. The mask must consist of two colors, opaque white (255, 255, 255, 255) and transparent black (0, 0, 0, 0):mask = pygame.mask.from_surface(inversionMaskImage)
inversionMask = mask.to_surface(setcolor=(255, 255, 255, 255), unsetcolor=(0, 0, 0, 0))
pygame.Surface.subsurface
subSurface = surface.subsurface(pygame.Rect((sx, sy), mask.get_size()))
BLEND_SUB
mode (see pygame.Surface.blit
)finalImage = mask.copy()
finalImage.blit(invertedArea, (0, 0), special_flags = pygame.BLEND_MULT)
See also Blending and transparency and Clipping
Minimal example
import pygame
pygame.init()
screen = pygame.display.set_mode((1024, 683))
clock = pygame.time.Clock()
image = pygame.image.load('image/parrot1.png').convert_alpha()
inversionMaskImage = pygame.Surface((200, 200), pygame.SRCALPHA)
pygame.draw.circle(inversionMaskImage, (255, 255, 255), inversionMaskImage.get_rect().center, inversionMaskImage.get_width()//2)
mask = pygame.mask.from_surface(inversionMaskImage)
inversionMask = mask.to_surface(setcolor=(255, 255, 255, 255), unsetcolor=(0, 0, 0, 0))
def invert_surface(surface, mask, sx, sy):
areaRect = pygame.Rect((sx, sy), mask.get_size())
clipRect = areaRect.clip(surface.get_rect())
subSurface = surface.subsurface(clipRect)
finalImage = mask.copy()
finalImage.blit(subSurface, (clipRect.x - areaRect.x, clipRect.y - areaRect.y), special_flags = pygame.BLEND_SUB)
return finalImage
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
areaRect = pygame.Rect(pygame.mouse.get_pos(), (0, 0)).inflate(inversionMask.get_size())
invertedArea = invert_surface(image, inversionMask, areaRect.x, areaRect.y)
screen.fill('black')
screen.blit(image, (0, 0))
screen.blit(invertedArea, areaRect)
pygame.display.flip()
pygame.quit()
exit()