Login
Newsletter
Werbung

Fr, 2. August 2002, 00:00

Multimedia-Entwicklung mit SDL

Pixel zeichnen

Bisher haben wir immer nur Bilder geladen und angezeigt. Jedoch ist es auch oft nötig, einzelne Pixel zu zeichnen. Sei es um Linien und Kreise darzustellen (auch wenn es dafür wieder bereits fertige Bibliotheken gibt) oder auch aus Geschwindigkeitsgründen (wenn Pixelzeichnungen ausreichen). Das Zeichnen einzelner Pixel auf den Bildschirm ist jedoch nicht so trivial, wie es sich vielleicht anhören mag.

Farbauswahl-Dialogbox

Marco Kraus

Farbauswahl-Dialogbox

RGB-Grundlagen:
Eine Pixelfarbe wird auf dem Bildschirm über 3 Farben bestimmt. Dies sind RGB, also Rot, Grün und Blau. Durch die Mischung dieser Farbtöne kann man jede andere Farbe erzeugen. Die Farbselektion in GIMP (WaterColor-Selection) verdeutlicht dies:

Ein rein grüner Pixel wird also natürlich über die Werte R=0, G=255 und B=0 erzeugt. Ein gelber Pixel jedoch wird nur durch die Mischung von Rot und Grün generiert, was also R=255, G=255 und B=0 entsprechen würde. Weiss und Schwarz sind laut Farblehre keine Farben. Weiss wäre durch R=255, G=255, B=255 zu erreichen. Schwarz durch R=0, G=0, B=0. Neben RGB kann es noch einen Alphawert geben. Dieser wird aber erst relevant, wenn wir später Transparenz behandeln.

Nun wissen wir zwar, dass wir mindestens drei Werte benötigen, um einem Pixel die Farbe zu geben. Wie zeichnen wir aber nun einen Pixel auf den Bildschirm? Rein technisch gesehen müssen wir uns die Struktur eines Surfaces wieder in Erinnerung rufen. Dort gab es ein Element mit dem Namen: void *pixels;

Dieses Element speichert die Farbwerte der einzelnen Pixel des display-Surfaces. "Was interessiert mich hier das display surface?", werden einige wahrscheinlich fragen. Das Spezielle am Pixelzeichnen ist, dass wir direkt das dispay surface verändern. Wir schreiben also direkt in den Grafikspeicher. Nicht wie zuvor immer auf separate Surfaces, die wir dann durch einen Blit kopieren. Dies hat den riesigen Vorteil, dass wir mit hoher Geschwindigkeit arbeiten können und nicht mit Surfaces und Blits hantieren müssen. Der Nachteil dabei ist jedoch, dass bei der direkten Manipulation des Grafikspeichers dieser zuvor gelockt, also für andere Anwendungen gesperrt, sein muss. Ist dies nicht der Fall, könnten mehrere Zugriffe gleichzeitig auf den selben Speicherbereich stattfinden, was unweigerlich zu einem Segmention Fault führt.

Da die eigentliche Manipulation der Pixelmap des display-surfaces am Anfang recht kompliziert erscheint, verwenden wir die Standardfunktion der offiziellen SDL-Dokumentation. Diesen Code findet man fast in allen SDL-Applikationen wieder, die direkt Pixel zeichnen:

void DrawPixel(SDL_Surface *screen, int x, int y,Uint8 R, Uint8 G,Uint8 B)
{
 Uint32 color = SDL_MapRGB(screen->format, R, G, B);
 if ( SDL_MUSTLOCK(screen) )
 {
 if ( SDL_LockSurface(screen) < 0 ) {
 return;
 }
 }
 switch (screen->format->BytesPerPixel) {
 case 1: { /* vermutlich 8 Bit */
 Uint8 *bufp;
 bufp = (Uint8 *)screen->pixels + y*screen->pitch + x;
 *bufp = color;
 }
 break;
 case 2: { /* vermutlich 15 Bit oder 16 Bit */
 Uint16 *bufp;
 bufp = (Uint16 *)screen->pixels + y*screen->pitch/2 + x;
 *bufp = color;
 }
 break;
 case 3: { /* langsamer 24-Bit-Modus, selten verwendet */
 Uint8 *bufp;
 bufp = (Uint8 *)screen->pixels + y*screen->pitch + x * 3;
 if(SDL_BYTEORDER == SDL_LIL_ENDIAN) {
 bufp[0] = color;
 bufp[1] = color >> 8;
 bufp[2] = color >> 16;
 } else {
 bufp[2] = color;
 bufp[1] = color >> 8;
 bufp[0] = color >> 16;
 }
 }
 break;
 case 4: { /* vermutlich 32 Bit */
 Uint32 *bufp;
 bufp = (Uint32 *)screen->pixels + y*screen->pitch/4 + x;
 *bufp = color;
 }
 break;
 }
 if ( SDL_MUSTLOCK(screen) )
 {
 SDL_UnlockSurface(screen);
 }
}

Funktionsdetails:

Diejenigen, die mit mit dem Lesen dieses Tutorials das erste Mal etwas mit SDL zu tun haben, können diesen Abschnitt getost überspringen. Wir kopieren diese Funktion nachher einfach und verwenden sie. Es reicht, wenn wir wissen, dass sie funktioniert und mit

DrawPixel(SDL_Surface *screen, int x, int y, Uint8 R, Uint8 G, Uint8 B)

aufgerufen wird. Die Parameter sind relativ selbsterklärend. x und y sind hier die Positionen des zu verändernden Pixels auf dem Bildschirm. Die Maximalwerte sind auch hier natürlich wieder display->w und display->h. Die drei unsigned Integers R, G und B spiegeln die bereits erklärte Farbmischung des Pixels wieder.

Für alle, die es doch ein wenig genauer wissen wollen, hier die genauere Erklärung der Funktion:

Die übergebenen RGB-Werte werden zunächst mit SDL_MapRGB() in einen 32-Bit unsigned Integer-Wert umgewandelt. Dieser eine Wert spiegelt SDL-intern den RGB-Wert wieder. Um, wie schon erwähnt, Konflikte im Zugriff auf den Grafikspeicher zu vermeiden, "locken" wir das Surface (und den somit für das Surface reservierten Grafikspeicher). Dies geschieht über die Funktion SDL_LockSurface(). Um sicherzustellen, dass wir wirklich locken müssen, wird dies immer mit SDL_MUSTLOCK() gekoppelt. Wenn wir viel zeichnen, ist es sicherlich besser, das Locken aus der eigentlichen Drawfunktion zu nehmen und manuell im Hauptprogramm zu setzen. Somit wird speziell bei zeichenintensiven Schleifen das Surface nur einmal gelockt. Nun ist der Speicher jedenfalls auf eine der beiden Weisen gesichert und wir können problemlos hinein schreiben.

screen-&gt;format-&gt;BytesPerPixel liefert natürlich die Farbtiefe in Byte zurück. Die Rückgabe wurde in Byte gewählt, damit der nachfolgende switch-Befehl übersichtlicher wirkt. Ein screen-&gt;format-&gt;BitsPerPixel und entsprechende Änderung der switches ist aber genauso möglich. Nun wird in jedem Zweig ein Pufferpointer mit der entsprechenden Grösse angelegt. Diesem wird die Adresse des screen->pixels plus der entsprechenden Abweichung in x und y zugewiesen. Somit zeigt der Puffer direkt auf den zu manipulierenden Pixel an der richtigen Stelle. Jetzt wird nur noch dieser Adresse der richtige Wert, nämlich der durch MapRGB erstellte Farbwert, zugewiesen. Am Ende noch das gelockte Surface wieder frei geben und wir sind fertig. Der Bildpunkt hat nun unsere Farbe.

Nun zurück zu unserem eigentlichen Problem. Wir wollten einen Pixel auf dem Bildschirm ausgeben. Ohne uns weiter Gedanken zu machen, nutzen wir die oben beschriebene Funktion DrawPixel():

#include <stdlib.h>
#include "SDL.h"
#include "SDL_image.h"
// Fuer einen 24-Bit-Modus unabhaengig von der Bytereihenfolge.
// Wird von also DrawPixel() benoetigt
#include "SDL_endian.h"
void DrawPixel(SDL_Surface *screen, int x, int y,Uint8 R, Uint8 G, Uint8 B);
int main()
{
 SDL_Surface *display;
 // init video stuff
 if ( SDL_Init( SDL_INIT_VIDEO) < 0 )
 {
 fprintf(stderr, "SDL konnte nicht initialisiert werden: %s\n",
SDL_GetError());
 exit(1);
 }
 atexit(SDL_Quit);
 // init screen
 display = SDL_SetVideoMode( 800, 600, 16, SDL_SWSURFACE);
 if ( display == NULL )
 {
 fprintf(stderr, "Konnte kein Fenster 640x480px oeffnen: %s\n",
 SDL_GetError());
 exit(1);
 }
for( int x=0; x < 800; x++ )
{
 DrawPixel(display, x, display->h/2, 255, 255, 255);
}
SDL_Flip(display);
SDL_Delay(3000);
}

Detail:

Das Locking ist bereits in der Funktion DrawPixel() geschehen (siehe Detailinformationen zu DrawPixel). Darum müssen wir uns also an dieser Stelle nicht mehr kümmern. Der Rest sieht alles sehr einfach aus; ist es auch. Eine einfache for-Schleife, die auf der Horizontalen 800 Pixel in weiß zeichnet. Eine einfache Linie also. Dies kann man nach Herzenslust erweitern: For-Schleifen ineinander schachteln, Positionen berechnen oder die RGB-Werte dynamisch gestalten.

Noch zwei Anmerkungen zum Thema Pixelmanipulation:

  • Um Linien und Kreise zu zeichnen, muss man das Rad nicht neu erfinden. Es gibt eine sehr gute Bibliothek (6) von Andres Schiffler, die sdl_gfx. Diese stellt die Basiszeichenfunktionen auf Pixelebene (und noch einiges mehr) über ein einfaches Interface zur Verfügung.
  • Um den ganzen Bildschirm oder ein bekanntes SDL_Rect mit einer Farbe zu füllen, muss man auch nicht selbst jeden Pixel selbst zeichnen. Hier stellt schon SDL nativ die Funktion SDL_FillRect() zur Verfügung, welche als Parameter das Surface, das SDL_Rect und die RGB-Farbe annimmt. Details: siehe SDL-Doc-Project (7)

Quellen

(1) http://www.devolution.com/~slouken
(2) http://www.libsdl.org/mailman/listinfo
(3) http://www.libsdl.de/liste.htm
(4) http://www.libsdl.org
(5) http://www.geekcomix.com/snh/files/docs/sdl-kdev/sdl-kdev-mini-how2.html
(6) http://www.ferzkopp.net/Software.html
(7) http://sdldoc.csn.ul.ie

Über den Autor

Marco Kraus betreut libSDL.de und ist unter marco@libsdl.de zu erreichen. Er freut sich über jegliche Form von Feedback.

Kommentare (Insgesamt: 0 )
Pro-Linux
Pro-Linux @Facebook
Neue Nachrichten
Werbung