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.