Color Modulation and Alpha Blending
Last Updated: Oct 13th, 2024
We're going to be modifying our texture's color with color modulation and making it transparent with alpha blending.Oh and if you don't know how RGBA color works to make pixels, I recommend reading what is a pixel or you might be a bit lost.
/* Class Prototypes */ class LTexture { public: //Symbolic constant static constexpr float kOriginalSize = -1.f; //Initializes texture variables LTexture(); //Cleans up texture variables ~LTexture(); //Loads texture from disk bool loadFromFile( std::string path ); //Cleans up texture void destroy(); //Sets color modulation void setColor( Uint8 r, Uint8 g, Uint8 b); //Sets opacity void setAlpha( Uint8 alpha ); //Sets blend mode void setBlending( SDL_BlendMode blendMode ); //Draws texture void render( float x, float y, SDL_FRect* clip = nullptr, float width = kOriginalSize, float height = kOriginalSize, double degrees = 0.0, SDL_FPoint* center = nullptr, SDL_FlipMode flipMode = SDL_FLIP_NONE ); //Gets texture dimensions int getWidth(); int getHeight(); private: //Contains texture data SDL_Texture* mTexture; //Texture dimensions int mWidth; int mHeight; };
We have the following test image:
And there's two thing we want to do with it: 1) modify its colors with color modulation and 2) make it transparent with alpha blending. We have the
The color values for the pixels on our texture are:
And there's two thing we want to do with it: 1) modify its colors with color modulation and 2) make it transparent with alpha blending. We have the
setColor
for the first part and setAlpha
/setBlending
for the second part.The color values for the pixels on our texture are:
white = red: 255 green: 255 blue: 255
red = red: 255 green: 0 blue: 0
green = red: 0 green: 255 blue: 0
blue = red: 0 green: 0 blue: 255
black = red: 0 green: 0 blue: 0
void LTexture::setColor( Uint8 r, Uint8 g, Uint8 b ) { SDL_SetTextureColorMod( mTexture, r, g, b ); }
Here we're setting the color modulation with SDL_SetTextureColorMod. What color modulation does is multiply the texture colors with the modulation color.
By default, the color modulation is white (r:255 g:255 b:255) which when multiplied gives you the original image colors:
By default, the color modulation is white (r:255 g:255 b:255) which when multiplied gives you the original image colors:
modulation red * texture red / 255 = 255 * texture red / 255 = texture red
modulation green * texture green / 255 = 255 * texture green / 255 = texture green
modulation blue * texture blue / 255 = 255 * texture blue / 255 = texture blue
modulation red * texture red / 255 = 0 * texture red / 255 = 0
modulation green * texture green / 255 = 0 * texture green / 255 = 0
modulation blue * texture blue / 255 = 255 * texture blue / 255 = texture blue
void LTexture::setAlpha( Uint8 alpha ) { SDL_SetTextureAlphaMod( mTexture, alpha ); } void LTexture::setBlending( SDL_BlendMode blendMode ) { SDL_SetTextureBlendMode( mTexture, blendMode ); }
Here we're setting the alpha value of the texture with SDL_SetTextureAlphaMod and the SDL_BlendMode with
SDL_SetTextureBlendMode.
The alpha value is the opacity of the texture so an alpha of 255 is full transparent, an alpha of 0 is completely transparent, and an alpha of 127 is roughly half transparent. Well, that is assuming you're using the
The way it works is that the alpha value controls how the rest of the colors are blended when you render an image on the screen. It uses the following equation:
So if:
The alpha value is the opacity of the texture so an alpha of 255 is full transparent, an alpha of 0 is completely transparent, and an alpha of 127 is roughly half transparent. Well, that is assuming you're using the
SDL_BLENDMODE_BLEND
style of alpha blending which is usually the type that
is used. You can look in the SDL documentation for other types of blending but this is the most common type.The way it works is that the alpha value controls how the rest of the colors are blended when you render an image on the screen. It uses the following equation:
final color = texture color * alpha / 255 + screen color * ( 255 - alpha ) / 255
.So if:
- The alpha was 255 you would get
final color = texture color * alpha / 255 + screen color * ( 255 - alpha ) / 255 = texture color * 255 / 255 + screen color * ( 255 - 255 ) / 255 = texture color
so the original texture would appear - The alpha was 0 you would get
final color = texture color * alpha / 255 + screen color * ( 255 - alpha ) / 255 = texture color * 0 / 255 + screen color * ( 255 - 0 ) / 255 = screen color
so the original texture would be invisible and you would see what's behind the image - The alpha was 127 you would get
final color = texture color * alpha / 255 + screen color * ( 255 - alpha ) / 255 = texture color * 127 / 255 + screen color * ( 255 - 127 ) / 255 = texture color
so you would we roughly a 50%/50% blend of the image and the background
//Set color constants constexpr int kColorMagnitudeCount = 3; constexpr Uint8 kColorMagnitudes[ kColorMagnitudeCount ] = { 0x00, 0x7F, 0xFF }; enum eColorChannel { eColorChannelTextureRed = 0, eColorChannelTextureGreen = 1, eColorChannelTextureBlue = 2, eColorChannelTextureAlpha = 3, eColorChannelBackgroundRed = 4, eColorChannelBackgroundGreen = 5, eColorChannelBackgroundBlue = 6, kColorChannelTotal = 7, kColorChannelUnknown = 8 };
Before we go into a main loop we want to define some contants. For this demo, we'll be setting r, g, b, or a values to a magnitude of 255, 127, or 0.
We'll be able to update the texture's r, g, b, or a channels and the background's r, g, or b channels. We also have some constants to define the channel count and also an extra channel constant that will function like an error value.
We'll be able to update the texture's r, g, b, or a channels and the background's r, g, or b channels. We also have some constants to define the channel count and also an extra channel constant that will function like an error value.
//Initialize colors Uint8 colorChannelsIndices[ kColorChannelTotal ]; colorChannelsIndices[ eColorChannelTextureRed ] = 2; colorChannelsIndices[ eColorChannelTextureGreen ] = 2; colorChannelsIndices[ eColorChannelTextureBlue ] = 2; colorChannelsIndices[ eColorChannelTextureAlpha ] = 2; colorChannelsIndices[ eColorChannelBackgroundRed ] = 2; colorChannelsIndices[ eColorChannelBackgroundGreen ] = 2; colorChannelsIndices[ eColorChannelBackgroundBlue ] = 2; //Initialize blending gColorsTexture.setBlending( SDL_BLENDMODE_BLEND );
Before we enter the main loop, we want to set all of the color channels to the index that has 255 and enable blending on the texture.
//Check for channel key eColorChannel channelToUpdate = kColorChannelUnknown; switch( e.key.key ) { //Update texture color case SDLK_A: channelToUpdate = eColorChannelTextureRed; break; case SDLK_S: channelToUpdate = eColorChannelTextureGreen; break; case SDLK_D: channelToUpdate = eColorChannelTextureBlue; break; case SDLK_F: channelToUpdate = eColorChannelTextureAlpha; break; //Update background color case SDLK_Q: channelToUpdate = eColorChannelBackgroundRed; break; case SDLK_W: channelToUpdate = eColorChannelBackgroundGreen; break; case SDLK_E: channelToUpdate = eColorChannelBackgroundBlue; break; }
For this demo we will be updating the textures r/g/b/a values with the a/s/d/f keys and the background color with the q/w/e keys.
//If channel key was pressed if( channelToUpdate != kColorChannelUnknown ) { //Cycle through channel values colorChannelsIndices[ channelToUpdate ]++; if( colorChannelsIndices[ channelToUpdate ] >= kColorMagnitudeCount ) { colorChannelsIndices[ channelToUpdate ] = 0; } //Write color values to console SDL_Log("Texture - R:%d G:%d B:%d A:%d | Background - R:%d G:%d B:%d", kColorMagnitudes[ colorChannelsIndices[ eColorChannelTextureRed ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelTextureGreen ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelTextureBlue ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelTextureAlpha ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelBackgroundRed ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelBackgroundGreen ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelBackgroundBlue ] ] ); }
When we press a channel key, we go to the next index the magnitude array for that channel. If we go past the last one, we go back to the first value. Every time we update the channels, we print the values to the console so they're easier to keep track of.
//Fill the background SDL_SetRenderDrawColor( gRenderer, kColorMagnitudes[ colorChannelsIndices[ eColorChannelBackgroundRed ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelBackgroundGreen ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelBackgroundBlue ] ], 0xFF ); SDL_RenderClear( gRenderer ); //Set texture color and render gColorsTexture.setColor( kColorMagnitudes[ colorChannelsIndices[ eColorChannelTextureRed ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelTextureGreen ] ], kColorMagnitudes[ colorChannelsIndices[ eColorChannelTextureBlue ] ] ); gColorsTexture.setAlpha( kColorMagnitudes[ colorChannelsIndices[ eColorChannelTextureAlpha ] ]); gColorsTexture.render( ( kScreenWidth - gColorsTexture.getWidth() ) / 2.f, ( kScreenHeight - gColorsTexture.getHeight() ) / 2.f ); //Update screen SDL_RenderPresent( gRenderer ); }
Finally, we set the background/texture colors and draw them out.