Paletten - Programmierung

Palette – Was ist das?

Das Bild, das auf einem Bildschirm dargestellt wird, besteht im allgemeinen aus vielen farbigen Punkten, sogenannten Pixeln, die in einer rechteckigen Matrix angeordnet sind.

Im Textmodus können nur ganze Buchstaben, die aus mehreren Pixeln zusammengesetzt sind, verändert werden. Im Graphikmodus kann man jedem dieser Pixeln eine eigene Farbe zuordnen. Nun stellt sich die Frage, wie man dem Computer zum Beispiel eine Mischung zwischen Blau und Grün beibringt. Bei dem PC wurde dieses Problem durch RGB–Anteile gelöst. Jeder Farbe wird somit Rot-, Grün- und Blauanteil zugeordnet.

Eine Palette ist eine Tabelle, in der die RGB–Anteile den einzelnen Farben zugeordnet sind.

Diese Zuordnung wird durch eine Art Zeiger auf einen Paletteneintrag realisiert. Im Bildschirmspeicher steht daher für jeden Pixel eine Zahl, die die Nummer des gewünschten Paletteneintrags angibt und in dem Paletteneintrag stehen die RGB-Werte.

Diese Zuordnung spart einerseits Speicher und andererseits können alle Pixel, die die selbe Farbnummer tragen, auf einmal verändert werden.

Bei hohen Farbtiefen ist diese Vorgangsweise aber nicht mehr sinnvoll und die einzelnen RGB-Werte werden für jeden Pixel einzeln angegeben.

Veränderung der Palette

Hier stellt sich die Frage, wie man eine Palette überhaupt verändern kann.

Eine VGA-kompatible Graphikkarte bietet verschiedene Ports an, mit denen man dies realisieren kann.

Pixel Write Adress Read / Write
Port: 3C8h

Auf diesen Port muß die Farbnummer geschrieben werden, bevor die RGB Anteile geschrieben werden können.

Pixel Read Adress   Write only
Port: 3C7h

Auf diesen Port muß die Farbnummer geschrieben werden, bevor die RGB Anteile gelesen werden können.

Pixel Color Value Read / Write
Port: 3C9h

Dieser Port stellt den Datenport dar. Hier können die Rot/Grün/Blau-Anteile hintereinander gelesen oder geschrieben werden.

Als erstes möchte ich hier zwei kleine Funktionen vorstellen, die die Anteile einer Farbe setzen oder auslesen.

/********************************************************************

void getcol(int i,unsigned char *r,unsigned char *g,unsigned char *b)

Ermittelt die RGB - Anteile einer Farbe

Parameter:

int i Farben - Nummer

unsigned char * r,g,b Rot / Grün / Blauanteil der Farbe

*********************************************************************/

void getcol(int i,unsigned char *r,unsigned char *g,unsigned char *b)

{

outp(0x3C7,i); //Farbnummer bekanntgeben

*r=inp(0x3c9); //RGB Werte auslesen

*g=inp(0x3c9);

*b=inp(0x3c9);

}

/********************************************************************

void setcol(int i,unsigned char r,unsigned char g,unsigned char b)

Setzt die RGB - Anteile einer Farbe

Parameter:

int i Farben - Nummer

unsigned char r,g,b   Rot / Grün / Blauanteil der Farbe

/********************************************************************/

void setcol(int i,unsigned char r,unsigned char g,unsigned char b)

{

outp(0x3c8,i); //Farbnummer bekanntgeben

outp(0x3c9,r); //RGB Werte setzten

outp(0x3c9,g);

outp(0x3c9,b);

}

Als nächstes möchte ich zwei Funktionen vorstellen, die die gesamte Palette setzten bzw. auslesen

/*********************************************************************

void getpal(unsigned char * palette)

Ermittelt die Palette, die von der Graphikkarte verwendet wird

Parameter:

unsigned char * palette Erhält RGB Anteile der einzelnen Farben

/********************************************************************/

void getpal(unsigned char * palette)

{

int i;

outp(0x3C7,0); //Auslesen ab Farbe 0

for(i=0;i<FARBEN_ANZAHL;i++) //Farben durchlaufen

{

 palette[i*3]=inp(0x3c9); //RGB Werte lesen

 palette[i*3+1]=inp(0x3c9);

 palette[i*3+2]=inp(0x3c9);

}

}

/*********************************************************************

void setpal(unsigned char * palette)

Legt die Palette fest, die von der Graphikkarte verwendet werden soll

Parameter:

unsigned char * palette Enthält RGB Anteile der einzelnen Farben

/********************************************************************/

void setpal(unsigned char * palette)

{

int i;

outp(0x3c8,0); //Setzen ab Farbe 0

for(i=0;i<FARBEN_ANZAHL;i++) //Farben durchlaufen

{

 outp(0x3c9,palette[i*3]); //RGB Werte setzen

 outp(0x3c9,palette[i*3+1]);

 outp(0x3c9,palette[i*3+2]);

}

}

FARBEN_ANZAHL Gibt die Anzahl der zu bearbeitenden Farben an.

Mit diesen Funktionen kann man einige schöne Effekte erreichen. So kann man es zum Beispiel bewerkstelligen, daß Wasser am Bildschirm so aussieht, als würde es fließen oder ähnliches, ohne die eigentlichen Bilddaten zu verändern. Dies wird meist durch Rotation eines Teils der Palette realisiert.

Man kann aber auch den gesamten Bildschirm abblenden (abdunkeln) bzw. einblenden (aufdunkeln). Dieser Effekt wird auch als “fading” bezeichnet. Man dekremiert bzw. inkremiert dabei die einzelnen RGB Werte bis der Bildschirm ganz dunkel ist bzw. bis das Bild in seinen Originalfarben dargestellt wird.

Eine Möglichkeit, wie man diesen Effekt bewerkstelligt, zeigen die folgenden Funktionen:

/*********************************************************************

void fadepalout(int from,int count)

Abdunkeln

Fadet einen Farb - Block der aktuellen Palette ab (setzt alle Anteile

auf schwarz)

Parameter:

int from Startfarbe des Blocks

int count Anzahl der Farben des Blocks

*********************************************************************/

void fadepalout(int from,int count)

{

int i,j; //Zähler

unsigned char palette[FARBEN_ANZAHL*3]; //Temporär Palette RGB

getpal(palette); //Aktuelle Palette ermitteln

count=(count+from)*3; //Anzahl der Anteile des Blocks

for(j=0;j<=FARBEN_TIEFE;j++)

{

for(i=0;i<count;i++) //Alle Farbanteile durchlaufen

if(palette[i]!=0)palette[i]--;

 WaitRetrace();

 setpal(palette); //Palette setzen

 delay(50);

}

return;

}

Als erstes wird die aktuelle Palette ausgelesen. Die RGB Werte dieser Palette werden in der Schleife kontinuierlich derkemiert, d.h. die Farben werden immer dunkler. Damit unsere Modifikationen auch sichtbar werden, muß die Palette nach jedem Schleifendurchgang neu gesetzt werden. Das Delay dient nur zur Verlängerung des Vorgangs.  

/*********************************************************************

void fadepalin(int from,int count,unsigned char *destpal)

Aufdunkeln

Fadet einen Farb - Block der aktuelle Palette auf eine neue Palette

Parameter:

int from Startfarbe des Blocks

int count Anzahl der Farben des Blocks

unsigned char * destpal Neue RGB - Anteile, auf die

gefadet werden soll

/********************************************************************/

void fadepalin(int from,int count,unsigned char *destpal)

{

int i,j; //Zähler

unsigned char palette[FARBEN_ANZAHL*3]; //Temporär Palette RGB

getpal(palette); //Aktuelle Palette ermitteln

count=(count+from)*3; //Anzahl der Anteile des Blocks

for(j=0;j<=FARBEN_TIEFE;j++)

{

for(i=0;i<count;i++) //Alle Farbanteile durchlaufen

{ //Paletten angleichen

if(palette[i]<destpal[i])(unsigned char)(palette[i])++;

else if(palette[i]>destpal[i])palette[i]--;  }

 WaitRetrace();

 setpal(palette); //Palette setzen

 delay(50);

}

return;

}

Die Funktion fadpalin() ist das Gegenstück zu fadepalout(). Die aktuellen RGB-Werte werden dabei einfach der Originalpalette des Bildes (destpal) immer mehr angenähert.

Um die Funktion WaitRetrace() zu verstehen, muß man wissen, wie der Bildschirm sein Bild aufbaut.

Es wird eine Elektronenkanone benutzt, die Zeile für Zeile vom oberen linken Rand bis zum unteren rechten Rand abfährt und je nach Intensität des Elektronenstrahls ein Aufleuchten eines Pixels verursacht, daß bei ausreichender Geschwindigkeit für unser Auge wie ein konstantes Leuchten aussieht.

WaitRetrace() wartet nun unter Verwendung eines VGA–Ports auf den Moment, bei dem die Elektronenkanone am unteren Bildschirmrand angekommen ist, den Elektronenrand abschaltet und zurück auf den oberen linken Rand fährt. Dieses Zurückstellen des Elektronenstrahls wird vertikaler Retrace genannt.

Wird WaitRetrace() beim Faden nicht vor dem Setzten der Palette aufgerufen, so kann es zum sogenannten Einschneien kommen. Dabei leuchten einige Pixeln auf, die eigentlich nicht leuchten sollten.

Dies wird dadurch verursacht, daß einige RGB-Werte durch das Programm genau zu dem Zeitpunkt verändert werden, bei dem die VGA-Karte diese zum Monitor schickt, was durch WaitRetrace() unterbunden wird.

/*********************************************************************

void

WaitRetrace(void)

Wartet auf vertikalen Retrace

/********************************************************************/

void WaitRetrace(void)

{

while(!(inp(0x3da)&8));

while(inp(0x3da)&8);

}

Zur Demonstration des Effekts kann folgendes Programm dienen:

#include <stdio.h>

#include <conio.h>

#include <dos.h>

#include <stdlib.h>

#define FARBEN_ANZAHL 256 //Anzahl der Farben

#define FARBEN_TIEFE  64-1 //Höchster Wert, den ein R/G/B Anteil

//enthalten kann

void getpal(unsigned char * palette)

...

void setpal(unsigned char * palette)

...

WaitRetrace(void)

...

void fadepalout(int from,int count)

...

void fadepalin(int from,int count,unsigned char *destpal)

...

/*******************************************************************/

/****************  H A U P T   P R O G R A M M  ********************/

/*******************************************************************/

void main(void)

{

int i; //Zähler

unsigned char palette[FARBEN_ANZAHL*3]; //Temporär Palette RGB

randomize(); //Zufallsgenerator aktivieren

for(i=0;i ;i++) //Bildschirm füllen

{

 textcolor(rand()%16);

 cprintf(„Hallo“);

}

getpal(palette); //Aktuelle Palette holen

fadepalout(0,FARBEN_ANZAHL-1); //Bildschirm abdunkeln

delay(500); //Eine halbe Sekunde warten

fadepalin(0,FARBEN_ANZAHL-1,palette); //Farben wieder zurück faden

return;

}

Dieses Demo füllt den Bildschirm mit zufälligen Farben und fadet den Bildschirm dann ab. Nach einer halben Sekunde wird der Bildschirm wieder zurückgefadet und das Programm wird beendet.