Translate

Saturday, April 26, 2014

Displaying an image with SDL - basic C++ class structure

Today, we will create a simple image displaying program with SDL 2.0 and Mingw. This will help you understand some of the program structure related to C++ and how SDL used objects to render images on screen. We will be making a class to hold our program logic, and using that class to render an image to the screen.

Create header file; this file will hold our class, the one we will be building our program with. With a class, an entire object, with it’s variables and methods, can be encapsulated into a single object, to be used as needed or desired by a program. In our example, this class will run the loop for displaying our image to the screen, and for testing for user input.

Add the two includes at the very top of the file:

#include "SDL2/SDL.h"
#include <stdio.h>

And enter the following to start the class definition: class cpp_Application {

The class keyword will start the class definition for this header file, defining the name for the class as cpp_Application when an object of this class is created. After this definition, enter the next line below and type public: . This will define any method or variable with a public scope, meaning any method or variable can be used or see by anywhere in the program.

On the lines below, define the following 4 methods:

cpp_Application()   //The default constructor for C++ classes
~cpp_Application()   //The default constructor for C++ classes
Int executeProgram();   //Method to run the program in a loop; this method will be called to call all other loops
Int getInputEvent(SDL_Event* event)   //This method takes in a parameter of ‘SDL_Event’ type to check for user input. For this tutorial, it simply tests for interaction with the window.
Then, while still defining the class in the header file, type private: . This will define any methods and variables defined after this line only viewable and usable to this class only. This helps with a lot of the concepts that make C++, such as enabling data encapsulation to protect the data from being altered by another component in the program. In the next few lines in the header file, below the private: declaration, enter the following few variables:

bool running; //Boolean for program execution
SDL_Event    applicationEvent; //Event object for user interaction
SDL_Window*  applicationWindow = NULL; //The window to open and use for our program
SDL_Surface* windowSurface = NULL; //Surface object to be used with window for rendering.
SDL_Surface* testImage = NULL; //Basic bmp image for tutorial use

End the class definitions with };, like any other method. Please remember the semi-colon ‘;’, this tells the compiler that our class definition is done at this point.

Create another file, this time a .cpp file, but name it after the .h file. When this file is opened, type #include “headerName.h”. This line will ‘include’ the header file in the source when compiling this cpp file, looking at it for the definitions and boundaries of the data.  Don’t forget to also include the “SDL2/SDL.h” file as well.

In the cpp file, we add the definitions to all the methods defined in the class in the header file. Starting with the constructor method:

CPP_Application::CPP_Application(void) //Default constructor
{
   running = true; //Set running boolean to true
   SDL_Init( SDL_INIT_VIDEO ); //Initialize SDL
   applicationWindow = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED , 800, 600, SDL_WINDOW_SHOWN); //Create Window for SDL
   windowSurface = SDL_GetWindowSurface( applicationWindow ); //Get the surface for the created window, and return it to a Surface object
   testImage = SDL_LoadBMP( "imagefilename" );
   if( testImage == NULL ) //Tests to see if image loaded properly, if not, then an error message is displayed
   {
       printf( "Unable to load image %s! SDL Error: %s\n", "testImage.bmp", SDL_GetError() );
       running = false; }
};

First note, this method starts with the class name and :: . This is because we are defining the function of the method declared in the class, and ‘::’ tells the compiler that this function’s scope is being defined for that classes method. So every method declared in a class that needs a definition should start with the original classes name and ::.

This first method is the constructor method, which is called first every time an object of className type is created. This constructor method will initialize every variable needed to start this class; in our case, it will set the running Boolean variable to true, initialize SDL with SDL_Init(SDL_INIT_VIDEO), and initialize all the objects in our class by calling the appropriate methods. applicationWindow will be created when SDL_CreateWindow() returns; windowSurface will be initialized with SDL_GetWindowSurface(), that method being passed the already created window for our program. And finally, our image object testImage will be called and initialize with our image file to display with SDL_LoadBMP(). If the image file is NULL, our program tests for this, and ends execution if the testImage is NULL. Note that the window dimensions are 800 to 600, this will e important for our image. Note also our image being loaded is a .bmp file; we will need this format for this tutorial. This image format is easily found in MS Paint, which will suffice for our needs.

With our constructor defined, defining our destructor will be easier. Enter the following:

CPP_Application::~CPP_Application(void) //Default destructor
{
   SDL_Quit();
};

Again, note the class name and use of :: for scope definition. This method simply calls SDL_Quit(), which will end execution and use of SDL and close all objects related to SDL before closing the object itself. Also note the tilde ‘~’ character by the number 1 on your keyboard; this character is specifically for defining a destructor.

The next method to define will give us our program loop:

int CPP_Application::executeApplication() //Function to run when program starts execution, main loop
{
   while(running)
   {
      //Render the image
      SDL_BlitSurface(testImage, NULL, windowSurface, NULL);
      SDL_UpdateWindowSurface(applicationWindow);
     
      //Get the input from the window
      getUserInput(&applicationEvent);
   };
};

The function starts with a while loop, with our running Boolean as a conditional; when this Boolean is set to false, this loop ends and the program with it. While this loop executes, the following methods will be called repeatedly:

SDL_BlitSurface() : Taking four arguments, this method will blit, or draw an image to our surface. The first parameter is our image to blit, the second one is NULL (normally used to define the boundary size for the first parameter), the third parameter is our window surface (the one defined in the constructor), and the fourth is NULL (again, used for boundary sized for the third parameter). This method tells the computer to draw this first image unto the latter image defined in the third parameter.

SDL_UpdateWindowSurface() : Taking the SDL_Window object as an argument, this method simply redraws our window to display our current image.

And the third method called in the loop is the fourth method defined in our class, getUserInput().

The fourth method defined in our class is the getUserInput(SDL_Event) method. This method takes an SDL_Event pointer object as a parameter, which is a way for SDL to check for user input from various devices. When a user event is detected, this method will call to check for what kind of even has happened by calling SDL_PollEvent(event) != 0 . If the event object isn’t NULL, then it is compared to several flags that indicate the current status of the program: in our tutorial, all we do is check for end program conditions, and set the running Boolean to false if the window was exited out of by the button at the top-right.

From here, in the .cpp file, define the main() method below the other definitions. Once ready, simply enter the following code in the main:

CPP_Application application;
application.executeApplication();
return 0;
These lines will define as object of our class by name for its type, and by the name application for its object name. The object name is the name called by the program to reference the object of the class type defined. This object will have its executeApplication() method called, which will start the loop to run the program and display our image. And the last line will return 0, ending our program execution after the loop exits.

With these two files, create and enter a new directory and find the SDL2.dll – you will need to move that .dll into your new directory to make this program, or any program that uses SDL2 to even start up. When you have the .dll file for SDL2 inside the same directory as your .h and .cpp files, start up MS Paint.

In MS Paint, simply create an image of 800 by 600 pixels (or as close as you can) and add whatever colors, text, or drawing you would like to see displayed to the window. Remember to save this image as a .bmp file, so that our program can load it into our Surface object. Save this file in the same directory as the other files, and save it to an easily remembered name, like test.bmp or image.bmp. Before compiling the source code, remember to go back into the .cpp file and add the image file name to the SDL_LoadBMP method between two quotation marks, with the .bmp file type at the end (i.e : “image.bmp”).

With all this done, enter the command prompt and maneuver to the directory with all the files you’ve just made. Enter the following command to compile and link this program:

g++ -o SDL2.exe cpp_Application.h cpp_Application.cpp –lmingw32 –lSDL2main –lSDL2

If the compilation and linking goes well, the execution of your program should display the image you just drew in MS Paint!

No comments:

Post a Comment