Tutorial 07: Working with Colors
What you’ll learn
- Constructing
Colorfrom RGBA components and from the predefined constants. - Colour arithmetic and
Lerpfor fades and tints. - How the alpha channel interacts with
SpriteBatchdrawing.
Before you start — Tutorial 06: Drawing Your First 2D Shape — every example here tints a sprite drawn through SpriteBatch.
The Color Struct
Color is a 32-bit RGBA value stored as four unsigned bytes: R (red), G (green), B (blue), A (alpha). It lives in the Microsoft::Xna::Framework namespace and is included via:
#include "Microsoft/Xna/Framework/Color.hpp"
using namespace Microsoft::Xna::Framework;
Each channel ranges from 0 (none) to 255 (full). Alpha 255 means fully opaque; alpha 0 means fully transparent.
You can read individual channels:
Color c(255, 128, 0, 200);
bytecs r = c.getRProperty(); // 255
bytecs g = c.getGProperty(); // 128
bytecs b = c.getBProperty(); // 0
bytecs a = c.getAProperty(); // 200
bytecs is the sharp-runtime equivalent of C# byte (unsigned 8-bit integer). It is implicitly convertible to and from unsigned char.
You can also access a packed 32-bit integer representation:
uint32_t packed = c.getPackedValueProperty(); // ABGR packed
Predefined Colors
CNA provides all 141 named colors from the XNA 4.0 API as static constants on the Color class. A selection:
| Name | R | G | B | Use for |
|---|---|---|---|---|
Color::CornflowerBlue | 100 | 149 | 237 | Default XNA clear color |
Color::White | 255 | 255 | 255 | Neutral tint (no tinting) |
Color::Black | 0 | 0 | 0 | Dark backgrounds |
Color::Red | 255 | 0 | 0 | Enemies, health |
Color::Green | 0 | 128 | 0 | Success, health bar |
Color::Blue | 0 | 0 | 255 | Water, mana |
Color::Yellow | 255 | 255 | 0 | Score, coins |
Color::Orange | 255 | 165 | 0 | Fire, warmth |
Color::Purple | 128 | 0 | 128 | Magic, poison |
Color::Transparent | 0 | 0 | 0 | Fully transparent (A=0) |
// Use predefined colors directly
device.Clear(Color::CornflowerBlue);
spriteBatch_->Draw(*pixel_, Rectangle(0,0,100,100), Color::Red);
spriteBatch_->Draw(*pixel_, Rectangle(0,0,100,100), Color::Yellow);
RGBA Construction
Color has several constructors:
// From R, G, B (alpha defaults to 255 — fully opaque)
Color orange(255, 128, 0);
// From R, G, B, A
Color semiBlue(0, 0, 255, 128); // 50% transparent blue
// From normalized floats [0.0f, 1.0f] — note the f suffix
Color warm(1.0f, 0.5f, 0.0f, 1.0f); // (255,128,0,255): 127.5 rounds to the even neighbour
// Copy the RGB of another colour with a new alpha (straight alpha,
// the same RGB bytes; see "Transparency and Alpha" below)
Color c = Color::Red;
Color redA128 = Color(c.getRProperty(), c.getGProperty(), c.getBProperty(),
static_cast<bytecs>(128));
The float constructors saturate out-of-range values and round to the nearest integer, with exact ties going to the even neighbour (NaN becomes 0). That is XNA 4.0’s own behaviour, measured against the real runtime. The alpha.1 release truncated instead, so the 0.5f above gave 127 there and gives 128 in this snapshot. Color::Lerp and Color::Multiply, on the other hand, still truncate, exactly as XNA does.
The float constructor is useful when you compute colors algorithmically, for example when using sine waves for pulsing effects:
float t = static_cast<float>(gameTime.getTotalGameTimeProperty().getTotalSecondsProperty());
float pulse = (std::sin(t * 3.0f) + 1.0f) * 0.5f; // 0..1
Color glowColor(pulse, 0.2f, 1.0f - pulse, 1.0f); // blue-to-red glow
Color Arithmetic
Like XNA’s, CNA’s Color deliberately has a small arithmetic surface: Color::Multiply(color, scale) and the equivalent operator* (color * scale, plus scale * color as a CNA extension), Color::Lerp, and conversion to Vector3/Vector4 with ToVector3()/ToVector4(). There is no Color::Add or Color::Subtract.
// Scale ALL FOUR channels (R, G, B and A) by a float; results are clamped
// to [0,255] and truncated. Multiplying by less than 1 darkens AND fades.
Color tinted = Color::Multiply(Color::Red, 0.5f); // (127, 0, 0, 127)
Color same = Color::Red * 0.5f; // identical, operator form
// Adding two colors: do the arithmetic in Vector4 (0..1 floats), then
// convert back. The Color(Vector4) constructor saturates at 1.0 for you.
Vector4 sum = Color::Red.ToVector4() + Color(50, 50, 0, 0).ToVector4();
Color bright(sum); // (255, 50, 0, 255)
Because Multiply scales alpha too, it is not a pure brightness control:
// Alpha is scaled as well: this is CornflowerBlue at 60% brightness AND 60% opacity
Color sixty = Color::Multiply(Color::CornflowerBlue, 0.6f); // (60, 89, 142, 153)
// To darken only RGB and keep the colour opaque, rebuild the colour:
Color darker(static_cast<int>(Color::CornflowerBlue.getRProperty() * 0.6f),
static_cast<int>(Color::CornflowerBlue.getGProperty() * 0.6f),
static_cast<int>(Color::CornflowerBlue.getBProperty() * 0.6f));
The scaling behaviour is the whole point under BlendState::AlphaBlend, the default: it expects premultiplied colours, and colour * opacity produces exactly that. See the next sections.
Lerping Between Colors
Linear interpolation between two colors creates smooth transitions — fade-outs, damage flashes, day/night cycles.
// Color::Lerp(from, to, amount)
// amount = 0.0 returns 'from', amount = 1.0 returns 'to'
Color dawn = Color::CornflowerBlue;
Color dusk = Color(30, 10, 60, 255); // deep purple
// In Update():
float t = (std::sin(totalTime * 0.5f) + 1.0f) * 0.5f; // 0..1
currentSkyColor_ = Color::Lerp(dawn, dusk, t);
// In Draw():
device.Clear(currentSkyColor_);
Lerp is also useful for health bar color transitions:
// Red at 0% health, green at 100%
float healthFraction = playerHealth_ / maxHealth_;
Color barColor = Color::Lerp(Color::Red, Color::Green, healthFraction);
Transparency and Alpha
The alpha channel controls how transparent a color is. Alpha = 255 is fully opaque; alpha = 0 is fully invisible. Values in between give partial transparency.
Alpha blending in SpriteBatch is enabled by default using BlendState::AlphaBlend. There is one catch that trips up almost everyone coming to XNA: AlphaBlend is a premultiplied-alpha blend (result = src + dst * (1 - src.a)). It expects the RGB you draw with to be already multiplied by alpha. So the way to draw something at 40% opacity is to scale all four channels together, which is what colour * 0.4f does:
// A tooltip background with semi-transparent dark panel.
// Black is the same premultiplied or not, so this one is fine as written:
Color tooltipBg(0, 0, 0, 180); // 70% opaque black
spriteBatch_->Draw(*pixel_, tooltipRect, tooltipBg);
// Fade text: scale the whole colour (RGB and A) by the opacity
float fadeAlpha = 1.0f - elapsedFadeTime_ / fadeOutDuration_;
Color textColor = Color::White * fadeAlpha;
A colour such as Color(255, 255, 255, 100) keeps full-strength RGB with alpha 100. Under AlphaBlend that draws (nearly) solid white rather than a translucent one, because the blend adds the full-strength colour on top of the darkened background. If you have a colour with straight (non-premultiplied) alpha, convert it with Color::FromNonPremultiplied, or draw with BlendState::NonPremultiplied instead (Tutorial 22).
To create a color that is a copy of another with a different opacity:
Color original = Color::CornflowerBlue;
// Premultiplied result for AlphaBlend: same hue at 50% opacity
Color withAlpha = Color::FromNonPremultiplied(original.getRProperty(),
original.getGProperty(),
original.getBProperty(), 128);
// Equivalent: scale the opaque colour
Color halfOpaque = original * 0.5f;
Using Color with SpriteBatch
Every SpriteBatch::Draw and DrawString call accepts a color parameter. This color is a tint: the texture pixels are multiplied by it channel by channel.
Color::White— no tint, draw the texture as-is.- Any other color — tints the texture. A red texture tinted with blue will become very dark.
- Use the tint alpha to fade the entire sprite in or out.
// No tint — draw texture in original colors
spriteBatch_->Draw(*playerTex_, position_, Color::White);
// Damage flash: tint red
if (isHurt_) {
spriteBatch_->Draw(*playerTex_, position_, Color::Red);
} else {
spriteBatch_->Draw(*playerTex_, position_, Color::White);
}
// Ghost effect: semi-transparent (40% of white = premultiplied 40% opacity)
Color ghost = Color::White * 0.4f;
spriteBatch_->Draw(*playerTex_, ghostPosition_, ghost);
// Fade-in at game start
float alpha = std::min(1.0f, elapsedTime_ / fadeInDuration_);
Color fadeColor = Color::White * alpha;
spriteBatch_->Draw(*titleTex_, titlePos_, fadeColor);
These examples assume your textures are premultiplied, which is the case for textures that went through the XNA content pipeline or CNA’s cna-content tool with default settings. A plain .png loaded straight from disk is not premultiplied, so draw it with BlendState::NonPremultiplied (see Tutorial 22).
Colors are one of the most versatile tools in 2D game development. In Tutorial 08 you will load real image files and draw them with full control over position, scale, rotation, and color.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Rectangle, Point and Color: integer geometry and packed colour semantics — Rectangle's half-open edges, touching and empty rules, Point's missing Vector2 bridge and rounding, Color's AABBGGRR word, the 141/140/139 named-colour counts, construction rounding and premultiplied alpha.