Taking the
first lesson as a basis, we will delve into the world of
SDL surfaces. As I said,
SDL surfaces are mostly images stored in memory. Imagine that we have an empty window of 320x240 pixels. In the
SDL coordinate system, the window is represented as follows:

This coordinate system is different from the one you are used to (I'm talking about Cartesian). But the main difference between these systems is that the
Y coordinate “grows” down. Understanding the
SDL coordinate system is important in order to properly draw the images on the screen, so take a good look.
Since we already have a prepared and customized surface (
Surf_Display ), we just need to find a way to draw images. This method is called blitching (from the English.
Blitting - moving a group of bits from one place to another, in our case means transferring the image (or part of it) over the other), i.e. a kind of overlay. But before we can do this, we must also find a way to load these images into memory.
SDL offers a simple function to implement your plans -
SDL_LoadBMP (note:
SDL_LoadBMP only allows uploading images in
* .BMP format, as its name suggests. To upload images of other formats,
SDL_image must be connected to the project, as comrade rightly
noted in the comments alrusdi in the first lesson, and use the
IMG_Load function). Sample code might look like this:
')
ExampleSDL_Surface* Surf_Temp; if((Surf_Temp = SDL_LoadBMP("mypicture.bmp")) == NULL) {
Everything is quite simple here,
SDL_LoadBMP accepts just one argument as a parameter - the path to the file you want to load, and it returns the surface containing the specified image. If the function returns
NULL , then either the file was not found, or it was damaged, or other, more complex errors occurred. Unfortunately, to the detriment of efficiency, this method does not provide full coverage of various loading errors. Very often the loaded image does not correspond to the pixel format of the surface into which we load it. Thus, during display, there may be a loss of performance, image colors, etc. (It is important that the prepared surface and the loaded image match each other in all respects, that is, (exaggerating) the size of the box would fit the size of the load). Fortunately,
SDL has a quick and painless workaround to this problem -
SDL_DisplayFormat . This function adjusts the already loaded image, and returns a new surface suitable for the format displayed.
Now you need to open the project created in the previous lesson and add two files:
CSurface.h and
CSurface.cpp . Open
CSurface.h and add the following:
CSurface.h #ifndef _CSURFACE_H_ #define _CSURFACE_H_ #include <SDL/SDL.h> class CSurface { public: CSurface(); public: static SDL_Surface* OnLoad(char* File); }; #endif
Thus, we have created a simple
OnLoad function that will load the surface for us. Now open
CSurface.cpp and add:
CSurface.cpp #include "CSurface.h" CSurface::CSurface() { } SDL_Surface* CSurface::OnLoad(char* File) { SDL_Surface* Surf_Temp = NULL; SDL_Surface* Surf_Return = NULL; if((Surf_Temp = SDL_LoadBMP(File)) == NULL) { return NULL; } Surf_Return = SDL_DisplayFormat(Surf_Temp); SDL_FreeSurface(Surf_Temp); return Surf_Return; }
So, a couple of things you should pay attention to:
1. Always reset your pointers before using them in any way (NULL or “0” does not matter). This will help avoid tuevuyuchi most different problems and errors;
2. Remember that
SDL_DisplayFormat returns a new surface based on the old one, so do not forget to release the resources occupied by that old surface. Otherwise, we will observe the surface "wandering" in memory as it pleases.
Now we have a way to load surfaces into memory, but we also need a way to map them to other surfaces. As well as for loading images,
SDL has a function for this:
SDL_BlitSurface . It may not be as easy to use as
SDL_LoadBMP , but don't be scared. Open
CSurface.h and add the following function prototype:
CSurface.h #ifndef _CSURFACE_H_ #define _CSURFACE_H_ #include <SDL/SDL.h> class CSurface { public: CSurface(); public: static SDL_Surface* OnLoad(char* File); static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y); }; #endif
Open
CSurface.cpp again and add the following:
CSurface.cpp #include "CSurface.h" CSurface::CSurface() { } SDL_Surface* CSurface::OnLoad(char* File) { SDL_Surface* Surf_Temp = NULL; SDL_Surface* Surf_Return = NULL; if((Surf_Temp = SDL_LoadBMP(File)) == NULL) { return NULL; } Surf_Return = SDL_DisplayFormat(Surf_Temp); SDL_FreeSurface(Surf_Temp); return Surf_Return; } bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y) { if(Surf_Dest == NULL || Surf_Src == NULL) { return false; } SDL_Rect DestR; DestR.x = X; DestR.y = Y; SDL_BlitSurface(Surf_Src, NULL, Surf_Dest, &DestR); return true; }
First of all, let's take a look at the arguments that are passed to the
OnDraw function. We see two surfaces, and two variables of type
int . The first surface is taken as the base (remember the board in the first lesson?), I.e. the one to which we will display everything in the future. Accordingly, the second surface is the one that we will impose on the base (and here are our stickers). Basically, we just place
Surf_Src on top of
Surf_Dest , that's the whole secret.
X and
Y are variables that denote the coordinates of a place on the surface of
Surf_Dest to which we will map
Surf_Src .
At the beginning of the function, we must make sure that we have surfaces, otherwise we return
false . Next, we create a variable of type
SDL_Rect . This is the
SDL structure, which consists of four properties:
X ,
Y ,
W ,
H. Of course you have already guessed that it sets the parameters of the displayed region of the surface. So far we are only interested in the coordinates of the place in which we will display the rectangle, and we don’t give a damn about its size. So, then we assign the coordinates passed to the
X ,
Y function to the structure of the displayed region. If you are wondering what the
NULL parameter was in the
SDL_BlitSurface (yes, the author, we are interested!), This is another parameter of the type
SDL_Rect . We will come back to this later.
Later came! I think that no one will be offended if we analyze the
SDL_BlitSurface signature a little earlier. Briefly I will explain: we do not always need to display the entire surface on top of another, there are many cases when you need to select some part of the image (for example, we have a tileset (from the English tileset - a set of images, just a lot of pictures collected in one image) and you need to choose from it a certain square with a texture or character, etc.). And so
int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);
takes as parameters in order, from left to right:
- surface to be applied;
- Parameters of the region for displaying the overlaid surface (i.e. which part of it will be displayed)
- surface on which we will impose;
- well, and, accordingly, the parameters of the region of the base surface, which will impose.
I think now everything has become more or less transparent and understandable.
At the end of the function, we draw the configured surfaces and return
true .
Now, to make sure everything works, let's create a test surface. Open
CApp.h , and add a new surface, and include the
CSurface.h header file we
created :
CApp.h #ifndef _CAPP_H_ #define _CAPP_H_ #include <SDL/SDL.h> #include "CSurface.h" class CApp { private: bool Running; SDL_Surface* Surf_Display; SDL_Surface* Surf_Test; public: CApp(); int OnExecute(); public: bool OnInit(); void OnEvent(SDL_Event* Event); void OnLoop(); void OnRender(); void OnCleanup(); }; #endif
Also in the constructor, do not forget to zero our surfaces first:
CApp.cpp CApp::CApp() { Surf_Test = NULL; Surf_Display = NULL; Running = true; }
And remember to clean!
CApp_OnCleanup.cpp #include "CApp.h" void CApp::OnCleanup() { SDL_FreeSurface(Surf_Test); SDL_FreeSurface(Surf_Display); SDL_Quit(); }
It is time to download something already. Open
CApp_OnInit.cpp and bring it to this view:
CApp_OnInit.cpp #include "CApp.h" bool CApp::OnInit() { if(SDL_Init(SDL_INIT_EVERYTHING) < 0) { return false; } if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) { return false; } if((Surf_Test = CSurface::OnLoad("myimage.bmp")) == NULL) { return false; } return true; }
Make sure you actually have a file
called myimage.bmp . If not, download or draw it yourself and put it in the directory with the executable file of your game. Open
CApp_OnRender.cpp and add the following:
CApp_OnRender.cpp #include "CApp.h" void CApp::OnRender() { CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0); SDL_Flip(Surf_Display); }
Notice the new
SDL_Flip feature. It updates the buffer and displays the
Surf_Display to the screen. This is called double buffering. It prepares the created surfaces first in memory, and then displays the prepared ones on the screen. If we didn’t use it, we’d see a flickering screen. Remember the
SDL_DOUBLEBUF flag we specified when creating the surface? This is exactly what turns on double buffering.
Now you can compile the project and make sure that everything works correctly. You should see the image in the upper left corner of the window. If so, congratulations, you are one step closer to the real game. If not, make sure that you have
myimage.bmp in the same folder as the executable file, and also that it opens normally in the graphics viewer. That's what happened with me:

(And yes, I have a little
cleverly modified the code and uploaded my avatar in
* .PNG format using
IMG_Load . I advise you to experiment with this function too, and with others too. Dare and everything will work out for you!). If you
deprecated conversion from string constant to 'char*' -wwrite-strings
a message that you want to change the signature of the
OnLoad (char * File) function to
OnLoad (const char * File) in
CSurface.h and accordingly in
CSurface.cpp .
Let's move on! We excused ourselves by finally displaying our first image in the window, but very often we only need to display a part of it, as an example — the tilesets listed below:
Those. With just one image, we only need to draw a part of it. Open
CSurface.h , and add the following code:
CSurface.h #ifndef _CSURFACE_H_ #define _CSURFACE_H_ #include <SDL/SDL.h> class CSurface { public: CSurface(); public: static SDL_Surface* OnLoad(char* File); static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y); static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H); }; #endif
Open
CSurface.cpp , and add the following function
(Important, we add the second OnDraw function, and do not replace the existing one! Are you aware of the function overloading ?) :
CSurface.cpp bool CSurface::OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H) { if(Surf_Dest == NULL || Surf_Src == NULL) { return false; } SDL_Rect DestR; DestR.x = X; DestR.y = Y; SDL_Rect SrcR; SrcR.x = X2; SrcR.y = Y2; SrcR.w = W; SrcR.h = H; SDL_BlitSurface(Surf_Src, &SrcR, Surf_Dest, &DestR); return true; }
See, this is basically the same function as before, except we added another
SDL_Rect . This region allows you to specify which pixels from the overlay surface to copy to the main one. Now, along with the coordinates, we also indicate the remaining two parameters - the width and height of
0, 0, 50, 50 and as a result we get the displayed region in the form of a square 50x50 pixels.

CApp_OnRender.cpp #include "CApp.h" void CApp::OnRender() { CSurface::OnDraw(Surf_Display, Surf_Test, 0, 0); CSurface::OnDraw(Surf_Display, Surf_Test, 100, 100, 0, 0, 50, 50); SDL_Flip(Surf_Display); }
And here is a piece of my avatar with an indent of 100 pixels from the top and left edge of the screen:

Links to source code:
Links to all lessons: