How do I palette swap sprites in pygame without the rectangle in the back

90 Views Asked by At

Player sprite without palette swap

Colored rectangle appears behind sprite

So I am making an auto generated game where in each level there is a different color palette. For now, I want to switch one color inside my player sprite. I used this video: https://www.youtube.com/watch?v=MLrDR-Mks8I to make a function that takes in the color I want to swap and the new color to replace it. However, when I do this, it replaces the color in the sprite but also creates a rectangle around it that is the same color as the new color. This is probably because I am filling the new surface and creating a colorkey for the old surface, which fills in the whole hitbox of the sprite.

def palette_swap(self, surf, old_c, new_c):
    img_copy = pygame.Surface(surf.get_size())
    img_copy.fill(new_c)
    surf.set_colorkey(old_c)
    img_copy.blit(surf, (0, 0))
    return img_copy

def animate(self):

image = self.palette_swap(image, (255, 148, 66), (249, 4, 99))

I don't want to upload all my code because it is a lot but just know the animate function updates the image. Here is what happens when I apply this function to every sprite:

Image

For reference there isn't supposed to be rectangles around each sprite. Is there another approach to palette swapping in pygame that doesn't require this method? And no, this isn't the same as changing the hue of a sprite, I am changing the palette, which means I am changing the colors individually. I thought this wouldn't be difficult since I have a color palette with only 4 colors, and I am looking to change only 2 of them for each level.

1

There are 1 best solutions below

1
Rabbid76 On

You have to use pygame.mask fro your task.

  1. Create a Mask from the color

    color_mask = pygame.mask.from_threshold(image, old_c, threshold=(1, 1, 1, 255))
    
  2. Convert the Mask to a Surface with the new color

    color_change_surf = color_mask.to_surface(setcolor=new_c, unsetcolor=(0, 0, 0, 0))
    
  3. Copy the image and draw the color mask surface on top of it

    img_copy = surf.copy()
    img_copy.blit(color_change_surf, (0, 0))
    
def palette_swap(self, surf, old_c, new_c):
    color_mask = pygame.mask.from_threshold(image, old_c, threshold=(1, 1, 1, 255))
    color_change_surf = color_mask.to_surface(setcolor=new_c, unsetcolor=(0, 0, 0, 0))
    img_copy = surf.copy()
    img_copy.blit(color_change_surf, (0, 0))
    return img_copy

Minimal example

import pygame

pygame.init()
window = pygame.display.set_mode((350, 200))
clock = pygame.time.Clock()

def palette_swap(surf, old_c, new_c):
    color_mask = pygame.mask.from_threshold(image, old_c, threshold=(1, 1, 1, 255))
    color_change_surf = color_mask.to_surface(setcolor=new_c, unsetcolor=(0, 0, 0, 0))
    img_copy = surf.copy()
    img_copy.blit(color_change_surf, (0, 0))
    return img_copy

image = pygame.Surface((100, 100))
image.fill((0, 255, 0))
image.set_colorkey((0, 255, 0))
pygame.draw.circle(image, "black", (50, 50), 50)
pygame.draw.circle(image, (255, 148, 66), (50, 50), 40)
pygame.draw.circle(image, "black", (30, 35), 10)
pygame.draw.circle(image, "black", (70, 35), 10)

new_iamge = palette_swap(image, (255, 148, 66), (249, 4, 99))

run = True
while run:
    clock.tick(100)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False 
    
    window.fill("gray")
    window.blit(image, image.get_rect(center = (100, 100)))
    window.blit(new_iamge, new_iamge.get_rect(center = (250, 100)))
    pygame.display.flip()

pygame.quit()
exit()