SDL: Video

Aus Wikibooks

Das Video Subsystem[Bearbeiten]

Das Video-Subsystem der SDL ist relativ überschaubar, da Grafikprimitive zum Zeichnen von Objekten in anderen Projekten ausgelagert sind. Tatsächlich stellt SDL Ihnen lediglich eine Oberfläche zur Verfügung, auf der Sie zeichnen könnten, hätten Sie nur die nötigen Funktionen.

Innerhalb dieses Subsystems gibt es nur 7 verschiedene Strukturen, von denen Sie einige schon kennen und andere sicher niemals gebrauchen werden:

Selbstverständlich arbeitet auch SDL mit einer Verbindung zum Grafiktreiber. Diesen Treiber können Sie mittels

 char *SDL_VideoDriverName (char *puffer, int laenge_des_puffers)

erfragen. Die Funktion gibt Ihnen einen Zeiger auf puffer zurück. puffer selbst ist ein Speicherbereich, der laenge_des_puffers groß ist und Platz für den von der Funktion zurückgelieferten Namen des Treibers bietet.

Video-Modi[Bearbeiten]

Die Funktion zum Setzen des Videomodus

 SDL_Surface *SDL_SetVideoMode (int breite, int höhe, int bpp, Uint32 flags);

haben sie ja schon in Ansätzen kennen gelernt. Die Funktion erwartet eine Bildschirmgröße in breite und höhe sowie die Bits pro Pixel (bpp). Diesen Wert können Sie zwischen 8 und 32 nahezu beliebig wählen, gegebenenfalls wird Ihr gewünschter Wert emuliert. Typische Werte sind 16, 24, oder 32. Flags ist ein Wert, den Sie aus folgenden Fenstermodi bilden können:

SDL_ANYFORMAT Verhindert eine Emulation der Pixeltiefe (bpp-Parameter)
SDL_ASYNCBLIT Asynchrones blitten, interessant bei Mehrprozessorsystemen
SDL_DOUBLEBUF In Zusammenhang mit SDL_HWSURFACE wird ein zweiter Grafikpuffer angelegt, in den geschrieben werden kann. SDL_Flip (...) zeigt dann jeweils den aktualisierten Puffer
SDL_FULLSCREEN Vollbild-Modus
SDL_HWPALETTE Exklusiver zugriff auf die Farbpalette
SDL_HWSURFACE die Grafikoberfläche wird in der Videohardware angelegt
SDL_NOFRAME Erzeugt ein Fenster ohne Fensterdekoration
SDL_OPENGL Erzeugt eine Oberfläche, die man in Zusammenhang mit OpenGL(tm) oder Mesa benutzen kann
SDL_OPENGLBLIT Erlaubt das Blitten auf OpenGL-Oberflächen
SDL_RESIZABLE Erzeugt einen größenveränderliche Oberfläche
SDL_SWSURFACE Die Grafikoberfläche wird im Hauptspeicher angelegt

Die Funktion gibt im Fehlerfall NULL, sonst einen Zeiger auf eine Grafikoberfläche zurück.

SDL verfügt über viele Video-Modi, die nahezu beliebig miteinander kombinierbar sind. Das bedeutet, daß bekannte und frei wählbare Auflösungen sich z. B. im Hardwarepuffer der Grafikkarte anlegen lassen. Um herauszufinden, ob eine bestimmte Kombination auf der gewählten Hardware verfügbar ist, dient die Funktion

 SDL_Rect **SDL_ListModes (SDL_PixelFormat *format, Uint32 flags);

Die Funktion gibt NULL zurück, wenn kein Format passt, -1, wenn alle Formate passen und ein Array von Rechtecken, wenn eine Auswahl an Auflösungen verfügbar ist. Folgendes Beispiel verdeutlicht das:

 #include <SDL/SDL.h>
 #include <stdio.h>
 int main (void)
 {
   SDL_Rect **modi;
   int i;
   if (SDL_Init (SDL_INIT_VIDEO) != 0)
     exit (-1);
   modi = SDL_ListModes (NULL, SDL_HWSURFACE|SDL_FULLSCREEN|SDL_OPENGL);
   if (modi == NULL)
     printf ("Kein Video-Modus verfügbar\n");
   else if (modi == (SDL_Rect**)-1)
     printf ("Jeder Modus ist verfügbar\n");
   else
     for (i = 0; modi[i]; i++)
       printf ("x = %2d, y = %2d, w = %3d, h = %3d\n",
         modi[i]->x, modi[i]->y, modi[i]->w, modi[i]->h);
   return 0;
 }

Die Ausgabe auf einer bestimmten Hardware könnte so aussehen:

 x =  0, y =  0, w = 1024, h = 768
 x =  0, y =  0, w = 832, h = 624
 x =  0, y =  0, w = 800, h = 600
 x =  0, y =  0, w = 720, h = 400
 x =  0, y =  0, w = 640, h = 480
 x =  0, y =  0, w = 640, h = 400
 x =  0, y =  0, w = 640, h = 350

Um herauszufinden, ob ein bestimmter Videomodus zur verfügbaren Hardware passt, dient die Funktion

 int SDL_VideoModeOK (int breite, int höhe, int bpp, Uint32 flags);

bpp ist die Anzahl der Bits pro Pixel, flags ist eine Kombination der verfügbaren Fenstermodi. Die Funktion gibt 0 zurück, wenn der Videomodus nicht verfügbar ist.

Oberflächen[Bearbeiten]

Oberflächen sind Variablen vom Typ SDL_Surface. Letztlich bilden sie einen Teil eines Grafikspeichers ab, haben aber ebenfalls einige Verwaltungselemente wie die Größe der Grafik gespeichert. Oberflächen sind z. B. Ihr gegenwärtiges Grafikfenster wie auch eine kleine Grafik, die Sie geladen haben.

Oberflächen erstellen[Bearbeiten]

Oberflächen kann man mit Funktionen wie

 SDL_Surface *SDL_SetVideoMode (int breite, int höhe, int bpp, Uint32 flags);
 SDL_Surface *SDL_LoadBMP (const char *dateiname);
 SDL_Surface *SDL_CreateRGBSurface (Uint32 flags, int breite, int höhe, int bpp, Uint32 Rmaske, Uint32 Gmaske, int32 Bmaske, Uint32 Amask);
 SDL_Surface *SDL_CreateRGBSurfaceFrom (void *pixel, int breite, int höhe, int bpp, int pitch, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask);

erzeugen.

Oberflächen entfernen[Bearbeiten]

Die Funktion

 void SDL_FreeSurface (SDL_Surface *oberfläche);

dient dazu, den belegten Speicher freizugeben.

Zugriff auf die Pixeldaten[Bearbeiten]

Wollen Sie wirklich einmal direkt auf die Pixeldaten zugreifen, sei es, um eine eigene Zeichenroutine zu schreiben oder um Daten direkt auszulesen müssen sie einen exklusiven Zugriff auf die Pixeldaten beantragen:

 if (SDL_MUSTLOCK(surface))
   SDL_LockSurface (surface);
 
 /* jetzt greifen wir auf die Pixeldaten zu */
 /* surface->pixels */

 /* nun wieder freigeben */  
 if (SDL_MUSTLOCK(surface))
   SDL_UnlockSurface (surface);

SDL_MUSTLOCK ist hierbei ein Makro, welches zu 0 ausgewertet wird, wenn SDL_LockSurface und SDL_UnlockSurface unnötig sind.

Der eigentliche Zugriff mit einer PutPixel-Funktion könnte bei 32 Bit Farbtiefe folgendermaßen aussehen:

 void SDL_PutPixel(SDL_Surface *surface, int x, int y, Uint32 color)
 {
   Uint32 *ptr = (Uint32 *)surface->pixels;
   int lineoffset = y * (surface->pitch / 4);
   ptr[lineoffset + x] = color;
 }

Oberflächen kopieren[Bearbeiten]

Mit Hilfe der Funktion

 int SDL_BlitSurface(SDL_Surface *von, SDL_Rect *von_rect, SDL_Surface *ziel, SDL_Rect *ziel_rect);

können Sie eine Oberfläche auf eine andere kopieren. Sie können also eine Grafik in ihr Grafikfenster hineinkopieren. Sie können auch Ausschnitte einer Grafik an einen Ort kopieren, jedoch können Sie die Größe nicht verändern. Mit anderen Worten, Sie können mit dieser Funktion nicht stauchen oder stretchen oder die Grafik in irgend einer Weise verändern. Ist von_rect NULL, so wird die gesamte Oberfläche kopiert. Ist hingegen ziel_rect NULL, wird an den Ursprung von ziel kopiert. Die Funktion gibt 0 zurück, wenn sie erfolgreich war.

Das Rechteck, in welches Sie hineinblitten können Sie zum einen über das ziel_rect bestimmen, andererseits aber auch durch Clipping festlegen:

 void SDL_SetClipRect (SDL_Surface *surface, SDL_Rect *rect)

Das Rechteck rect ist der Bereich auf surface, in den geblittet werden kann. Kein anderer Bereich wird berücksichtigt. Setzen Sie rect auf NULL, so bedeutet dies die gesamte Oberfläche.

Um die Clipping-Region abzufragen dient

 void SDL_GetClipRect (SDL_Surface *surface, SDL_Rect *rect);

Oberflächen ins Bildschirmformat umwandeln[Bearbeiten]

Als eine sehr große Geschwindigkeitsbremse beim Blitten macht sich das konvertieren der Formate der einzelnen Oberflächen bemerkbar. So wird z.b wenn wir ein Bild mit 16bit auf eine 32bit Bildschirmoberfläche blitten das Bild jedes Mal auf 32bit konvertiert. Das ist zwar noch nicht sehr problematisch, da das konvertieren einigermaßen schnell auf den heute üblichen Grafikkarten läuft, doch gibt es schon bei Szenen mit mehr als 50 Bildern auf einmal größere Probleme mit der Performance. Warum sollte man dann nicht das Bild gleich nach dem Laden ins Bildschirmformat konvertieren, dass dies nicht mehr bei jedem einzelnen blitten gemacht werden muss? Da sich das viele gedacht haben und es auch viele gemacht haben gibt es in der SDL seit einiger Zeit die Funktion "SDL_Surface *SDL_DisplayFormat(SDL_Surface *surface);", und das konvertieren der Bilder stellt sich mit dieser viel leichter heraus als erwartet. Hier ein Beispiel:

SDL_Surface *temp, *image;
...
temp = SDL_LoadBMP("tux.bmp");
image = SDL_DisplayFormat(temp); // Wandelt das Bild ins Bildschirmformat um und weist die Adresse image zu
SDL_FreeSurface(temp); // Die Oberfläche temp wird nun nicht mehr benötigt

So, war doch wirklich einfach oder? Jetzt kann man endlich ohne Reue rumblitten wie man will ohne im Hinterkopf zu haben, dass die Oberfläche immer wieder konvertiert wird und somit die wichtige Performance verloren geht.

Arbeiten mit Farben[Bearbeiten]

Alpha Blending[Bearbeiten]

Transparenz in Spielen ist schon was Tolles, denn durch sie sehen manche Sachen wie Wasser, Blut oder auch Gläser erst richtig real aus. Aber auch eine Menge anderer Dinge, wie z.B. schöne Fades, lassen sich mit Transparenz bewirken und deshalb gibt es auch in der SDL eine Möglichkeit, Oberflächen transparent zu machen. Dass es so etwas gibt, konnte man schon z.B. bei den Colorkeys erahnen. Möglich ist das ganze bei der SDL mit der Funktion SDL_SetAlpha, die als ersten Parameter einen Zeiger auf diejenige Oberfläche erwartet, welche transparent werden soll. Als zweiter Parameter wird dann ein Flag gefordert, wobei man hier SDL_SRCALPHA übergeben muss damit die anderen Angaben in der Funktion für's Alpha Blending beachtet werden, doch kann man als weiteren Flag natürlich auch SDL_RLEACCEL nehmen, um RLE Beschleunigung für's Blending zu nutzen. Letzten Endes wird dann noch die Stärke der Transparenz als vorzeichenlose Ganzzahl erwünscht. Hier gilt, dass der höchste Wert 255 für keine Transparenz, also komplette Sichtbarkeit steht und dass 0 für volle Transparenz, also Unsichtbarkeit steht. Hier ein Beispiel:

...
// Macht die Oberfläche: "alpha_image" halb transparent
SDL_SetAlpha(alpha_image, SDL_SRCALPHA | SDL_RLEACCEL, 128);
...

Schon wieder etwas, was mit der SDL super einfach ist. Übrigens: Eine Oberfläche mit Halbtransparenz, wie sie in dem Beispiel verwendet wird, wird schneller geblittet als eine mit anderer Transparenz, da diese Halbtransparenz bei den meisten Grafikkarten optimiert ist.

Bitmap-Grafiken[Bearbeiten]

SDL kann Bitmapgrafiken laden und schreiben. Mit

 SDL_Surface *SDL_LoadBMP (const char *dateiname);

laden sie ein Bild im BMP-Format, mit

 int SDL_SaveBMP (SDL_Surface *surface, const char *dateiname);

können Sie ein Bild schreiben. Diese beiden Funktionen sind Makros. SDL_SaveBMP gibt 0 zurück, wenn das Bild erfolgreich gespeichert wurde. Im Kapitel SDL-Image lernen Sie eine Funktion kennen, mit deren Hilfe Sie Grafiken aus nahezu allen relevanten Bitmap-Formaten laden können.


Oberflächen auffrischen[Bearbeiten]

Um in SDL Änderungen wirksam werden zu lassen, muss neu gezeichnet werden. Dies geschieht mit der Funktion

 void SDL_UpdateRect (SDL_Surface *surface, Sint32 x, Sint32 y, Sint32 breite, Sint32 höhe);

Nach dem Funktionsaufruf werden alle innerhalb des Rechtecks veränderten Grafikelemente gezeichnet. Sind alle Parameter 0, wird die gesamte Oberfläche neu gezeichnet.

Hat man ein Array von Rechtecken, die es regelmäßig neu zu zeichnen gilt, so kann

 void SDL_UpdateRects (SDL_Surface *surface, int anz_rects, SDL_Rect *rects);

benutzt werden. Diese Funktion erneuert anz_rects Rechtecke auf einer Oberfläche. Jedes Rechteck ist ein Element des Arrays rects.