PyGame: translucent sprites with per pixel alpha

If your input image has per-pixel alpha enabled, but is only using a single bit for transparency (e.g., .png sprites with a transparent background or text), you can convert the transparent background to a colorkey-alpha background pretty easily, and then use set_alpha() to blend the whole texture onto whatever.

This is a quick and dirty helper function I used for fading text effects and other things:

def convert_to_colorkey_alpha(surf, colorkey=pygame.color.Color("magenta")):
    newsurf = surface.Surface(surf.get_size())
    newsurf.fill(colorkey)
    newsurf.blit(surf, (0, 0))
    newsurf.set_colorkey(colorkey)
    return newsurf

A couple of other tricks potentially solve this problem might be:

  • Doing per-pixel editing to modify the alpha channel before blitting
  • Blitting a white or black-filled, semi-transparent surface with special_flags=BLEND_RGBA_MULT or BLEND_RGBA_SUB onto your source surface.

Yes you can. The documentation of the Surface class explains how to do it. It boils down to two cases only: Either you set a flag during the creation of the Surface object:

s = pygame.Surface((16, 16), flags=pygame.SRCALPHA)

Or you give an alpha channel to a surface that doesn't have one yet:

s = pygame.image.load('spam.png')
s.convert_alpha()

The documentation of the pygame.image module says that applying convert_alpha is necessary for PNGs with transparency.

If you want you can modify the alpha value of each pixel with the modules draw and surfarray. When specifying a color, use then a tuple of four integers: (red, green, blue, alpha). The alpha parameter ranges from 0 (totally transparent) to 255 (totally opaque).

import pygame
pygame.init()
screen = pygame.display.set_mode((320, 200))
screen.fill((200, 100, 200))
s = pygame.Surface((64, 64), flags=pygame.SRCALPHA)
for i in range(64):
    pygame.draw.line(s, (0, 0, 0, i*4), (0, i), (64, i))

screen.blit(s, (50, 30))
pygame.display.flip()

After checking both PyGame and SDL documentations I came to conclusion that what I asked wasn't doable in PyGame using standard functions.

SDL docs state that per-pixel alpha and per-surface alpha cannot be combined with the former always taking the precedence. The only way to achieve the effect I want is by writing a code which updates per-pixel alpha values of the source surface before the blit.


The Pygame documentation says that you can't combine surface alpha with per-pixel alpha, but if you're using Pygame 1.8.1 or newer, you can work around this by using the special_flags parameter to .blit().

Here's how I did it:

# Load an image with per-pixel alpha
s = pygame.image.load('spam.png')
s = s.convert_alpha()

# Simulate s.set_alpha(alpha)
alpha_img = pygame.Surface(s.get_rect().size, pygame.SRCALPHA)
alpha_img.fill((255, 255, 255, alpha))
s.blit(alpha_img, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)

The way this works is to create a second surface the same size as the first, and blit it on to the first using RGBA multiplication. By having the R, G and B components of the second image equal to 255, the multiplication won't affect the colour of the image, but it will scale the alpha channel by the given alpha factor.

Note that the method above differs from calling set_alpha() in that set_alpha() can be reversed by calling set_alpha(255). If you use the method above it will result in the pixel alphas of every pixel being changed, so the process cannot be straightforwardly reversed.