Game Development with SDL2 Part 3: Drawing our first image

Guest Posts Bilal Cheema

Author: Bilal Cheema

Previous Post of the Series: Game Development with SDL2 Part 2: Event Handling

In this post, we are going to draw our first image and show it on to the screen! The idea may looks simple to you but it is one of the toughest! But don't worry, I will simplify this by discussing all about Hardware Rendering and skipping most of the Software Rendering. From rendering, we mean drawing our image on the screen. We should use the word 'render' and not 'draw' because 'the screen of a device' and 'a piece of paper' are two very different things!

Software Rendering with SDL2

Unfortunately, we can't proceed without completely knowing what is software rendering and how to do it with SDL2. Software Rendering is the process of generating the image on the screen with the use of CPU only. The GPU or Graphics Hardware which includes our graphics card present inside the device, is not used at all! In other words, the GPU is not fulfilling its own purpose i.e generating Graphics because it is done with CPU only which is highly inefficient and slow!

In SDL2, software rendering is done via structure called SDL_Surface which is then, used to get all color values of all the positions of an image. This surface is then, blitted on to the surface of the SDL_Window to get the image on to the screen. There is no need to have the SDL_Renderer instance!

Getting image into the SDL_Surface

SDL_Surface* surface = NULL;

surface = SDL_LoadBMP("your_image.bmp"); //Works with BMP images only

//Codes here...

SDL_FreeSurface(surface); //Deleting the surface at the end

Hardware Rendering with SDL2

Hardware Rendering is the process of generating the image on the screen with the use of GPU. CPU is also used, not for rendering but for getting the resources and giving it to the GPU. We will only be using hardware rendering! In fact, we already dealt with the hardware rendering in the first two post! The SDL_Renderer object is used for hardware rendering. Software rendering don't needs any renderer.

In SDL2, hardware rendering is done via structures called SDL_Texture and SDL_Renderer. Texture is a general term used for the resources that are going to be used by the GPU for rendering purposes. SDL_Texture don't have any methods to get the image for rendering. In order to do that, we use SDL_Surface.

Getting image into the SDL_Texture

//Let us assume that we created SDL_Renderer with the name, 'renderer' before this
SDL_Texture* texture = NULL;

SDL_Surface* temp = NULL; //This is going to be a temporary SDL_Surface object
temp = SDL_LoadBMP("your_image.bmp");

texture = SDL_CreateTextureFromSurface(renderer, temp);

SDL_FreeSurface(temp); //Deleting the surface after creating texture from that surface

//Codes here...

SDL_DestroyTexture(texture); //Deleting the texture at the end

SDL_image 2.0

In the above codes, you can see that we are able to load BMP images only using SDL_LoadBMP() method. They are very uncompressed and are heavy type of images which are not suitable for creating good games! Fortunately, SDL provides us a library called SDL_image here which is capable of loading many types of images. Just download it and equip it with your project like you did before, with the base SDL2 library.

Using SDL_image to get image into the SDL_Surface

//After SDL_Init(), initialize IMG but this is completely optional!
//I only needed to load PNG images so, I used the flag, IMG_INIT_PNG in it.
IMG_Init(IMG_INIT_PNG);

SDL_Surface* surface = NULL;
surface = IMG_Load("your_image.png"); //Works with PNG images and more...

//Codes here...

//Before SDL_Quit() and unlike intializing, this is compulsory!
IMG_Quit();

SDL_Rect for defining dimensions of the rendering image

We learned some of the basics. Now, we can render images easily but there is still one problem: how can we simply put it to a specific position on the window with a particular size? Well, SDL solves this issue with the structure called SDL_Rect. SDL_Rect contains four integers called as x, y, w and h.

Our window consists of pixels which are just tiny boxes, if we create a window of size, 800 by 600, every row of the window will have 800 pixels and every colomn, 600 pixels. In the very first post of this series, we learned that the window acts like a 2D Cartesian coordinate graph with 'x' equals to zero at the extreme left and 800 at the extreme right, and 'y' equals to zero at the extreme top and 600 at the extreme bottom, for a window of size, 800 by 600. Now, we have to mention the positions, x and y of the SDL_Rect structure, along with their sizes w (means width) and h (means height), all measured in pixels.

//If the window is of size 800 by 600, using this instance of SDL_Rect
//will cause the image to render at the bottom left corner of the window
SDL_Rect rect;
rect.x = 0; //Extreme left of the window
rect.y = 500; //Very bottom of the window
rect.w = 100; //100 pixels width
rect.h = 100; //100 pixels height

Drawing/Rendering our first image!

That's all we need to know. The only thing that we haven't discussed yet, is a method combining all these things in order to give a proper image on to the screen! SDL_RenderCopy() is that method which simply copies the texture to the rectangle coordinates in order to be displayed on to the screen. Take that image below and try rendering it with the same codes as below!

#include <SDL.h>

//In order to access SDL_image features, we must include SDL_image.h
#include <SDL_image.h>

#undef main

int main(int argc, char** args)
{
    SDL_Init(SDL_INIT_EVERYTHING);
    //For loading PNG images
 IMG_Init(IMG_INIT_PNG);
    SDL_Window* window = SDL_CreateWindow("Getting Started", SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
    SDL_Event input;
    bool quit = false;

    SDL_Texture* texture = NULL;
    SDL_Surface* temp = IMG_Load("awesome_face.png");

    //Filling texture with the image using a surface
 texture = SDL_CreateTextureFromSurface(renderer, temp);

    //Deleting the temporary surface
 SDL_FreeSurface(temp);

    SDL_Rect rect;
    rect.x = 0; //Extreme left of the window
 rect.y = 500; //Very bottom of the window
 rect.w = 100; //100 pixels width
 rect.h = 100; //100 pixels height
 //'rect' defines the dimensions for the bottom-left of the window

    while (!quit)
    {
        while (SDL_PollEvent(&input) > 0)
        {
            if (input.type == SDL_QUIT) quit = true;
        }

        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
        SDL_RenderClear(renderer);

        //Copying the texture on to the window using renderer and rectangle
     SDL_RenderCopy(renderer, texture, NULL, &rect);

        SDL_RenderPresent(renderer);
    }

    //Deleting the texture
 SDL_DestroyTexture(texture);

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    //For quitting IMG systems
 IMG_Quit();

    SDL_Quit();

    return 0;
}

Concept behind the Code

The concept behind these codes is just the same as what we are getting through in the paragraphs above and in the previous posts. SDL_RenderCopy() has 4 parameters, one for the renderer, one for the texture and the other two for the rectangles. Renderer takes the texture and copies it on to the screen at the rectangle coordinates. It is not important to have the third parameter filled with the rectangle. We will discuss about the third parameter of the SDL_RenderCopy() method and more, in the next post. For now, just consider these things and practice them! Try to experiment with the codes! This might looks hard to you (especially the rectangle thing) but once you get used to it, this will not be a problem at all!

All the other code is as clear and self-explanatory as it looks! And if it is not, you must go through all my previous posts of this series. For having more programming stuff or if you have something to discuss with me, you can visit my blog: bacprogramming.wordpress.com

And as always, Happy Coding!