[Guide] Convert Byte Array to SDL_Surface

You start out with a byte array of some size (e.g., 3 * width * height)

unsigned char pixel_buffer[kPixelBufferSize];

Here, I like to use an unnamed enum to compute kPixelBufferSize and define the pixel buffer properties.

enum {
  kPixelSize = 3,
  kWidth = 320,
  kHeight = 240,
  kResolution = kWidth * kHeight,
  kPixelBufferSize = kPixelSize * kResolution
};

Now we have SDL_Surface that contains an array of pixels or Uint32s.

Let’s remember that an Uint32 takes up 4 bytes of memory.
We can write that in hex

0xAABBCCDD

Computer’s usually address memory byte-wise.
Reading from left-to-right, we have bytes AA, BB, CC and DD.

Some computers (Intel) like to store the right-most byte first to the lowest address.
So DD at address 0, then CC at address 1, then BB at address 2, then AA at address 3.

0: DD
1: CC
2: BB
3: AA

Some computers (Motorola 6800) like to store the left-most byte first to the lowest address.
So

0: AA
1: BB
2: CC
3: DD

With each byte, we can store a color intensity (R, G, B or A) ranging from 0 to 255.

RGBA

In C, we can access the bytes of a 4-byte integer via

  Uint32 pixel = 0;
  Uint8* p = (Uint8*)&pixel;
  p[0] = 255; // Red, assume address 0
  p[1] = 0; // Green, assume address 1
  p[2] = 0; // Blue, assume address 2
  p[3] = 255; // Alpha, assume address 3

Note: I chose addresses 0 through 3 out of convenience.

At index 0, address 0, we have color intensity 255 (Red) and at index 3, address 3, we store the alpha intensity 255.
This will work fine on a computer which likes to store the left-most byte first (i.e., R from RGBA) to the lowest address.

Again, Intel stores the right-most byte first.
So let’s see how Intel would access the color intensities from above:

  Uint32 pixel = 0;
  Uint8* p = (Uint8*)&pixel;
  p[0] = 255; // Alpha, assume address 0
  p[1] = 0; // Blue, assume address 1
  p[2] = 0; // Green, assume address 2
  p[3] = 255; // Red, assume address 3

Here we see that Intel puts our red color intensity at address 3.

Since SDL_Surface encodes colors within a 4-byte Uint32, we need to make sure that Intel and Motorola 6800 stores each color intensity at the right place.

One way to solve this, would be to detect whether we are on an Intel or Motorola 6800 computer.
If on an Intel machine, we store our alpha value at index 0, address 0, and our red value at index 3, address 3.
We could also use bit shifting to place the color intensities at the correct positions:

  Uint32 pixel = 0;
  // We detected an Intel computer, so we store the alpha value first (to the lowest address).
    Uint32 red = 255;
    Uint32 green = 0 << 8;
    Uint32 blue = 0 << 16;
    Uint32 alpha = 255 << 24;
    pixel =  red | green | blue | alpha;

By the way, to detect whether we are on Intel or a Motorola computer, we can use a Uint16, store the value 0xAABB and see if value AA is at index 0 (Intel) or 1 (Motorola).
However, SDL offers us a handy Macro utility, so that we can use the correct format:

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define RED_MASK 0xff000000
#define GREEN_MASK 0x00ff0000
#define BLUE_MASK 0x0000ff00
#define ALPHA_MASK 0x000000ff
#else
#define RED_MASK 0x000000ff
#define GREEN_MASK 0x0000ff00
#define BLUE_MASK 0x00ff0000
#define ALPHA_MASK 0xff000000
#endif

Okay, back to SDL_Surface.
Again, our goal is to store an RGB byte array into an SDL_Surface where each pixel is not Uint8, but Uint32.

unsigned char pixel_buffer[kPixelBufferSize];
/* For some reason, we need to lock and unlock before each surface modification. */
SDL_LockSurface(sdl_resources->rgb_surface);

  /* Copy pixels from the source buffer (pixel_buffer) into SDL's RGBA surface. */
  for (y = 0; y < kHeight; ++y) {
    for (x = 0; x < kWidth; ++x) {
      /* Compute surface's (x, y) position. */
      Uint32* pixel = NULL;
      int const kSurfaceBytesPerPixel = surface->format->BytesPerPixel;
      /* 'Pitch' just means row size, so kSurfaceBytesPerPixel * kSurfaceWidth. */
      int const kSurfaceRowSize = surface->pitch;
      int const kSurfaceOffset = x * kBytesPerPixel + y * kRowSize;
      Uint8* const kPixels = (Uint8*)sdl_resources->rgb_surface->pixels;
      SDL_PixelFormat const* kPixelFormat = sdl_resources->rgb_surface->format;

      /* Compute the pixel buffer's (x, y) position. */
      int const kPixelBufferOffset = kSourceColorChannels * (x + kWidth * y);
      unsigned char const kRed = rgb_buffer[kPixelBufferOffset];
      unsigned char const kGreen = rgb_buffer[kPixelBufferOffset + 1];
      unsigned char const kBlue = rgb_buffer[kPixelBufferOffset + 2];

      /* Let's use SDL's utility macro `SDL_MapRGBA` to convert our RGB intensity values to an machine-independent Uint32 value. */
      Uint32 const kColor =
          SDL_MapRGBA(kPixelFormat, kRed, kGreen, kBlue, kAlpha);

      /* Finally, store `kColor` (the Uint32 value) inside SDL's surface. */
      pixel = (Uint32*)(kPixels + kOffset);
      *pixel = kColor;
    }
  }

  SDL_UnlockSurface(sdl_resources->rgb_surface);

That’s it.

Don’t forget to create an SDL_Surface before

    int const kRgbaDepth = 32;
    SDL_Surface* rgb_surface = NULL;
    rgb_surface = SDL_CreateRGBSurface(0, kWidth, kHeight, kRgbaDepth, RED_MASK,
                                       GREEN_MASK, BLUE_MASK, ALPHA_MASK);
    /* We disable alpha blending. */
    SDL_SetSurfaceBlendMode(rgb_surface, SDL_BLENDMODE_NONE);

Then copy (or blit) the source surface (i.e., rgb_surface) to the destination surface (i.e., window_surface).

  SDL_BlitScaled(rgb_surface, NULL,
                 window_surface, NULL);
  SDL_UpdateWindowSurface(sdl_resources->window);

Hint: you can get the window’s surface via

    SDL_Window* window =
        SDL_CreateWindow(kTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                         kWindowWidth, kWindowHeight, 0);

    SDL_Surface* window_surface = SDL_GetWindowSurface(window);
    /* Here, we also disable alpha blending. */
    SDL_SetSurfaceBlendMode(window_surface, SDL_BLENDMODE_NONE);

Summary:

So we converted our RGB byte array to SDL’s RGBA Uint32 array.
Both are just arrays representing pixels with different integer types (i.e., Uint8 and Uint32).
We saw that we need to respect the byte order for different computer types (i.e., Intel or Motorola), if we want to encode our RGBA color in an Uint32.

We only need to respect the byte order with multibyte types such as Uint16 and Uint32.
However, we don’t need to respect the byte order with an Uint8 array at all.