SDL2 Fixing Black Rim Artifacts in Multi-Layer Alpha Blending

Hello! I’m working on a project that requires clean alpha blending between multiple texture “layers” in SDL2. The specific issue I’m trying to solve is the appearance of black rim artifacts that occur when compositing semi-transparent layers, particularly visible around brush stroke edges.

Current setup:

  • Using SDL2 with SDL_image
  • ARGB8888 texture format
  • Separate base, paint, and display layers
  • Multiple compositing passes required
  • Using SDL’s built-in blend modes

Requirements for the solution:

  1. Need to eliminate the black rim artifacts while maintaining clean, smooth edges
  2. Must keep the separate layer system (can’t paint directly to base layer)
  3. Must support multiple compositing passes
  4. Must maintain alpha channel accuracy
  5. Need support for global opacity settings that affect the final composite

The issue becomes particularly visible when doing multiple compositing passes of semi-transparent content. I’ve created a minimal test case that demonstrates the problem [I’ll attach code and screenshots].

Has anyone encountered this issue before? I’m open to any approach, including different blend modes or even custom shaders if necessary.

I’ve created a YouTube video the describes the issue in a bit more visually:

I’ve created a minimal demo that illustrates the problem here:

I’m a new user, and the platform is limiting me to two links, so I placed additional links under the video to sample output, and directly to the images used in the code.

Thank you so much!
-Bret

This happens when you have a source image that has been premultiplied by its alpha channel but render with a blending mode for images that haven’t been premultiplied, so you get darkening at semi-transparent parts of the image, aka black fringing. Read more about it alpha blending and alpha channel premultiplication here.

Unfortunately, in SDL2 the default alpha blending mode is set up for non-premultiplied alpha blending, and it weirdly didn’t offer a built-in blend mode for use with premultiplied images (SDL3 does, though).

So, you have two solutions:

  1. Don’t use premultiplied alpha. If your image editing software is doing the premultiplication, then un-premultiply them at load time.
  2. Compose a custom blend mode and set it on the texture. In SDL2 the code is something like:
SDL_BlendMode premultOver = SDL_ComposeCustomBlendMode(
    SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
    SDL_BLENDOPERATION_ADD,
    SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
    SDL_BLENDFACTOR_ADD);
SDL_SetTextureBlendMode(myTexture, premultOver);

Image editing software tends to handle images as non-premultiplied because performing color operations on premultiplied images gives incorrect results.

1 Like

Thanks @sjr !

If you have a moment, I have a few follow up questions:

  • There’s probably no particular reason that I’m using SDL2 for this project. If I switch to SDL3, what would I search for to learn more about the built-in blend mode for use with premultiplied images?

  • Do you know of any existing demos that show this type of alpha layering working correctly in either SDL2 or 3? (Specifically, one that uses an intermediary layer before drawing to a final layer.)

Thanks again. I really appreciate the time you took to respond. My initial tinkering with your custom blend mode haven’t worked, but I’ll keep experimenting with it.

For SDL3, if you look at the docs for SDL_SetTextureBlendMode() and SDL_BlendMode you’ll notice that there’s specifically a blend mode called SDL_BLENDMODE_BLEND_PREMULTIPLIED.

I’m not aware of any existing demos that show rendering to a layer, then rendering that layer on another layer. It may be better to just make sure your images aren’t premultiplied.

edit: found an old demo that was meant to help you choose which blend function & blend equation to use with OpenGL, but it also applies to SDL: Anders Riggelsen - Visual glBlendFunc and glBlendEquation Tool

With the Premultiply box ticked, the correct blend function is
source: GL_ONE (aka SDL_BLENDFACTOR_ONE)
destination: GL_ONE_MINUS_SRC_ALPHA (aka SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA)
equation: GL_ADD (aka SDL_BLENDOPERATION_ADD)

For un-premultiplied images it;s GL_SRC_ALPHA instead of GL_ONE.

Also, if you’re rendering a premultiplied image to an intermediary layer, and that layer doesn’t then get premultiplied by its alpha channel, you have to use a different blend mode for the intermediary layer.

And if you’re going to make changes to a premultiplied image, you first need to un-premultiply it (divide it by its alpha channel, making sure to watch out for pixels with an alpha value of 0), make your changes, then re-premultiply it.

This gets complicated, as you can imagine. It’s probably better to keep your images un-premultiplied. Then, for a game, as part of the asset bake process you can premultiply your textures if you must.

If you stick with SDL2 but use the Custom Blend Mode described, the end result will be exactly the same as it would be in SDL3. The creation of the Custom Blend Mode is one line of code in the initialisation phase of your program!

I quickly wanted to touch base and thank you all for your responses! I think that you put me on the right path, but I’ve been a bit distracted and haven’t spent much more time on this project. Once I do, I’ll report back. :slight_smile: