Cropping and Positioning Images

Learn to precisely control image display using source and destination rectangles.
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

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

In this lesson, we'll dive into image positioning and cropping techniques. Here's what we'll cover:

  • Source and Destination Rectangles: Learn how to control which parts of an image are rendered and where they appear on screen.
  • Cropping: Discover how to display only specific portions of an image.
  • Positioning: Learn how to place images precisely where we want them on our destination surface.
  • Clipping: Understand what happens when images don't fit entirely on screen and how to detect it.

As a starting point, we’ll be building upon the basic application loop and surface-blitting concepts we covered in previous lessons:

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

class Window {
public:
  Window() {
    SDLWindow = SDL_CreateWindow(
      "My Program", SDL_WINDOWPOS_UNDEFINED,
      SDL_WINDOWPOS_UNDEFINED, 600, 300, 0);
  }

  void Render() {
    SDL_FillRect(
      GetSurface(), nullptr, SDL_MapRGB(
        GetSurface()->format, 50, 50, 50
      )
    );
  }

  void Update() {
    SDL_UpdateWindowSurface(SDLWindow);
  }

  SDL_Surface* GetSurface() {
    return SDL_GetWindowSurface(SDLWindow);
  }

  ~Window() { SDL_DestroyWindow(SDLWindow); }
  
  Window(const Window&) = delete;
  Window& operator=(const Window&) = delete;

private:
  SDL_Window* SDLWindow;
};

int main(int argc, char** argv) {
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Event Event;
  bool shouldQuit{false};

  Window GameWindow;
  Image ExampleImg{"example.bmp"};

  while (!shouldQuit) {
    while (SDL_PollEvent(&Event)) {
      if (Event.type == SDL_QUIT) {
        shouldQuit = true;
      }
    }
    GameWindow.Render();
    ExampleImg.Render(GameWindow.GetSurface());
    GameWindow.Update();
  }

  SDL_Quit();
  return 0;
}
#pragma once
#include <iostream>
#include <SDL.h>
#include <string>

class Image {
public:
  Image(std::string File)
  : ImageSurface{SDL_LoadBMP(File.c_str())} {
    if (!ImageSurface) {
      std::cout << "Failed to load image: "
        << File << ":\n" << SDL_GetError();
    }
  }

  void Render(SDL_Surface* DestinationSurface) {
    SDL_BlitSurface(
      ImageSurface, nullptr,
      DestinationSurface, nullptr
    );
  }

  ~Image() {
    if (ImageSurface) {
      SDL_FreeSurface(ImageSurface);
    }
  }
  Image(const Image&) = delete;
  Image& operator=(const Image&) = delete;

private:
  SDL_Surface* ImageSurface{nullptr};
};

It relies on an image called example.bmp being in the same location as our executable. The image we’re using in these examples is available here.

Our program currently renders the following window:

Screenshot of our program output

Source and Destination Rectangles

Most of our work in this lesson will be based on the 2nd and 4th arguments we’re passing to the Render() method:

// Image.h
class Image {
public:
  // ...
  void Render(SDL_Surface* DestinationSurface) {
    SDL_BlitSurface(
      ImageSurface,
      nullptr, // Source rectangle
      DestinationSurface,
      nullptr // Destination rectangle
    );
  }
  // ...
};

These two arguments are pointers to SDL_Rect objects representing a source rectangle and destination rectangle respectively. We’ll focus on the source rectangle for now.

When we pass a nullptr as the second argument, SDL_BlitSurface() will copy as much of the source surface as possible. If the destination surface is big enough to contain the entire source surface, then the entire source will be blitted.

To blit only a portion of the image, we’d define an SDL_Rect representing the rectangular area we’re interested in. SDL_Rect is a simple struct with 4 members:

  • x - how far the rectangle is from the left edge
  • y - how far the rectangle is from the top edge
  • w - how wide the rectangle is
  • h - how tall the rectangle is

We can select the entire surface by setting:

  • x to 0
  • y to 0
  • w to the surface’s width (available through the w member on SDL_Surface)
  • h to the surface’s height (available through the h member on SDL_Surface)

This creates behavior equivalent to before, where we passed nullptr as the source rectangle:

// Image.h
class Image {
public:
  // ...
  void Render(SDL_Surface* DestinationSurface) {
    SourceRectangle(
      ImageSurface, &SourceRectangle,
      DestinationSurface, nullptr
    );
  }
  // ...

private:
  SDL_Surface* ImageSurface;
  SDL_Rect SourceRectangle{
    0, 0, ImageSurface->w, ImageSurface->h
  };
};
Screenshot of our program output

Cropping

Let’s demonstrate how we can update our SourceRectangle to cause or Render() method to blit only a portion of the image:

Screenshot of our program output

To make our rectangle smaller, we reduce the w and h values:

// Image.h
class Image {
// ...

private:
  SDL_Surface* ImageSurface;
  SDL_Rect SourceRectangle{
    0, 0,
    ImageSurface->w - 50,
    ImageSurface->h - 100
  };
};

Our output now looks like this:

Screenshot of our program output

Let’s move our SourceRectangle to the bottom right of our image:

Screenshot of our program output

Our SDL_Rect is currently 50 pixels narrower and 100 pixels shorter than our image so, to move it to the bottom right edge, we use those values for the x and y positions:

// Image.h
class Image {
// ...

private:
  SDL_Surface* ImageSurface;
  SDL_Rect SourceRectangle{
    50, 100,
    ImageSurface->w - 50,
    ImageSurface->h - 100
  };
};

Our output looks like this:

Screenshot of our program output

As a final example, let’s crop from all edges. We’ll exclude 50 pixels from the left and right edges, and 100 pixels from the top and bottom edges:

Screenshot of our program output

Our SourceRectangle looks like this:

// Image.h
class Image {
// ...

private:
  SDL_Surface* ImageSurface;
  SDL_Rect SourceRectangle{
    50, 100,
    ImageSurface->w - 50,
    ImageSurface->w - 100,
    ImageSurface->h - 100
    ImageSurface->w - 200,
  };
};

Our output now looks like this:

Screenshot of our program output

Positioning

The fourth argument to SDL_BlitSurface() is an SDL_Rect representing where we want the image to be blitted on our destination surface. The x and y properties specify where we want the image positioned within the destination surface.

The w and h are used a little differently in the context of a destination rectangle. We’ll set them to 0 for now, and talk about them in more detail later.

To replicate the default behavior of SDL_BlitSurface(), we’d set the x and y values to 0, too. This places the image at the top left edge of the surface:

// Image.h
class Image {
public:
  // ...

  void Render(SDL_Surface* DestinationSurface) {
    SDL_BlitSurface(
      ImageSurface, &SourceRectangle,
      DestinationSurface, &DestinationRectangle
    );
  }
  
  // ...

private:
  SDL_Surface* ImageSurface;
  SDL_Rect SourceRectangle{
    0, 0, ImageSurface->w, ImageSurface->h};
  SDL_Rect DestinationRectangle{
    0, 0, 0, 0};
};
Screenshot of our program output

To move the image away from the left edge, we’d update the x and y components of the destination rectangle accordingly:

// Image.h
#pragma once
#include <iostream>
#include <SDL.h>
#include <string>

class Image {
 //...
 private:
  SDL_Surface* ImageSurface;
  SDL_Rect SourceRectangle{
    0, 0, ImageSurface->w, ImageSurface->h};
  SDL_Rect DestinationRectangle{
    50, 100, 0, 0};
};
Screenshot of our program output

Clipping

As we can see in the previous examples, we sometimes copy fewer pixels than our source rectangle contains. Clipping occurs when SDL needs to adjust what's being rendered. This happens in two main scenarios:

Source clipping: When the source rectangle extends beyond the boundaries of the source image. In this case, SDL2 only renders the part of the rectangle that's within the image.

Destination clipping: When the image (or part of it) would be rendered outside the boundaries of the destination surface. This can happen if:

  • The destination position would place the image partially or fully off-screen.
  • The image is larger than the destination surface.

In both cases, SDL automatically adjusts the rendering to show only the visible portion of the image.

If we need to detect and react to clipping, we can examine our destination rectangle after the call to SDL_BlitSurface(). Whilst SDL_BlitSurface() receives the source rectangle as a const SDL_Rect*, the destination rectangle parameter is simply an SDL_Rect*.

This indicates that SDL_BlitSurface() is going to modify our destination rectangle, and that’s indeed the case. Specifically, SDL_BlitSurface() updates the w and h properties of the destination rectangle with the dimensions of the area that was blitted, after clipping:

// Image.h
class Image {
public:
  // ...

  void Render(SDL_Surface* DestinationSurface) {
    SDL_BlitSurface(
      ImageSurface, &SourceRectangle,
      DestinationSurface, &DestinationRectangle
    );

    std::cout << "\nDestination Width: "
      << DestinationRectangle.w
      << ", Height:"
      << DestinationRectangle.h;
  }

  // ...
};
Destination Width: 512, Height:200
Destination Width: 512, Height:200
Destination Width: 512, Height:200
...

We can compare the sizes of our source and destination rectangles to understand if clipping is happening:

// Image.h
class Image {
public:
  // ...

  void Render(SDL_Surface* DestinationSurface) {
    SDL_BlitSurface(
      ImageSurface, &SourceRectangle,
      DestinationSurface, &DestinationRectangle
    );

    if (DestinationRectangle.w !=
      SourceRectangle.w) {
      std::cout << "Clipping Horizontally\n";
    }

    if (DestinationRectangle.h !=
      SourceRectangle.h) {
      std::cout << "Clipping Vertically\n";
    }
  }

  // ...
};
Clipping Vertically
Clipping Vertically
Clipping Vertically
...

Whilst SDL can handle source and destination rectangles being invalid in this way, it’s generally not something we want to rely on. Proactively taking steps to keep our object’s state accurate makes adding more powerful capabilities.

For example, adding more capabilities to this Image class may want to use the SourceRectangle, and that becomes more complex if we can’t assume the SourceRectangle is accurate.

Later in this chapter, we’ll extend our Image class to add this and additional capabilities.

Summary

In this lesson, we've explored several key concepts for manipulating images in SDL2:

  • Source and Destination Rectangles: We learned how to use SDL_Rect structures to control which parts of an image are rendered (source) and where they appear on screen (destination).
  • Cropping: We practiced selecting specific portions of an image by adjusting the source rectangle's dimensions and position.
  • Positioning: We discovered how to place images at different locations on the screen by modifying the destination rectangle's x and y coordinates.
  • Clipping: We understood how SDL2 handles situations where images don't fit entirely on the screen and learned to detect when clipping occurs.

These techniques form the foundation for more advanced image manipulation in SDL2. Later in this section, we’ll use this knowledge to expand our Image class with new capabilities. First, though, we’ll learn some more topics that will influence how we design that API.

In the next lesson, we’ll see how we can control the size of our rendering, by blitting a larger or smaller copy of our image onto the destination surface.

Was this lesson useful?

Next Lesson

Image Scaling and Aspect Ratios

Learn techniques for scaling images and working with aspect ratios
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Cropping and Positioning Images

Learn to precisely control image display using source and destination rectangles.

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

Free, Unlimited Access
Rendering Images and Text
  • 60.GPUs and Rasterization
  • 61.SDL Renderers
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

Free, unlimited access

This course includes:

  • 62 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Image Scaling and Aspect Ratios

Learn techniques for scaling images and working with aspect ratios
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved