16.5 Direkt auf den Bildschirm zeichnen
Ein Hinweis gleich vorweg. Dieses Kapitel ist nur für denjenigen gedacht, der seine eigenen Funktionen (Bibliothek) zum Zeichnen von Linien, Kreisen oder Rechtecken erstellen will. Das Zeichnen eines Pixels stellt dabei immer die Grundlage für das Zeichnen anderer grafischer Primitiven da. Dies kann sich bezüglich der Performance positiv auswirken – doch es werden dabei ein wenig mehr geometrische Kenntnisse abverlangt. Wie dem auch sei, wer sich dafür interessiert, bekommt hiermit die Grundlagen zum Setzen eines Pixels. Alle anderen, die erst nur die SDL-Funktionen verwenden wollen, können dieses Kapitel überfliegen.
Hinweis Für das Zeichnen von grafischen Primitiven wie Linien, Kreisen oder Polygonen können Sie die Bibliothek sdl_gfx verwenden. Diese Bibliothek stellt Ihnen Basiszeichenfunktionen, die alle auf das Zeichnen eines Pixels aufbauen, zur Verfügung. Um einen rechteckigen Bereich mit einer Farbe auszufüllen, wird der (Standard-)Bibliothek von SDL die Funktion SDL_FillRect() mitgeliefert.
|
Um ein Pixel zeichnen zu können, benötigen Sie wieder das Surface. In der Struktur SDL_Surface finden Sie eine Strukturvariable void *pixel. In dieser Variablen werden die Farbwerte der einzelnen Pixel gespeichert. Diese Strukturvariable benötigen Sie zum Manipulieren eines Pixels. Nun sind allerdings die Strukturvariablen alle mit dem Read-only-Attribut (nur zum Lesen) ausgestattet. Das Surface (einfach an eine Zeichen-Oberfläche denken), das sich ja im Grafikspeicher befindet und dessen Inhalt Sie auf dem Bildschirm wiedergegeben bekommen, muss jetzt verändert werden. Sie müssen somit direkt in den Grafikspeicher schreiben – was beim derzeitigen Stand Ihrer Kenntnisse unweigerlich zum Segmentation Fault führen würde. Der Grafikspeicher ist ein recht empfindlicher Teil der Hardware. Um das Schreiben zu realisieren, müssen Sie den Speicherbereich der Grafikkarte für andere Anwendungen sperren. Sperren können Sie das Surface mit folgender Funktion:
int SDL_LockSurface(SDL_Surface *surface);
Nachdem Sie die Sperre für das Surface gesetzt haben, haben Sie direkten Zugriff auf das Pixel. Zwischen dem Aufruf von SDL_LockSurface() und dem Aufheben der Sperre mit SDL_UnlockSurface() können Sie etwas in surface->pixel schreiben und auch wieder etwas daraus lesen, ohne dass es Probleme gibt. Da nicht alle Surfaces eine Sperre benötigen, können Sie das Makro SDL_MUSTLOCK(surface) verwenden, um zu überprüfen, ob eine Sperre nötig ist oder nicht. Gibt das Makro 0 zurück, können Sie auf dem Surface lesen und schreiben, wie es Ihnen in den Kram passt. Ansonsten müssen Sie das Surface sperren.
Hinweis Ab Version 1.1.8 der SDL-Bibliothek wird das Sperren rekursiv ausgeführt. Das sollten Sie beachten, weil dies die Reihenfolge des Freigebens der Sperren beeinträchtigt (Stack!).
|
Bei Erfolg gibt die Funktion SDL_LockSurface() 0 und bei einem Fehler –1 zurück. Um die Sperre wieder aufzuheben, müssen Sie die Funktion SDL_UnlockSurface() verwenden.
void SDL_UnlockSurface(SDL_Surface *surface);
Ein Surface sollte so schnell wie möglich wieder freigegeben werden. Natürlich sollten Sie auch hier mit dem Makro SDL_MUSTLOCK() überprüfen, ob ein Sperren zuvor überhaupt nötig war, um nicht ein nicht gesperrtes Surface freizugeben.
Der Pixel alleine macht noch keinen Indian Summer auf Ihrem Bildschirm. Sie brauchen Farbinformationen. Farben werden hierbei nach dem üblichen RGB-Schema erstellt. Zum Glück stimmen hierbei die englischen Anfangsbuchstaben mit den deutschen überein – RGB für Rot, Grün, Blau. Aus diesen drei Farben können Sie je nach Mischung jede andere Farbe erzeugen. Der maximale Wert ist 255 und der kleinste 0. Geben Sie z. B. allen drei Werten den Wert 255, erzeugen Sie die Farbe Weiß. Bei Vergabe von 0 an alle drei Werte haben Sie Schwarz. Gelb würden Sie mit der Mischung (255, 255, 0) erreichen. Sie können anschließend gerne selbst damit experimentieren.
Hinweis Natürlich können Sie bei der Angabe der Farben auch einen Hexwert (0x00 = 0 und 0xFF = 255) verwenden, wenn Sie damit besser vertraut sind.
|
Wenn Sie jetzt der Funktion die Farbwerte für Rot, Grün und Blau übergeben haben, müssen Sie daraus noch einen 32-Bit-Wert machen – was den internen Wert von SDL für RGB wiedergibt. Diesen Wert können Sie sich mit der Funktion SDL_MapRGB() zurückgeben lassen.
Uint32 SDL_MapRGB( SDL_PixelFormat *format,
Uint8 r, Uint8 g, Uint8 b );
Der Parameter SDL_PixelFormat ist ein Mitglied der Struktur SDL_Surface und wird mit surface->format angesprochen. Die anderen drei Parameter sind die 8-Bit-RGB-Werte.
Wenn Sie jetzt den Wert der Strukturvariablen pixel an die Struktur SDL_Surface mit entsprechenden Informationen übergeben haben, fehlt noch eine Funktion, ohne die gar nichts auf dem Bildschirm passieren würde.
void SDL_UpdateRect( SDL_Surface *screen,
Sint32 x, Sint32 y,
Sint32 w, Sint32 h );
Damit geben Sie einen bestimmten Bereich des Surfaces an, der neu gezeichnet werden soll. Geben Sie für x, y, w und h den Wert 0 an, wird der komplette Bildschirm neu gezeichnet.
Hinweis Wichtig ist es, dass Sie noch vor Verwendung der Funktion die Sperre für das Surface wieder freigeben.
|
16.5.1 Programmbeispiel – direkt auf den Bildschirm zeichnen
Das nun folgende Beispiel demonstriert Ihnen wieder die eben erwähnten Funktionen in der Praxis. Es wird ein 320 x 200 großes Surface mit 16 Bit Farbtiefe erstellt. Darin werden nach dem Zufallsprinzip 50000 einzelne Pixel gezeichnet. Anschließend beendet sich die Anwendung wieder.
/* sdl2.c */
#include <SDL/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd. h.>
/* Surface sperren */
static void Lock( SDL_Surface *screen ) {
/* Muss gesperrt werden? */
if (SDL_MUSTLOCK (screen)) {
if (SDL_LockSurface (screen) < 0) {
printf ("Konnte Surface nicht sperren: %s\n",
SDL_GetError ());
return;
}
}
}
/* Surface freigeben */
static void Unlock( SDL_Surface *screen ) {
if (SDL_MUSTLOCK (screen)) {
SDL_UnlockSurface (screen);
}
}
/* Einen 32-Bit-RGB-Wert zurückgeben */
static Uint32 GetColor( SDL_Surface *screen,
Uint8 R, Uint8 G, Uint8 B ) {
return SDL_MapRGB( screen->format, R, G, B );
}
/* Einen Pixel zeichnen */
static void DrawPixel (SDL_Surface * screen, int x, int y,
Uint8 R, Uint8 G, Uint8 B) {
/* Surface sperren, falls nötig */
Lock( screen );
/* Farbtiefe des Surface ermitteln (in Byte) */
switch (screen->format->BytesPerPixel) {
case 1: /* 1 Byte */
{ /* Auflösung: 8 Bit */
Uint8 *bufp;
/* Position x, y des Pixels an bufp */
bufp = (Uint8 *) screen->pixels + y * screen->pitch + x;
/* Farbe des Pixels verändern */
*bufp = GetColor( screen, R, G, B );
}
break;
case 2: /* 2 Bytes */
{ /* Auflösung: 15 Bit oder 16 Bit */
Uint16 *bufp;
/* Position x, y des Pixels an bufp */
bufp=(Uint16 *)screen->pixels + y * screen->pitch / 2 + x;
/* Farbe des Pixels verändern */
*bufp = GetColor( screen, R, G, B );
}
break;
case 3: /* 3 Bytes */
{ /* 24-Bit-Modus, wird hier nicht verwendet */
printf ( "Der 24-Bit-Modus wird in diesem Beispiel"
" nicht unterstützt\n");
exit (EXIT_FAILURE);
}
break;
case 4: /* 4 Byte */
{ /* Auflösung: 32 Bit */
Uint32 *bufp;
/* Position x, y des Pixels an bufp */
bufp=(Uint32 *)screen->pixels + y * screen->pitch / 4 + x;
/* Farbe des Pixels verändern */
*bufp = GetColor( screen, R, G, B );
}
default:
printf ("Konnte die Farbtiefe nicht ermitteln!?!?\n");
break;
}
/* Sperre wieder aufheben, falls nötig */
Unlock( screen );
/* Den Bereich, der verändert wurde, neu zeichnen */
SDL_UpdateRect (screen, x, y, 1, 1);
}
int main (void) {
SDL_Surface *screen;
int i, points = 50000;
/* 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 (320, 200, 16, SDL_SWSURFACE);
if (screen == NULL) {
printf ("Videomodus konnte nicht eingerichtet werden: "
" %s\n", SDL_GetError ());
return EXIT_FAILURE;
}
for (i = 0; i < points; i++) {
DrawPixel(screen, rand () % 320, rand () % 200, 0, 0, 255);
DrawPixel(screen, rand () % 320, rand () % 200, 0, 255, 0);
DrawPixel(screen, rand () % 320, rand () % 200, 255, 0, 0);
DrawPixel(screen, rand () % 320, rand () % 200, 255,255,0);
}
/* 1000 ms warten ... */
SDL_Delay (1000);
printf ("Erfolgreich beendet!\n");
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc `sdl-config --libs` `sdl-config --cflags` -o sdl2 sdl2.c
$ ./sdl2
Auch hier können Sie gerne nach Kenntnisstand herumexperimentieren. Sofern Sie die Algorithmen zum Implementieren von Grafikprimitiven kennen, steht Ihnen ja jetzt dank DrawPixel() nichts mehr im Wege.
|