
SDL 2.0 Lesson 2: Lesson 2
- Transfer
- Tutorial

From a translator: a continuation of a series of Twinklebear tutorials, available in the original here . The previous lesson can be found here .
Today we will start refactoring the code from the last lesson, adding some very useful functions, as well as analyzing how images are positioned and scaled in SDL_Window.
As usual, we will start the program by enabling SDL. Also in this tutorial we will use the string class, so we will include it.
#include "SDL.h"
#include
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
SDL_Window *window = nullptr;
SDL_Renderer *renderer = nullptr;
We will also declare a pair of constant values for the width and height of the screen, along with global window and renderer declarations so that they are accessible from our functions. Again we initialize pointers as nullptr for security. If you are not using C ++ 11, initialize them as NULL.
Note: you should try to avoid using non-constant global values, or global values in general, i.e. you should never declare a global SDL_Window or SDL_Renderer. However, for this simple lesson we will still use them. We will look at using global objects in a few lessons.
Remember how we loaded texture in the first lesson? It’s not so bad to load one texture in main, but imagine if there were a couple of hundred of them? We would have to print the same piece of code every time! It is much better to declare a function to load textures by file name:
SDL_Texture* LoadImage(std::string file){
SDL_Surface *loadedImage = nullptr;
SDL_Texture *texture = nullptr;
loadedImage = SDL_LoadBMP(file.c_str());
if (loadedImage != nullptr){
texture = SDL_CreateTextureFromSurface(renderer, loadedImage);
SDL_FreeSurface(loadedImage);
}
else
std::cout << SDL_GetError() << std::endl;
return texture;
}
This function should look very familiar, because this is the same code that we wrote in the first lesson, but now it is combined into a good function. With it, we can pass the file name as a string and get a pointer to the loaded texture. Note that the pointer will be nullptr if loading fails.
Next, we want to write a function to make drawing easier. This function also allows you to specify the position of the image on the screen. It will take x and y coordinates, pointers to the texture and renderer, and draw the texture at the specified point.
void ApplySurface(int x, int y, SDL_Texture *tex, SDL_Renderer *rend){
SDL_Rect pos;
pos.x = x;
pos.y = y;
SDL_QueryTexture(tex, NULL, NULL, &pos.w, &pos.h);
SDL_RenderCopy(rend, tex, NULL, &pos);
}
To indicate where the texture will be drawn, we need to create SDL_Rect, the address of which we will pass to SDL_RenderCopy as the parameter of the target rectangle.
In order to create our rectangle, we take the x and y that were transferred and assign the corresponding values of the rectangle to their values. However, we also need to specify the width and height, because SDL 2.0 allows us to scale the texture. Try playing around with the width and height values and see what happens!
For now, we just pass the width and height of the texture to draw it on a 1: 1 scale. We can get these values using the SDL_QueryTexture function. It takes a pointer to the texture, we indicate the next two parameters as NULL (these parameters are responsible for the format and access level that we do not need). And finally, we must pass the addresses of the variables that the function will fill with the width and height of the texture.
Now that we have the initialized SDL_Rect, we can pass it to SDL_RenderCopy so that the texture is drawn at the point we need with the original height and width. The remaining NULL parameter passed to this function is responsible for cutting out a fragment of the original texture. We will analyze its use later.
Let’s see our functions in action. To get started, launch the SDL and create a window and a renderer, as last time. But we got something new here, namely SDL_WINDOWPOS_CENTERED. This option allows you to center it on a certain axis when creating a window, in this case we use to center the window both on x and y.
int main(int argc, char** argv){
if (SDL_Init(SDL_INIT_EVERYTHING) == -1){
std::cout << SDL_GetError() << std::endl;
return 1;
}
window = SDL_CreateWindow("Lesson 2", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (window == nullptr){
std::cout << SDL_GetError() << std::endl;
return 2;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED
| SDL_RENDERER_PRESENTVSYNC);
if (renderer == nullptr){
std::cout << SDL_GetError() << std::endl;
return 3;
}
Let's upload our pictures. For this tutorial, we will draw a background image tiled with tiles and a second image centered on top of the background.
Here is our background:

And it will be on top of the background:

Let's load them using the LoadImage function we just wrote.
SDL_Texture *background = nullptr, *image = nullptr;
background = LoadImage("Lesson2res/background.bmp");
image = LoadImage("Lesson2res/image.bmp");
if (background == nullptr || image == nullptr)
return 4;
Note that you need to change the paths to the images so that they match the paths on your system.
Before we draw pictures, we need to know where we want to place them. To get started, you need to understand how the SDL coordinate system works, so it is slightly different from the standard 2D Cartesian coordinate system. The SDL coordinate system looks like this:

Point (0,0) is located in the upper left corner of the screen. Values in Y increase when moving down the screen, and in X - to the right. It should also be noted that the point that we pass when drawing the image points to the upper left corner of the image, in contrast to the center, as in some other libraries.
Before we draw: remember that what we draw will be located under the subsequent images. Thus, the picture drawn in the first place will be under all, and in the last - above all.
You probably already noticed that our background image has a resolution of 320x240, which means we have to draw it four times to tile the entire screen.
Before we draw anything, we clear the screen, then we will position the elements. In order to get the width and height of the texture, we will again use the QueryTexture function. Of course, you can use a loop to draw the background, but in this case we will use the stupid method and just write the same text 4 times.
SDL_RenderClear(renderer);
int bW, bH;
SDL_QueryTexture(background, NULL, NULL, &bW, &bH);
ApplySurface(0, 0, background, renderer);
ApplySurface(bW, 0, background, renderer);
ApplySurface(0, bH, background, renderer);
ApplySurface(bW, bH, background, renderer);
Now we will draw an image that will be located on top of the background and in the center of the screen. Calculating the center point is extremely simple, because the point that we pass to the ApplySurface function is the coordinate of the upper left corner of the image. Therefore, we need to add an offset so that the center of the image matches the center of the screen.
int iW, iH;
SDL_QueryTexture(image, NULL, NULL, &iW, &iH);
int x = SCREEN_WIDTH / 2 - iW / 2;
int y = SCREEN_HEIGHT / 2 - iH / 2;
ApplySurface(x, y, image, renderer);
In order to see the drawing, we need to show the renderer, and then wait a few seconds to have time to see the result.
SDL_RenderPresent(renderer);
SDL_Delay(2000);
Finally, we end the program by freeing up the occupied memory, exit the SDL, and return 0.
SDL_DestroyTexture(background);
SDL_DestroyTexture(image);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
When you compile and run the program, the window should look something like this:

End of the lesson
So today's lesson has come to its end. See you in lesson 3: external SDL libraries.