SDL2_image: Loading Image Files with Different Formats

Last updated on November 20th, 2021

Image files to use in games and applications often are available in other formats than simple bitmap files (e.g. jpg, png, tif and so forth), because often bitmap files need a significant higher amount of disk space memory.

Also it would be desirable to load an image file directly and create a SDL2 texture from it instead of firstly creating a SDL2 surface and then creating a SDL2 texture from the surface (as seen in the chapter about bitmap loading).

The SDL2_image Unit

Here comes the SDL2_image unit into play. It allows:

  • Loading of many common image formats
  • Supported formats: ICO, CUR, BMP, GIF, JPG, LBM, PCX, PNG, PNM, TIF, XCF, XPM, XV, WEBP
  • Creation of SDL2 texture from image files directly

These features are obviously not part of native SDL 2.0. SDL2_image is an official extension of SDL 2.0, which is developed and maintained by the same developers. Therefore, before right jumping into the code we need the corresponding library files.

Installing SDL2_image

  • download the most recent version of the Runtime Binaries of the SDL2_image library for your system
  • install the library according to your system (Win32/64, Linux, Mac OS X)

SDL2_image Installing Instructions for Windows

Download the corresponding SDL2_image package depending on your system (32 bit or 64 bit) and extract the file. You will end up with a SDL2_image.dll and some further dlls which are necessary for support of some image formats. Copy all these files to your system folder, e.g. for Windows XP 32 bit C:\WINDOWS\system32\. If you are not sure about your system folder, you should copy all these files to the same folder where the source code file (.pas or .pp) of your SDL 2.0 program is.

IMG_Load and IMG_LoadTexture

SDL2 Texture and Surface relation diagram
Creative Commons License This image by https://www.freepascal-meets-sdl.net is licensed under a Creative Commons Attribution 4.0 International License.
An image file (left) can be loaded as a surface or a texture and then be rendered to the screen.

Have a look at the flow diagram below which is an extended version of the diagram seen in the chapter about loading of bitmap files. You see it is extended by two function with the prefix IMG instead of SDL, namely IMG_LoadTexture() and IMG_Load(). Both of these functions allow to load image files of all the supported file formats mentioned above. Also you see that IMG_LoadTexture() creates a texture directly from the image file, so we skip the step of first creating a SDL2 surface from the image file.

Let’s try the following image files (dimensions: 200 x 200 pixels, formats: bmp, jpg and png) but feel free to use any other image file you like.

Coding example with SDL2_image

And now let’s start with some code. I will show the code as a whole first and after that I will discuss it in smaller pieces from top to bottom until the “end.” 🙂

program Chapter4_SDL2;

uses SDL2, SDL2_image;

var
sdlWindow1 : PSDL_Window;
sdlRenderer : PSDL_Renderer;
sdlTexture1 : PSDL_Texture;

begin

  //initilization of video subsystem
  if SDL_Init(SDL_INIT_VIDEO) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
  if sdlWindow1 = nil then HALT;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;

  sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'C:\fpsdl.bmp');
  if sdlTexture1 = nil then HALT;
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent (sdlRenderer);
  SDL_Delay(2000);

  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //shutting down video subsystem
  SDL_Quit;

end.

Well, here it is. This code will create a window of dimensions 500 x 500 pixels which is showing the “Freepascal meets SDL” image for two seconds. After that the window closes automatically. The following screenshot shows the result of the example program.

Now let’s get into the code step by step.

program Chapter4_SDL2;

uses SDL2, SDL2_image;

var
sdlWindow1 : PSDL_Window;
sdlRenderer : PSDL_Renderer;
sdlTexture1 : PSDL_Texture;

Now the first eight lines are discussed. Well, the first line starts a Pascal program as usual. In contrast to the previous chapter, the uses clause is extended by SDL2_image. To be clear again, native SDL 2.0 has no support for different image formats, except for BMP image files. Although native SDL 2.0 allows for loading of BMP image files, it just allows for creation of SDL_Surfaces, but we would like to create SDL_Textures.

We need a texture variable to which we can load the image information of an image file. This will be “sdlTexture1” of PSDL_Texture type. All these variable types are pointer types which is indicated by the captial “P” at the beginning of the variable types’ names.

begin

  //initilization of video subsystem
  if SDL_Init(SDL_INIT_VIDEO) < 0 then HALT;

  sdlWindow1 := SDL_CreateWindow('Window1', 50, 50, 500, 500, SDL_WINDOW_SHOWN);
  if sdlWindow1 = nil then HALT;

  sdlRenderer := SDL_CreateRenderer(sdlWindow1, -1, 0);
  if sdlRenderer = nil then HALT;

As any Pascal program the main program’s begin-end block is initiated by “begin”. The initilization of SDL 2.0 is started as discussed in detail in the last chapter by SDL_Init().

After successful initialization of SDL 2.0 a window with title “Window1”  and a renderer is created as known from a previous chapter.

Creation of SDL_Texture and Rendering in SDL 2.0

  sdlTexture1 := IMG_LoadTexture(sdlRenderer, 'C:\fpsdl.bmp');
  if sdlTexture1 = nil then HALT;
  SDL_RenderCopy(sdlRenderer, sdlTexture1, nil, nil);
  SDL_RenderPresent (sdlRenderer);
  SDL_Delay(2000);

Now we load the image file to the SDL_Texture we called “sdlTexture1”. The function to do this is

IMG_LoadTexture(renderer: PSDL_Renderer; _file: PAnsiChar): PSDL_Texture

This function is provided by SDL2_image. Its prefix is IMG instead of SDL for native SDL 2.0 functions. That function is why we needed to insert SDL2_image in the uses clause. The parameters of this function are a renderer, that is “sdlRenderer” for us, and as a second the absolute or relative path to an image file, for us it is “C:\fpsdl.bmp”. Of course you may use any other directory to store/load the image or even use a different image. The function will recognize the image file’s format automatically, so feel free to load any of the allowed formats. If the loading fails, for instance you gave a wrong path as argument, this function will return nil.

Next we would like the successfully loaded image in “sdlTexture1” to be rendererd for which reason we pass it to the renderer by function

SDL_RenderCopy(renderer: PSDL_Renderer; texture: PSDL_Texture; srcrect: PSDL_Rect; dstrect: PSDL_Rect): SInt32.

At first this function asks for a renderer (and indirectly for the related window) to which we would like to copy the texture. In our case this will be “sdlRenderer” again. Next the texture to be copied to the renderer/window is required, this is “sdlTexture1” here. The last two parameters are named “srcrect” and “dstrect” and of type PSDL_Rect. PSDL_Rect is a SDL 2.0 predefined record to define rectangles, hence the name. I will not go into details about this here, but in the next chapter (Chapter 5) we will learn more about PSDL_Rect, although it will be in another context. For simplicity we just use nil as argument here. This makes the function to pass the full texture to the renderer/window and stretching it to the dimensions of the window. So the 200 x 200 pixel image is strechted to 500 x 500 pixels, the latter being the width and height of the window. This function returns 0 on success and the negative error code on failure.

Finally everything gets rendered to the window by the SDL_RenderPresent(Renderer) procedure as known from the previous chapter. To be able to see it, we wait with a 2 seconds delay.

Clean up the memory in SDL 2.0

  SDL_DestroyTexture(sdlTexture1);
  SDL_DestroyRenderer(sdlRenderer);
  SDL_DestroyWindow (sdlWindow1);

  //shutting down video subsystem
  SDL_Quit;

end.

We first created a window, then a renderer and finally a texture. So now we go the opposite way, first destroy the texture, then the renderer and finally the window. The procedures  are:

SDL_DestroyTexture(texture: PSDL_Texture)

SDL_DestroyRenderer(renderer: PSDL_Renderer)

and

SDL_DestroyWindow(window: PSDL_Window).

After removing the objects from memory, SDL 2.0 has to be quit as seen in the previous chapter.

Wow, we finally made it. Congratulations, this chapter is finished :-). The next chapter is waiting though.

← previous Chapter | next Chapter →

5 thoughts on “SDL2_image: Loading Image Files with Different Formats

  1. IMG_LoadTexture(renderer: PSDL_Renderer; _file: PAnsiChar): PSDL_Texture
    For some reason relative path to a file does not work here. When trying to launch the application, instead of the image with this path ‘assets\img.png’, a black screen is displayed. However, if i set the absolute path to the file, the image is displayed correctly. How does this work?

    1. Hi Con,

      well, relative paths work fine usually. I assume you are running Windows? – What happens, if you put img.png in the main directory of your application (where the executable is generated) and use ‘img.png’ as argument? Does this work?

      Best regards
      Matthias

  2. Good evening
    After watching your fantastic tutorial, I tried to load a texture into a record and I can’t print it on the screen. Can you tell me I’ve done wrong?
    The code is this

     program Shooter;
    
    uses
      SDL2, SDL2_image;
    
    type
      TJugador = record
            x, y: Integer;
            textura: PSDL_Texture;
      end;
    
    var
      ventana: PSDL_Window;
      visualizador: PSDL_Renderer;
      evento: PSDL_Event;
      activo: Boolean = true;
      jugador: TJugador;
    
    
    function cargar_textura(visualizador: PSDL_Renderer; nombre_archivo: PChar): PSDL_Texture;
    var
       textura: PSDL_Texture;
    begin
       // Cargamos la textura,
       textura := IMG_LoadTexture(visualizador, nombre_archivo);
       // la devolvemos,
       Result := textura;
       // y destruimos la textura, antes de salir.
       SDL_DestroyTexture(textura);
    end;
    
    procedure imprimir_textura(visualizador: PSDL_Renderer; textura: PSDL_Texture; x: Integer; y: Integer);
    var
       dest: TSDL_Rect;
    begin
            dest.x := x;
    	dest.y := y;
            dest.w := 200;
            dest.h := 200;
    	//SDL_QueryTexture(textura, nil, nil, @dest.w, @dest.h);
            SDL_RenderCopy(visualizador, textura, nil, nil);
    end;
    
    
    begin
      // Iniciamos SDL
      if SDL_Init(SDL_INIT_VIDEO) < 0 then Halt;
    
      // Creamos la ventana
      ventana := SDL_CreateWindow('Shooter', SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, SDL_WINDOW_SHOWN);
      if ventana = nil then Halt;
    
      // Creamos el renderer
      visualizador := SDL_CreateRenderer(ventana, -1, SDL_RENDERER_ACCELERATED);
      if visualizador = nil then Halt;
    
      new(evento);
    
      // creamos el jugador
      jugador.x := 100;
      jugador.y := 100;
      jugador.textura := cargar_textura(visualizador, 'avion.bmp');
    
      // Bucle principal del juego
      while(activo = true) do
      begin
    
          // se prepara la escena
          SDL_SetRenderDrawColor(visualizador, 96, 128, 255, 255);
          SDL_RenderClear(visualizador);
    
          // bucle de eventos
          while SDL_PollEvent(evento) = 1 do
          begin
               case evento^.type_ of
    
                     // Eventos de sistema
                     SDL_QUITEV: activo := false;
    
                     // Eventos de teclado
                     SDL_KEYDOWN: begin
                            case evento^.key.keysym.sym of
                                  SDLK_ESCAPE: activo := false;
                        end;
                    end;
               end;
          end;
    
          // se imprime la textura
          imprimir_textura(visualizador, jugador.textura, jugador.x, jugador.y);
    
          // se presenta la escena
          SDL_RenderPresent(visualizador);
    
      end;
    
      // Eliminamos los recursos que hemos creado / asignado
      dispose(evento);
      SDL_DestroyRenderer(visualizador);
      SDL_DestroyWindow(ventana);
    
      // Cerramos SDL
      SDL_Quit;
    
    end.

    1. You should not destroy the created texture in function “cargar_textura”. Keep in mind that you just return a pointer. The memory is destroyed, when accessing the texture via the “jugador” record, there is nothing there.

      Instead you should destroy the texture in the jugador record when you are finished using it.

      Have fun,
      Matthias

Leave a Reply

Your email address will not be published. Required fields are marked *