Bilder laden und anzeigen

Zuerst lernen wir einmal eine neue wichtige Struktur, nämlich SDL_Surface, kennen. Diese Struktur repräsentiert einfach irgendeine Art von Bilddaten im Speicher, egal ob das der gesamte Fensterinhalt oder nur ein kleines Bild ist. Wie SDL_Surface genau aufgebaut ist, braucht uns eigentlich nicht näher zu beschäftigen, da die Struktur von der SDL gefüllt wird und von ihr auch wieder ausgelesen wird. Eigentlich werden wir die Struktur nie direkt verwenden sondern immer nur mit Zeigern arbeiten, da die SDL für jede SDL_Surface Struktur dynamisch speicher allokiert. Aus diesem Grund sollten wir jede Instanz mit SDL_FreeSurface auch wieder freigeben.

Laden

Tux im Sonic Stil

Um ein Bild von der Festplatte zu laden gibt es die Funktion SDL_LoadBMP. Diese Funktion kann, wie ihr Name schon vermuten lässt, Bitmaps laden und eine entsprechende SDL_Surface Struktur erstellen und füllen. Wenn wir nicht nur Bitmaps sondern auch andere Bildformate laden wollen, dann können wir dazu SDL_Image, eine Erweiterung der SDL verwenden. Vorerst werden wir uns aber mit Bitmaps begnügen. Um also wirklich ein Bild zu laden rufen wir SDL_LoadBMP mit dem Dateinamen auf und speichern den Rückgabewert in einem Zeiger auf eine SDL_Surface Struktur. Wenn der Zeiger einen Wert ungleich Null hat, dann ist das Bild erfolgreich geladen worden. Das Laden des netten Tux aus der Open Clipart Library könnte zum Beispiel so ausschauen:

SDL_Surface *image = SDL_LoadBMP("tux.bmp");
 
if( !image )
{
  std::cerr << "Konnte Bild nicht laden: " << SDL_GetError() << std::endl;
}

Jetzt sollten wir unser Bild geladen haben und über image darauf zugreifen können. Da gibt es nur noch ein kleines Problem, da das Format des Bitmaps nicht unbedingt mit dem des Bildschirms übereinstimmen muss. Sollten die beiden Formate nicht übereinstimmen konvertiert die SDL jedes Mal wenn wir das Bild anzeigen dieses in das Bildschirmformat. Da das aber nur unnötigen Rechenaufwand bedeuten würde könne wir direkt nach dem Laden, einmalig mit Hilfe der Funktion SDL_DisplayFormat das Bild in das richtige Format konvertieren.

Anzeigen

Tux in unser Fenster geblittet

Jetzt haben wir zwar ein Bild geladen und über eine Variable darauf Zugriff, aber anzeigen wollen wir es schließlich auch noch. Dafür müssen wir das Bild in unser Fenster kopieren, was wir mit der Funktion SDL_BlitSurface machen können:

int SDL_BlitSurface( SDL_Surface *src, SDL_Rect *src_rect, SDL_Surface *dest, SDL_Rect *dest_rect );

Wie wir am Namen der Funktion schon erkennen können, wird dieser Vorgang oft auch als Blitten bezeichnet. Dabei müssen wir als erstes Argument einen Zeiger auf eine SDL_Surface Struktur für die Oberfläche angeben von der kopiert wird, als zweites einen Zeiger auf eine SDL_Rect-Struktur, mit der wir angeben welcher Teil der Oberfläche kopiert und als drittes und viertes Argument noch einmal die beiden gleichen Zeiger für die Zieloberfläche, die bei uns natürlich unser Fenster sein wird. Die SDL_Rect Struktur beschreibt einfach ein Rechteck mit den Koordinaten x und y der linken oberen Ecke und die Breite w1) bzw. Höhe h2):

struct SDL_Rect
{
  Sint16 x, y; // x und y Koordinate der linken oberen Ecke (vorzeichenbehaftet)
  Uint16 w, h; // Breite und Höhe (vorzeichenlos)
}

Wichtig ist dabei das bei der SDL die Koordinaten immer von links oben nach rechts unten laufen, wie wir auch auf der Grafik gut erkennen können. Außerdem können Oberflächen beim Blitten nicht verzerrt werden, weshalb bei der Angabe der des Zielrechtecks (dest_rect) nur die x und y-Koordinate berücksichtigt werden. Wenn wir bei dem Zeiger auf die SDL_Rect Struktur der Quelloberfläche 0 angeben wird das gesamte Bild kopiert. Nach dem Kopieren müssen wir noch SDL_Flip mit der Oberfläche des Fensters aufrufen, damit das Bild auch wirklich sichtbar wird, da wir ja immer nur in den Backpuffer 3) zeichnen, der natürlich nicht sichtbar ist. Mit dem jetzt angeeigneten Wissen sollte es jetzt also kein Problem mehr sein den Tux in eine Oberfläche zu laden und dann in ein Fenster zu kopieren. Als Anregung können wir uns noch folgendes Programm anschauen, aus dem auch der Screenshot auf der rechten Seite stammt:

#include <iostream>
#include "SDL.h"
 
//------------------------------------------------------------------------------
// Diese Funktion ladet ein Bitmap und konvertiert es in das aktuelle
// Oberflächenformat des Bildschirms
SDL_Surface* loadBMP( const char* file_name );
 
//------------------------------------------------------------------------------
int main( int argc, char **argv )
{
  // Video und Timersubsystem initialisieren
  if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER ) )
  {
    // Irgendwas ist schiefgegangen. SDL_GetError weiß mehr
    std::cerr << "Konnte SDL nicht initialisieren: "
              << SDL_GetError() << std::endl;
    return 1;
  }
 
  // SDL_Quit registrieren
  atexit(&SDL_Quit);
 
  // Ein Fenster erstellen
  SDL_Surface *screen = SDL_SetVideoMode( 800, 600, 32, SDL_DOUBLEBUF );
 
  if( !screen )
  {
    std::cerr << "Konnte Videomodus nicht setzen: "
              << SDL_GetError() << std::endl;
    return 1;
  }
 
  //----------------------------------------------------------------------------
 
  // Tux laden...
  SDL_Surface *tux = loadBMP("tux.bmp");
  SDL_Rect dest_rect;
 
  dest_rect.x = 350;
  dest_rect.y = 150;
 
  // und anzeigen
  SDL_BlitSurface(tux, 0, screen, &dest_rect);
 
  // und nicht vergessen den Backpuffer mit dem aktuellen
  // Bildpuffer zu tauschen
  SDL_Flip(screen);
 
  //----------------------------------------------------------------------------
 
  SDL_Event event;
 
  // Auf Events warten
  while( SDL_WaitEvent(&event) )
  {
    switch( event.type )
    {
      // Programm sollte beendet werden ('x' im Fenster wurde geklickt)
      case SDL_QUIT:
        // Oberfläche wieder freigeben...
        SDL_FreeSurface(tux);
        return 0;
    }
  }
 
  // Wenn SDL_WaitEvent 0 zurück gibt dann ist ein Fehler aufgetreten...
  std::cerr << "Fehler in der Nachrichtenbehandlung: "
            << SDL_GetError() << std::endl;
  return 1;
}
 
//------------------------------------------------------------------------------
SDL_Surface* loadBMP( const char* file_name )
{
  SDL_Surface *tmp = SDL_LoadBMP(file_name);
  SDL_Surface *image = NULL;
 
  if( tmp )
  {
    image = SDL_DisplayFormat(tmp);
    SDL_FreeSurface(tmp);
 
    if( !image )
      std::cerr << "Fehler beim konvertieren: "
                << SDL_GetError() << std::endl;
  }
  else
  {
    std::cerr << "Bild(" << file_name << ") konnte nicht geladen werden: "
              << SDL_GetError() << std::endl;
  }
 
  return image;
}

Als Erweiterung könnten wir jetzt auch noch dauernd die x und y-Koordinate vom Tux ändern und ihn damit über den Bildschirm bewegen. Damit sind wir unserem ersten Spiel schon einen großen Schritt näher gekommen. Jetzt fehlt nur mehr die Behandlung der Tastatur mit der wir uns im nächsten Teil beschäftigen werden.


Den Videomodus setzen (Ein Fenster erstellen) ← | ↑ SDL Start ↑ | → Animation und Timing

Diskussion

1)
eng. width = Breite
2)
eng. height = Höhe
3)
Der Backpbuffer dient zur Verbesserung der Qualität beim Rendern. Dabei hat man zwei Bildpuffer, einen der angezeigt wird und einen in den gezeichnet wird. Nach dem Zeichnen vertauscht man beide Puffer und kann dann in dem anderen das nächste Bild zeichnen