0
\$\begingroup\$

I'm working on my first game, and I decided to add some trees that twinkle to the game. For some reason when the image is blitted to the screen using pygames blit function it changes the color of the yellow parts of the tree where it twinkles. enter image description here

Some googling led me to suspect the alpha channels could be the issue so I changed the load part of the image to use convert_alpha() to take care of transparency, which then displays the bottom tree correctly under a black background, but I get strange shading on the rest of the images that are being loaded prior to the trees, and as you can see, the yellow parts now have black rings around them.

enter image description here

I initially used these two lines of code to load the images.

self.sheet = pygame.image.load(filename).convert()

screen.blit(tileImage, (centered_x, centered_y))

The actual image looks like this when I open it up in Paint3D enter image description here

Here is the actual class I'm using to process my sprite sheets

class spritesheet(object):
    def __init__(self, filename):
        try:
            self.sheet = pygame.image.load(filename).convert()
        except pygame.error as message:
            print('Unable to load spritesheet image:', filename)
            raise SystemExit(message)
    #Load a specific image from a specific rectangle
    def image_at(self, rectangle, colorkey = None):
        "Loads image from x,y,x+offset,y+offset"
        rect = pygame.Rect(rectangle)
        image = pygame.Surface(rect.size).convert()
        image.blit(self.sheet, (0, 0), rect)
        if (colorkey is not None):
            if (colorkey == -1):
                colorkey = image.get_at((0,0))
            image.set_colorkey(colorkey, pygame.RLEACCEL)
        return image
    # Load a whole bunch of images and return them as a list
    def images_at(self, rects, colorkey = None):
        "Loads multiple images, supply a list of coordinates" 
        return [self.image_at(rect, colorkey) for rect in rects]
    # Load a whole strip of images
    def load_strip(self, rect, image_count, colorkey = None):
        "Loads a strip of images and returns them as a list"
        tups = [(rect[0]+rect[2]*x, rect[1], rect[2], rect[3])
                for x in range(image_count)]
        return self.images_at(tups, colorkey)

    def load_sheet(self,width,height,rows,image_count, colorkey = None):
        '''Loads an entire sprite sheet, assumes sprites are equally spaced, returns them as list of lists '''
        sprite_sheet = []
        for i in range(rows):
            sprite_sheet.append(self.load_strip((0, 0+i*height, width, height),image_count,colorkey))

        print('len(sprite_sheet)',len(sprite_sheet))
        return sprite_sheet

Any ideas what could cause this? It's not displaying the shadows of the tree either which I thought was odd, but that is a separate question I think.

Edit Here is a full example code. The images I'm using can be downloaded here

import pygame
import os
import errno
from pathlib import Path


TILEWIDTH = 143  #holds the tile width and height
TILEHEIGHT = 137
TILEHEIGHT_HALF = TILEHEIGHT /2
TILEWIDTH_HALF = TILEWIDTH /2
DEBUG = False

grass = [(0,0)]

class Enum(tuple): 
    '''Allows enumerations to be defined as 
       options = Enum(['COLOR','NOCOLOR'])
       Acccessing enum: options.COLOR returns 0, options.NOCOLOR returns 1
     '''
    __getattr__ = tuple.index

options = Enum(['CONVERTALPHA','CONVERT'])    

class spritesheet(object):
    def __init__(self, filename,load_options=None):
        try:
            if (load_options == options.CONVERTALPHA):
                self.sheet = pygame.image.load(filename).convert_alpha()
            elif (load_options == options.CONVERT):
                self.sheet = pygame.image.load(filename).convert()
            elif (load_options == None):
                self.sheet = pygame.image.load(filename).convert()
        except pygame.error as message:
            print('Unable to load spritesheet image:', filename)
            raise SystemExit(message)
    # Load a specific image from a specific rectangle
    def image_at(self, rectangle, colorkey = None):
        "Loads image from x,y,x+offset,y+offset"
        rect = pygame.Rect(rectangle)
        image = pygame.Surface(rect.size).convert()
        image.blit(self.sheet, (0, 0), rect)
        if (colorkey is not None):
            if (colorkey == -1):
                colorkey = image.get_at((0,0))
            image.set_colorkey(colorkey, pygame.RLEACCEL)
        return image
    # Load a whole bunch of images and return them as a list
    def images_at(self, rects, colorkey = None):
        "Loads multiple images, supply a list of coordinates" 
        return [self.image_at(rect, colorkey) for rect in rects]
    # Load a whole strip of images
    def load_strip(self, rect, image_count, colorkey = None):
        "Loads a strip of images and returns them as a list"
        tups = [(rect[0]+rect[2]*x, rect[1], rect[2], rect[3])
                for x in range(image_count)]
        return self.images_at(tups, colorkey)

    def load_sheet(self,width,height,rows,image_count, colorkey = None):
        '''Loads an entire sprite sheet, assumes sprites are equally spaced, returns them as list of lists '''
        sprite_sheet = []
        for i in range(rows):
            sprite_sheet.append(self.load_strip((0, 0+i*height, width, height),image_count,colorkey))

        print('len(sprite_sheet)',len(sprite_sheet))
        return sprite_sheet

map = [[0, 0, 0,],
        [0, 30, 0]]
     

def get_file_path(file_directory):
    '''
    get_file_path 
    Description: Get absolute file path from current working directory, it will 
                 raise a FileNotFoundError exception if the file doesn't exist

    Inputs: file_directory- filepath from running directory

    
    Outputs: Absolute path to file

    run_directory - Is the current run path, while file_directory is the file path
    below the current root directory.
    '''
    run_directory = os.path.dirname(__file__)
    file_path = Path(run_directory + file_directory)

    if (not os.path.isfile(file_path) ):
        raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),file_path)

    return str(file_path)


class Map():
    def __init__(self,world):
        self.map = world
        ss = spritesheet(get_file_path('/Assets/Textures/Grass-Spritesheet_Blocks.png'))
        tree_sheet = spritesheet(get_file_path('/Assets/Textures/trees_twinkle.png'),options.CONVERTALPHA)
        self.grass = ss.load_sheet(192,192,5,6,(0,0,0))
        self.trees = tree_sheet.load_sheet(192,192,1,4)

        self.init = True

        self.rect = []
        self.previous_update = False


    def render(self,screen,updatemap):
        #Only update map when player screen changes
        self.reset = False
        self.foreground = []
        if (True):#(self.init == True) or True):
            tree_width = 100
            tree_height = 100
            actual_tree_width = 175
            actual_tree_height = 182
            for row in range(0,len(self.map)):
                for col in range(0, len(self.map[row])):

                    cart_x = col * TILEWIDTH_HALF
                    cart_y = row * TILEHEIGHT_HALF  
                    iso_x = (cart_x - cart_y) 
                    iso_y = (cart_x + cart_y)/2
                    centered_x = screen.get_rect().centerx + iso_x
                    centered_y = screen.get_rect().centery/2 + iso_y

                    if (self.map[row][col] < 30):
                        tileImage = self.grass[grass[self.map[row][col]][0]][grass[self.map[row][col]][1]]
                    else:
                        tileImage = self.grass[grass[0][0]][grass[0][1]]
                        tileForeground = self.trees[0][0]

                        self.foreground.append({'image': self.trees[0][0], 'coordinates': (centered_x,centered_x)})
                        
                    
                    if (self.map[row][col] < 30):
                        screen.blit(tileImage, (centered_x, centered_y))
                    else:
                        screen.blit(tileImage, (centered_x, centered_y))
                        screen.blit(tileForeground,(centered_x, centered_y-96),special_flags=pygame.BLEND_PREMULTIPLIED)




            self.init = False

if __name__ == '__main__':
    import pygame
    from pygame.locals import ( K_w, K_s, K_a, K_d, K_UP, K_DOWN, K_ESCAPE, KEYDOWN, QUIT, )
    running = True
    pygame.init()
    screen = pygame.display.set_mode((800, 600),pygame.DOUBLEBUF)
    while running:
        for event in pygame.event.get():
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    running = False
            elif event.type == QUIT:
                running = False

        worldMap = Map(map)
        worldMap.render(screen,False)
        pygame.display.update()

There's a colorkey option for the load_sheet which is set to black. I've tried removing that

\$\endgroup\$
6
  • 1
    \$\begingroup\$ It looks like you're drawing with an alpha test / conditional copy (output value is either the existing background pixel colour or the new sprite pixel, never anything in between) instead of applying alpha blending to feather between the background and foreground sprite. Have you tried using something like the BLEND_PREMULTIPLIED flag described here? \$\endgroup\$
    – DMGregory
    Apr 25, 2021 at 12:18
  • \$\begingroup\$ Well, that works to display the image properly, but adds a black background to the tree image over the top of the grass images anywhere it's been blitted over. \$\endgroup\$ Apr 25, 2021 at 20:45
  • \$\begingroup\$ Sounds like the alpha information is missing from your image when you apply the blending, so it's blending as if opaque. I notice you have some code that seems to be written for colour key transparency instead of alpha transparency - is it possible the colour key transparency is overriding / removing the alpha when you load and prepare the asset? \$\endgroup\$
    – DMGregory
    Apr 25, 2021 at 20:49
  • \$\begingroup\$ It's possible. I pulled this code from the internet, to load sprite sheets, and I don't understand everything it's doing. I removed the colorkey argument from my blits, and that doesn't change anything. Also added a full example with the images that can be run. I'm honestly not sure what the difference is between color key transparency vs alpha transparency. I assume you're referring to the image_at function color_key part of the code? This is what I get for using code I don't understand. \$\endgroup\$ Apr 25, 2021 at 21:14
  • \$\begingroup\$ @DMGregory I figured out a solution. I needed to add pygame.SRCALPHA and convert_alpha to both the surface being blitted and the loaded image as well. Not sure why I had to use convert_alpha on both, but it works. \$\endgroup\$ Apr 26, 2021 at 0:24

1 Answer 1

1
\$\begingroup\$

For anybody else that runs into an issue like this, here is what worked for me.

Add SRCALPHA to the Surface you are blitting to, and use convert_alpha() on the surface when there is transparency.

image = pygame.Surface(rect.size,pygame.SRCALPHA).convert_alpha()

It's important to use convert_alpha() on the loaded image as well or it won't treat the transparency properly.

self.sheet = pygame.image.load(filename).convert_alpha()

These two things worked for me.

Now I get this when I run the code. The suggestion to use pygame.BLEND_PREMULTIPLIED did not correct the issue.

enter image description here

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .