Lazy Foo' Productions

SDL Forums external link SDL Tutorials 🐰SDL3 Tutorials🥚 Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Color Keying

Color Keying screenshot

Last Updated: Oct 13th, 2024

In this tutorial we'll be giving our character texture a transparent background.
Say if we have a character image like this:
foo

And we want to draw it onto a background like this:
background

But we want don't want the light blue color (known as cyan) to show up like this:
no color key

We can make cyan color transparent using color keying.
bool LTexture::loadFromFile( std::string path )
{
    //Clean up texture if it already exists
    destroy();

    //Load surface
    if( SDL_Surface* loadedSurface = IMG_Load( path.c_str() ); loadedSurface == nullptr )
    {
        SDL_Log( "Unable to load image %s! SDL_image error: %s\n", path.c_str(), SDL_GetError() );
    }
    else
    {
        //Color key image
        if( !SDL_SetSurfaceColorKey( loadedSurface, true, SDL_MapSurfaceRGB( loadedSurface, 0x00, 0xFF, 0xFF ) ) )
        {
            SDL_Log( "Unable to color key! SDL error: %s", SDL_GetError() );
        }
        else
        {
            //Create texture from surface
            if( mTexture = SDL_CreateTextureFromSurface( gRenderer, loadedSurface ); mTexture == nullptr )
            {
                SDL_Log( "Unable to create texture from loaded pixels! SDL error: %s\n", SDL_GetError() );
            }
            else
            {
                //Get image dimensions
                mWidth = loadedSurface->w;
                mHeight = loadedSurface->h;
            }
        }
        
        //Clean up loaded surface
        SDL_DestroySurface( loadedSurface );
    }

    //Return success if texture loaded
    return mTexture != nullptr;
}
All we have to do to color key a texture is to call SDL_SetSurfaceColorKey on the SDL_Surface before creating the texture from it. Here we're setting all pixels that are red 0, green 0xFF, and blue 0xFF (which is how you get cyan) to transparent. Otherwise, texture loading works the same.
bool loadMedia()
{
    //File loading flag
    bool success{ true };

    //Load scene images
    if( success &= gFooTexture.loadFromFile( "04-color-keying/foo.png" ); !success )
    {
        SDL_Log( "Unable to load foo image!\n");
    }
    if( success &= gBgTexture.loadFromFile( "04-color-keying/background.png" ); !success )
    {
        SDL_Log( "Unable to load background image!\n");
    }

    return success;
}
Here we're loading our scene textures which are doing the color keying under the hood.
                //Fill the background white
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
                SDL_RenderClear( gRenderer );
            
                //Render images on screen
                gBgTexture.render( 0.f, 0.f );
                gFooTexture.render( 240.f, 190.f );
                
                //Update screen
                SDL_RenderPresent( gRenderer );
Here we draw the images on the screen.

If you have been messing around with drawing positions in SDL, you may be expecting something drawn at x=0, y=0 to be at the bottom left corner (or maybe even the center of the screen) because you expect it to use the Cartesian coordinate system like so:
cartesian coordinates

SDL (and a lot of other 2D rendering APIs) use the top left as the origin and have the Y axis point downward:
sdl coordinates

So when we positon the Foo sprite, this is how the x and y coordinates work:
positioning foo

Having to deal with different coordinate systems is something you're going to have to get used to. In physics we usually use the right hand rule with Y axis pointing up, but game engines and 3D modelling programs can use different conventions for which axis is up/down/left/right/forward/backward.

Addendum: Asset managers

So far in these tutorials we have been manually loading and unloading our textures and keeping track of when we need them. In most game engines, they use some sort of asset manager because you can only imagine how many textures, models, sounds, etc are loaded in a real video game and having to take keep track of them manually would be unfeasible.

The way asset managers would work at a basic level is when something (like a scene or game state) needs an asset like a texture, it would load it. If something else also needs it, the asset manager would check if it's already loaded and if it is instead of loading it again it would keep track of everything that's using it. Once nothing is using the asset anymore they would delete it or mark it for deletion so the user can delete it at a specified time.

If you're making an engine for a platformer with data driven level loading it would be a good idea to implement a level manager. However, for something like a nasty tetris project, an asset manager would be overkill. Again, a game engine should be as simple as possible.