16.8 Der rechteckige Bildbereich von SDL
Im Abschnitt zuvor wurde die Struktur SDL_Rects schon kurz angesprochen. Zwar handelt es sich hierbei um eine simple Struktur für einen rechteckigen Bereich, wo die Position von links oben und die Größenangabe gespeichert wird, doch ist gerade diese Information eine der wichtigsten in der SDL-Programmierung. Die Struktur SDL_Rect ist wie folgt definiert:
typedef struct {
Sint16 x, y;
Uint16 w, h;
} SDL_Rect;
x und y stehen für die linke obere Ecke und w für die Breite, h für die Höhe des rechteckigen Bereichs – der Bildausschnitt, wenn Sie so wollen.
Im Beispiel zuvor wurde beim Anzeigen eines Bildes der komplette Videomodus verändert – was in der Praxis natürlich absolut ineffizient ist. Wenn Sie also ein Surface fester Größe erstellt haben, können Sie somit mit dieser Struktur nur einen bestimmten Bereich im Surface erneuern. Oft will man vielleicht auch nur einen kleinen Teilbereich erneuern – was sich gerade aus Performancegründen auch empfiehlt. Und dabei muss nicht immer das komplette Surface neu kopiert werden (was gerade bei größeren Bilddateien auf Dauer negativ auffällt).
Um Ihnen zu demonstrieren, worum es sich bei einem Bildbereich handelt, soll das Beispiel vom Abschnitt zuvor ein wenig umgeschrieben werden. Die Bilder, die zuvor auf dem veränderbaren Surface dargestellt werden, sollen nun auf einem Surface fester Größe wiedergegeben werden. Außerdem sollen die Bilder zur Demonstration um 50 x 50 Pixel vom rechten oberen Bereich eingerückt werden. Dies erledigen Sie, indem Sie den rechteckigen Bereich des Haupt-Surfaces mit der Struktur SDL_Rect verändern:
SDL_Rect ziel;
...
ziel.x = 50;
ziel.y = 50;
ziel.w = bild->w;
ziel.h = bild->h;
Jetzt benötigen Sie noch einen zweiten rechteckigen Bereich für das Surface des Bildes. Dieses Surface stellt ja den Quellbereich da, den Sie anschließend mit der Funktion SDL_BlitSurface() in den rechteckigen Zielbereich ziel kopieren (die Funktion wurde bereits behandelt). Den Zielbereich ziel verwenden Sie nun, um wirklich auch nur den rechteckigen Bereich des Bildes zu erneuern, der neu auf dem Bildschirm erscheint. Häufig werden hierzu auch wiederum nur Bildausschnitte eines Bildbereichs benötigt. Diese Angaben müssen Sie allerdings dann bei dem rechteckigen Quellbereich machen, der folgendermaßen aussehen kann:
SDL_Rect quelle;
...
for (i = 640; i--;) {
quelle.x = i + 50;
quelle.y = 0;
quelle.w = bild->w / 2 + 50;
quelle.h = bild->h;
SDL_BlitSurface (bild, &quelle, screen, &ziel);
/* den veränderten Bildschirmbereich auffrischen */
SDL_UpdateRects (screen, 1, &ziel);
}
Sie können damit anschließend in der Praxis gerne herumexperimentieren. Würden Sie jetzt das Neuzeichnen mit einem Funktionsaufruf von
SDL_UpdateRect(screen,0,0,0,0);
machen, würden Sie wieder das komplette Surface neu zeichnen. Zwar wäre es auch möglich, wirklich nur den aktuellen Bildbereich neu zu zeichnen, der nötig ist, allerdings kann dies recht schnell fehleranfällig werden. Für solch einen Fall eignet sich hervorragend die Funktion:
void SDL_UpdateRects( SDL_Surface *display,
int numrects, SDL_Rect *rects );
Mit SDL_UpdateRects() können Sie einfach und komfortabel den rechteckigen Bereich rects im Ziel-Surface display angeben, der neu gezeichnet werden soll. Der zweite Parameter numrects zeigt es auch schon an, dass es möglich ist, mit dieser Funktion gleich mehrere Rechtecke auf einmal zu erneuern. Hierfür müssten Sie allerdings ein Array vom Typ SDL_Rects als dritten Parameter übergeben.
Dass wirklich nur ein rechteckiger Bereich gezeichnet wird, zeigt Ihnen das gleich folgende Beispiel, wenn das nächste Bild (oder der Ausschnitt) davon über das vorherige Bild geschoben wird, ohne dass das vorherige Bild verschwindet. Das Beispiel lädt geradezu ein, mit den Parametern zu spielen. Beispielsweise wird ein Bild von oben eingeschoben und eines von links – hierfür können Sie ja die Funktion SDL_UpdateRects() mit einem SDL_Rect-Array verwenden.
/* sdl5.c */
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd. h.>
static void display_bmp (const char *datei, SDL_Surface * screen,
int x, int y) {
SDL_Surface *bild;
SDL_Rect ziel, quelle;
int i;
/* lädt die BMP-Datei in ein Surface */
bild = IMG_Load (datei);
if (bild == NULL) {
fprintf (stderr, "'%s' konnte nicht geladen werden: %s\n",
datei, SDL_GetError ());
return;
}
/* auf den Bildschirm kopieren */
/* die Surfaces sollten hier nicht gelockt sein. */
/* x,y = linke obere Ecke; w, h = Breite und Höhe */
ziel.x = 50;
ziel.y = 50;
ziel.w = bild->w;
ziel.h = bild->h;
for (i = 640; i--;) {
quelle.x = i + 50;
quelle.y = 0;
quelle.w = bild->w / 2 + 50;
quelle.h = bild->h;
SDL_BlitSurface (bild, &quelle, screen, &ziel);
/* den veränderten Bildschirmbereich auffrischen */
SDL_UpdateRects (screen, 1, &ziel);
}
SDL_FreeSurface (bild);
}
int main (int argc, char **argv) {
SDL_Surface *screen;
int i;
if (argc == 1) {
printf ("Bitte mindestens eine gültige Grafikdatei "
" angeben!\n");
return EXIT_FAILURE;
}
/* SDLs Videosystem initialisieren und auf Fehler prüfen */
if (SDL_Init (SDL_INIT_VIDEO) != 0) {
printf ("Konnte SDL nicht initialisieren: %s\n",
SDL_GetError ());
return EXIT_FAILURE;
}
/* Wenn die Anwendung beendet wird, */
/* wird die Funktion SDL_Quit() ausgeführt */
atexit (SDL_Quit);
/* Videomodus mit 640 x 480 Pixel, Hi-Color (16 Bit) */
/* einrichten Oberfläche (Surface) Standardmäßig in */
/* den Hauptspeicher (SDL_SWSURFACE) legen */
screen = SDL_SetVideoMode (640, 480, 16, SDL_SWSURFACE);
if (screen == NULL) {
printf ("Videomodus konnte nicht eingerichtet werden: "
" %s\n", SDL_GetError ());
return EXIT_FAILURE;
}
/* Slideshow starten */
/* Zeigt alle BMP-Bilddateien an, die in der Kommandozeile */
/* mit angegeben wurden */
for (i = 1; argv[i] != NULL; i++) {
display_bmp (argv[i], screen, 0, 0);
SDL_Delay (1000);
}
printf ("Erfolgreich beendet!\n");
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc `sdl-config --libs` `sdl-config --cflags` -o \
sdl5 sdl5.c -lSDL_image
$ ./sdl5 john2.bmp john.bmp john2.bmp john.bmp
|