Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong alpha values applied when blitting to an SRCALPHA Surface #2706

Closed
bigwhoopgames opened this issue Feb 4, 2024 · 2 comments
Closed
Labels
bug Not working as intended

Comments

@bigwhoopgames
Copy link

Environment:

Windows 10
pygame-ce 2.4.0 (SDL 2.28.5, Python 3.12.0)

Current behavior:

When blitting a Surface with a global alpha value to an SRCALPHA Surface the alpha value is applied twice.

Expected behavior:

Each pixel of the SRCALPHA surface should maintain the alpha value of the surface blitted to it and not apply the global alpha value a second time. In the below code the two images should be identical.

Test code

import os
import sys
import pygame

pygame.init()

width, height = 128, 64
screen = pygame.display.set_mode((width, height), flags = pygame.SCALED)
pygame.display.set_caption("Test")

clock = pygame.time.Clock()

cobweb = pygame.image.load(os.path.join('assets/sprites/entities', 'cobweb1.png'))
cobweb.convert()
cobweb.set_colorkey('white')
cobweb.set_alpha(224)

test_surface = pygame.Surface(cobweb.get_size(), flags = pygame.SRCALPHA)
test_surface.blit(cobweb, (0, 0))

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    screen.fill('black')

    screen.blit(cobweb, (0, 0))
    screen.blit(test_surface, (50, 0))

    pygame.display.flip()

    clock.tick(60)

image

@bigwhoopgames bigwhoopgames added the bug Not working as intended label Feb 4, 2024
@MyreMylar
Copy link
Member

MyreMylar commented Feb 4, 2024

I think the main issue you are encountering here is the classic 'straight alpha' composition problem.

For example if I switch to premultiplied blending, the problem goes away:

import os
import sys
import pygame

pygame.init()

width, height = 128, 64
screen = pygame.display.set_mode((width, height), flags=pygame.SCALED)
pygame.display.set_caption("Test")

clock = pygame.time.Clock()

cobweb = pygame.image.load(os.path.join("images", "cobweb.png"))
cobweb.convert()
cobweb.set_colorkey("white")
cobweb.set_alpha(225)
cobweb = cobweb.convert_alpha().premul_alpha()

test_surface = pygame.Surface(cobweb.get_size(), flags=pygame.SRCALPHA)
test_surface.blit(cobweb, (0, 0), special_flags=pygame.BLEND_PREMULTIPLIED)

font = pygame.font.Font(size=16)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    screen.fill("black")

    screen.blit(cobweb, (0, 0), special_flags=pygame.BLEND_PREMULTIPLIED)
    screen.blit(test_surface, (50, 0), special_flags=pygame.BLEND_PREMULTIPLIED)

    text = str(screen.get_at(pygame.mouse.get_pos()))

    text_surf = font.render(text, True, pygame.Color("white"))
    screen.blit(text_surf, (0, 48))

    pygame.display.flip()

    clock.tick(60)

It is just a bit of a confusing way of getting there as setting the global alpha switches a colorkey surface to use standard alpha blending rather than the special color key blending.

If this is for a specific workflow rather than a general problem you are having with 'straight alpha' blending, you could just fill the intermediate transparent surface with white in the RGB and the blend will be a lot closer:

import os
import sys
import pygame

pygame.init()

width, height = 128, 64
screen = pygame.display.set_mode((width, height), flags=pygame.SCALED)
pygame.display.set_caption("Test")

clock = pygame.time.Clock()

cobweb = pygame.image.load(os.path.join("images", "cobweb.png"))
cobweb.convert()
cobweb.set_colorkey("white")
cobweb.set_alpha(224)


test_surface = pygame.Surface(cobweb.get_size(), flags=pygame.SRCALPHA)
test_surface.fill((255, 255, 255, 0))
test_surface.blit(cobweb, (0, 0))

font = pygame.font.Font(size=16)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

    screen.fill("black")

    screen.blit(cobweb, (0, 0))
    screen.blit(test_surface, (50, 0))

    text = str(screen.get_at(pygame.mouse.get_pos()))

    text_surf = font.render(text, True, pygame.Color("white"))
    screen.blit(text_surf, (0, 48))

    pygame.display.flip()

    clock.tick(60)

You can read more about the composition problems with straight alpha blending and why premultipled is superior over here:
What is Premultiplied alpha?

@MyreMylar
Copy link
Member

I'm going to close this now, because this is just the results of the 'straight alpha' blending formula when blending two surfaces with alpha and there isn't really anything we can do about it.

You are basically getting (255-225) = 30 alpha worth of extra black over what you were intuitively expecting. This (blending onto a 'transparent' surface) is basically the standard case which makes people discover pre-multiplied alpha blending.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Not working as intended
Projects
None yet
Development

No branches or pull requests

2 participants