SDL Surfaces & Colors

Explore SDL surfaces, the canvases for drawing, understand pixel formats, colors, and set your window's background.
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Posted

In this lesson, we'll take our first steps into drawing graphics with SDL. We'll learn about SDL Surfaces, the canvases we draw onto, understand how colors and pixel formats work, and use this knowledge to change the background color of our window.

Starting Point

In this lesson, we’ll continue working on our main.cpp and Window.h files from the previous lesson. We’ve provided them below.

#include <SDL.h>
#include "Window.h"

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
    
  while(true) {
    SDL_PumpEvents();
  }
  
  SDL_Quit();
  return 0;
}
// Window.h
#pragma once
#include <SDL.h>

class Window {
public:
  Window(){
    SDLWindow = SDL_CreateWindow(
      "Hello Window",
      SDL_WINDOWPOS_UNDEFINED,
      SDL_WINDOWPOS_UNDEFINED,
      800, 300, 0
    );
  }
  
  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;
  
  ~Window() {
    if (SDLWindow && SDL_WasInit(SDL_INIT_VIDEO)) {
      SDL_DestroyWindow(SDLWindow);
    }
  }
  
private:
  SDL_Window* SDLWindow{nullptr};
};

Surfaces

When we write graphical programs, we're essentially telling the computer how to color in a grid of dots on the screen. These dots are pixels, and a complete grid forms an image.

Showing the pixels of an image

By showing many images in quick succession, we create animations and videos.

SDL uses the SDL_Surface type to manage these 2D grids of pixel colors. Think of an SDL_Surface as a digital canvas containing all the color information for an image, plus details like its width, height, and the specific way colors are encoded.

Window Surfaces

An SDL_Window isn't just an empty frame; it comes equipped with its own SDL_Surface that acts as its drawing board. This is the surface we'll manipulate to change what's displayed inside the window.

The function SDL_GetWindowSurface() gives us access to this surface. It takes the SDL_Window* as an argument and returns an SDL_Surface*.

Let's add a GetSurface() method to our Window class to simplify getting this pointer when we need it:

// Window.h
#pragma once
#include <SDL.h>

class Window {
public:
  Window(){
    SDLWindow = SDL_CreateWindow(
      "Hello Window",
      SDL_WINDOWPOS_UNDEFINED,
      SDL_WINDOWPOS_UNDEFINED,
      800, 300, 0
    );
  }

  SDL_Surface* GetSurface() {
    return SDL_GetWindowSurface(SDLWindow);
  }
  
  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;
  
  ~Window() {
    SDL_DestroyWindow(SDLWindow);
  }
  
private:
  SDL_Window* SDLWindow{nullptr};
};

Pointer Invalidation

Behind the scenes, SDL can destroy the surface associated with a window at any time. The main scenario where this happens is when the window is resized. The existing surface is deleted, and a new surface with the new size is created.

The implication is that, if we were storing a pointer to that surface, we now have a dangling pointer. In almost all cases, we shouldn’t be storing such a pointer. Any time we need to get a pointer to a window surface, we should use SDL_GetWindowSurface() to ensure we’re getting the latest memory address.

Colors

To define a specific color in a digital format, we usually break it down into its core components. For display devices, the standard approach is to use the RGB model, which stands for Red, Green, and Blue. Each color we see on screen is a combination of these three primary light colors.

Each component (R, G, and B) can have a range of possible values, representing its intensity. By specifying a value for red, a value for green, and a value for blue, we precisely define a single color out of millions of possibilities.

Showing a color comprised of red, green, and blue values

Pixel Formats

Since computers store everything as numbers (ultimately sequences of binary bits), we need a standardized way to translate these numbers into the RGB color intensities we just discussed. This is where pixel formats come in.

A pixel format defines exactly how the color data for a single pixel is stored in memory - that is, how a color is converted to a sequence of bits, and how a sequence of bits is converted back to a color.

Example Pixel Formats

To understand how to convert a binary sequence to a color, and a color back to a binary sequence, a pixel format typically requires three main pieces of information:

  • Which channels are in the format
  • How many bits are assigned to each channel
  • What order the channels are in

Pixel formats are typically given names, and a common example is RGB888. All of the information we listed above is included in this short name. RGB888 has red (R), green (G), and blue (B) channels, in that order, and each channel is assigned to 8 bits.

8 bits can store one of 256 possible values, so RGB888 can have one of 256 different intensities for each of it’s red, green, and blue channels. Combined, this means an RGB888 pixel can be one of approximately 16.8 million colors (256×256×256256 \times 256 \times 256)

Even though a pixel in this format only needs 24 bits (8 + 8 + 8), for technical reasons related to memory alignment, we include 8 bits of unused space. As such, an RGB888 color (and generally any color requiring 24 bits) is stored in a 32 bit (4 byte) container:

Showing a the memory layout of an RGB888 pixel

We cover memory alignment and padding in a dedicated lesson later in the course.

Using SDL_MapRGB()

To create a color in a specific format, SDL provides the SDL_MapRGB() function to help us. It accepts 4 arguments:

  1. The format we want to use
  2. The intensity of the red channel (from 0 to 255)
  3. The intensity of the green channel (from 0 to 255)
  4. The intensity of the blue channel (from 0 to 255)

In almost all scenarios, the format we want to use will be based on the format of the thing we’re drawing to, such as an SDL_Surface. The format of an SDL_Surface is available through the format member variable.

So, for example, if we wanted to generate a red color to use on the window surface, we’d do this:

// Window.h
// ...

class Window {
public:
  Window(){
SDLWindow = SDL_CreateWindow(/*...*/); // Get the window surface's pixel format SDL_PixelFormat* PixelFormat{ GetSurface()->format}; // Create a bright red color value // suitable for this surface Uint32 RedColor{SDL_MapRGB( PixelFormat, 255, // Max intensity for Red 0, // Zero intensity for Green 0 // Zero intensity for Blue )}; } // ... };

Storing Colors as Unsigned Integers

When we have a small amount of arbitrary binary data, such as a color, it’s fairly common to use a fixed-width unsigned integer to store that data. For examaple, SDL_MapRGB() returns its color as a Uint32:

SDL_PixelFormat* Fmt{surface->format}; 
Uint32 Red{SDL_MapRGB(Fmt, 255, 0, 0)}; 
Uint32 Green{SDL_MapRGB(Fmt, 0, 255, 0)}; 
Uint32 Blue{SDL_MapRGB(Fmt, 0, 0, 255)}; 
Uint32 Yellow{SDL_MapRGB(Fmt, 255, 255, 0)};

Even though this type suggests it’s an integer, we never use it as one. We don’t care what its total numeric value is, and we don’t perform any arithmetic on it. A fixed-width unsigned integer is just a convenient way to allocate a small block of memory of known size - 32 bits, in the case of Uint32.

Transparency / Alpha

Pixels can also include an alpha channel, representing how opaque the color is. A common pixel format that includes alpha is RGBA8888. It is similar to RGB888, except the 8 bits of previously-unused space now contains transparency data:

Showing a the memory layout of an RGBA8888 pixel

We’ll work with alpha more later in the course when we start loading semi-transparent PNGs into programs. The SDL_MapRGBA() allows us to create colors that includes an alpha channel. It takes an additional argument, representing how opaque the color should be from 0 to 255. Below, we create a mostly-opaque red color:

Uint32 Color{SDL_MapRGBA(
  GetSurface()->format,
  255, // Red channel
  0,   // Green Channel
  0,   // Blue Channel
  200  // Alpha Channel
)};

If the pixel format we provide as the first argument doesn’t support alpha, our argument will effectively be ignored and our color will be fully opaque. This will generally be the case with window surface formats, as they rarely support alpha.

We can check if a format supports alpha by converting it’s Amask member to a boolean:

if (
  SDL_GetWindowSurface(SDLWindow)->format->Amask
) {
  std::cout << "Alpha is Supported";
} else {
  std::cout << "No Alpha Support";
}
No Alpha Support

Using SDL_FillRect()

Let’s use all of this to set the background color of our window. To full a rectangular area of a surface with a solid color, we use the SDL_FillRect() function. It accepts three arguments:

  1. A pointer to the surface we want to fill
  2. A pointer to the rectangle representing which area of the surface we want to fill. We’ll cover rectangles in the next chapter but, for now, we’ll pass a nullptr here. This indicates to SDL we want to fill the entire surface with our color
  3. The color we want to use

Let’s add this to our Window constructor to make it’s background dark gray:

// Window.h
// ...

class Window {
public:
  Window(){
SDLWindow = SDL_CreateWindow(/*...*/); SDL_FillRect( GetSurface(), nullptr, SDL_MapRGB( GetSurface()->format, 50, 50, 50 ) ); } // ... };

To see our changes, we need to call SDL_UpdateWindowSurface(), passing a pointer to our SDL_Window*. We’ll cover this function in more detail at the end of this chapter. For now, let’s just add it to our constructor, after the call to SDL_FillRect():

// Window.h
// ...

class Window {
public:
  Window(){
SDLWindow = SDL_CreateWindow(/*...*/);
SDL_FillRect(/*...*/); SDL_UpdateWindowSurface(SDLWindow); } // ... };

After these changes, we should now see our window adopt the requested background color:

Screenshot of a window with a dark background

Complete Code

After the changes of this lesson, our completed files look like the following. We’ll continue to build on these files in the next lesson.

#include <SDL.h>
#include "Window.h"

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  Window GameWindow;
    
  while(true) {
    SDL_PumpEvents();
  }
  
  SDL_Quit();
  return 0;
}
// Window.h
#pragma once
#include <SDL.h>

class Window {
public:
  Window(){
    SDLWindow = SDL_CreateWindow(
      "Hello Window",
      SDL_WINDOWPOS_UNDEFINED,
      SDL_WINDOWPOS_UNDEFINED,
      800, 300, 0
    );

    SDL_FillRect(
      GetSurface(),
      nullptr,
      SDL_MapRGB(
        GetSurface()->format, 50, 50, 50
      )
    );

    SDL_UpdateWindowSurface(SDLWindow);
  }

  SDL_Surface* GetSurface() {
    return SDL_GetWindowSurface(SDLWindow);
  }
  
  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;
  
  ~Window() {
    if (SDLWindow && SDL_WasInit(SDL_INIT_VIDEO)) {
      SDL_DestroyWindow(SDLWindow);
    }
  }
  
private:
  SDL_Window* SDLWindow{nullptr};
};

Summary

In this lesson, we learned how SDL represents drawable areas using SDL_Surface. We saw how to access the specific surface associated with our window using SDL_GetWindowSurface().

We also explored how colors are defined using RGB components and how pixel formats dictate their storage in memory. Finally, we used SDL_MapRGB() to create a color value and SDL_FillRect() to change our window's background, making sure to update the display with SDL_UpdateWindowSurface().

Key Takeaways:

  • Computer graphics involve drawing sequences of images made of pixels.
  • SDL_Surface represents a 2D grid of pixels in SDL.
  • Windows have an associated surface, obtainable via SDL_GetWindowSurface().
  • Colors are often represented by Red, Green, and Blue (RGB) channels.
  • Pixel formats define how color data is stored (channels, order, bits per channel).
  • SDL_MapRGB() converts RGB values into a single integer value suitable for a specific pixel format.
  • SDL_FillRect() can fill a surface area with a solid color.
  • SDL_UpdateWindowSurface() makes changes to the window surface visible.
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Ryan McCombe
Ryan McCombe
Posted
Lesson Contents

SDL Surfaces & Colors

Explore SDL surfaces, the canvases for drawing, understand pixel formats, colors, and set your window's background.

sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Creating an SDL Application
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

This course includes:

  • 108 Lessons
  • 92% Positive Reviews
  • Regularly Updated
  • Help and FAQs
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2025 - All Rights Reserved