Das PCX Graphikformat
Mit Windows und einigen Maustreibern von Mircrosoft wird das Programm Paintbrush ausgeliefert. Dieses Graphikprogramm verwendet ein eigens von der Firma ZSoft entwickeltes Bildformat, das sogenannte PCX-Format. Da Paintbrush weit verbreitet ist, hat sich das PCX-Format in Bereich der pixelorientierten Graphiken als Standard etabliert und wird von einem Großteil aller Graphik – Programme unterstützt.
So wie sich unsere Computerwelt immer mehr verbessert hat, so hat sich auch das PCX-Format immer weiter entwickelt. Es entstanden verschiedene Versionen des PCX-Formats, die Unterschiede beschränken sich aber im wesentlichen nur auf die Farbpalette.
Der PCX Header
Eine PCX Datei besteht - unabhängig von der Version - aus einem 128 Byte langen Header, dem dann die Bilddaten folgen.
Folgende Tabelle gibt den Aufbau des Headers wieder:
Offset |
Bytes |
Bemerkungen |
0 |
1 |
Identifikationsbyte: 0Ah = PCX File |
1 |
1 |
PCX Version: 0 = Version 2.5 2 = Version 2.8 mit Paletten - Information 3 = Version 2.8 ohne Palette - Information 4 = Windows ohne Paletten – Information 5 = Version 3.0 |
2 |
1 |
Flag für komprimierte PCX Files 0 = unkodiert, 1=RLE Kodierung |
3 |
1 |
Bits per Pixel (bzw. per Plane), Farbtiefe |
4 |
8 |
Koordinaten des Originalbildes als Worte (Integer) XMIN, YMIN, XMAX, YMAX |
12 |
2 |
Horizontale Auflösung in dpi (dots per inch) |
14 |
2 |
Vertikale Auflösung in dpi |
16 |
48 |
Color Map mit der Definition der Farbpalette. Organisiert als 3*16 Byte Feld |
64 |
1 |
Reserviert |
65 |
1 |
Zahl der Farbebenen (maximal 4) |
66 |
2 |
Bytes pro Bildzeile(gerade Zahl) |
68 |
2 |
Palette Information: 1=Color , 2=Graustufen |
74 |
58 |
Leerbytes zum Auffüllen des Headers |
Die Zahlen in den Spalten Offset und Bytes sind in dezimaler Schreibweise angegeben.
Das erste Byte dient zur Identifikation, ob es sich überhaupt um ein PXC-File handelt. Wenn dieses Byte nicht dem Wert 0Ah entspricht, handelt es sich nicht um ein PCX-File.
Das nächste Byte gibt die Versionsnummer an, normalerweise sind aber nur PCX-Files mit der Version 3.0 von Interesse.
Das dritte Byte des Headers ist ein Flag, das angibt, ob die Bilddaten komprimiert sind. Eine 1 bedeutet, daß eine Komprimierung vorliegt, bei einer 0 sind die Daten nicht komprimiert (was aber nur sehr selten vorkommt).
Ab Offset 04H beginnt eine Tabelle, bestehend aus vier Worten, die die Dimensionen des Bildfensters in Pixeln angeben. Die Koordinaten werden nach folgender Reihenfolge abgelegt: XMIN, YMIN, XMAX, YMAX.
Frame 175Folglich läßt sich die Größe des Bildes in Pixeln folgendermaßen berechnen:
X = XMAX – XMIN + 1
Y = YMAX – YMIN + 1
Die Auflösung in dpi ist nur mit Vorsicht zu genießen und im allgemeinen nicht verwendbar.
Die Farbpalette wird in verschiedenen Varianten gespeichert. Bei maximal 16 Farben ist die Palette im Header ab Offset 16 gespeichert. Bei 256 Farben wird die Palette an die Bilddaten angehängt, d.h. sie steht am Ende des PCX-Files. Da Paletten mit 16 Farben heutzutage schon ziemlich mickrig und zusätzlich abhängig von der jeweiligen Graphikkarte sind, werde ich mich nur auf Paletten mit 256 Farben beschränken.
Die Farbdaten sind hier als Tabelle der einzelnen Rot-, Grün-, und Blauanteile (3 Byte pro Farbe!) kodiert. (siehe Abschnitt Palettenprogrammierung)
Ab Offset 66 enthält der Header ein Wort mit der Zahl der Byte pro Bildzeile. Die Zahl bezieht sich auf eine unkomprimierte Zeile, muß aber nicht mit den Pixeln pro Zeile übereinstimmen, da die Bilddaten immer wortweise gespeichert werden.
Am Schluß des Headers finden sich 58 Leerbytes, um die Länge von 128 Byte zu erreichen.
Die Bilddaten
Die Daten werden einfach Zeile für Zeile nacheinander in das File geschrieben. Dabei liegen die Daten aber meist komprimiert vor, da so sonst zu viel Platz benötigt würde. Es wird das sogenannte RLE oder “Run Length Encoding” – Verfahren verwendet. Das Verfahren ist sehr schnell und unkompliziert und hat andererseits eine gute Komprimierungsrate.
Beim RLE Verfahren versucht man einfach, gleiche Pixeln, die nacheinander angeordnet sind, zusammenzufassen und deren Wiederholrate anzugeben.
Man hat folgendes festgelegt:
Sind die beiden oberen Bits eines Bytes nicht gesetzt (0), dann liegt ein unkomprimiertes Datenbyte vor. Das Byte kann unverändert verwendet werden.
Sind die beiden oberen Bits eines Bytes gesetzt (1), dann liegt eine komprimierte Information vor. Die Bits 0 bis 5 sind dann als Zähler zu interpretieren, die den Wiederholungsfaktor für das folgenden Byte angeben. Das folgende Byte definiert die Farbe.
Einige Beispiele, um das Verfahren zu verdeutlichen:
C3 55 55 55 55
07 07
05 05
C1 F2 F2
Bei der Ausgabe auf den Bildschirm muß darauf geachtet werden, daß lediglich der Bereich XMAX – XMIN ausgegeben wird, da die Leerbits unterdrückt werden müssen.
Umsetzung in Praxis
Hier möchte ich nun eine Routine vorstellen, die ein PCX-File einliest und entpackt.
Der Funktion wird dabei der Dateiname und zwei Pointer auf Speicherbereiche mitgegeben, die den Header und die Palette aufnehmen sollen. Es wird ein Pointer auf die entpackten Bilddaten zurückgegeben, welcher aber wieder frei gegeben werden muß (siehe Beispiel)!
Sollte die Funktion nicht erfolgreich ausgeführt werden können, wird NULL zurückgegeben.
Die Funtkion ist nur für 256 Farben, Version 3–PCX-Files geschrieben und kann (je nach Compiler) nur für Bilder mit einer maximalen Auflösung von 320*200 verwendet werden, da bei größeren Bildern die 64-kByt-eGrenze gesprengt wird und Probleme mit der Speicherverwaltung auftreten.
/********************************************************************/
/* Basic Types. Need these to avoid differences in compilers */
/* (And C is supposed to be a portable language!) */
/********************************************************************/
typedef signed long int SLONG; //32 bit signed.
typedef unsigned long int ULONG; //32 bit unsigned.
typedef signed int SWORD; //16 bit signed.
typedef unsigned int UWORD; //16 bit unsigned.
typedef signed char BYTE; //8 bit signed.
typedef unsigned char UBYTE; //8 bit unsigned.
#define TRUE 1
#define FALSE 0
/********************************************************************/
/* Pcx Header Struktur */
/********************************************************************/
typedef struct pcx_header
{
UBYTE ID;
UBYTE version;
UBYTE encoding;
UBYTE bits_per_pixel;
UWORD xmin,ymin;
UWORD xmax,ymax;
UWORD hres;
UWORD vres;
UBYTE palette16[48];
UBYTE reserved;
UBYTE color_planes;
UWORD bytes_per_line;
UWORD palette_type;
UBYTE filler[58];
}pcx_header;
#define PCX_BYTEMODE 0 //nächstes Byte bearbeiten
#define PCX_RUNMODE 1 //Wiederholschleife
#define PCX_BUFLEN 4*1024 //Bufferlänge beim Einlesen
/********************************************************************/
/********************************************************************/
/************ Funktion V_loadpcx *************/
/********************************************************************/
/********************************************************************/
UBYTE * V_loadpcx(UBYTE *filename, struct pcx_header * ph,UBYTE * palette)
{
SWORD handle; //Dateihandle
register UBYTE outbyte,bytecount; //Dekomprimierung
register UBYTE *buffer; //Pointer für Buffer
SLONG i; //Zähler
SLONG length; //Anzahl der entpackten Bytes
UBYTE *addr,*startaddr; //Laufpointer und Startadresse
SWORD mode=PCX_BYTEMODE; //Aktueller Modus
SWORD readlen=0, bufptr=0; //Buffer - Management
if(ph==NULL)return NULL;
/************ Datei öffnen ************/
if ((handle = open(filename, O_RDWR|O_BINARY,S_IWRITE)) == -1)
return NULL; //Fehler beim öffnen
/************ Header einlesen ************/
read(handle,&ph,sizeof(struct pcx_header));
/************ Header auswerten ************/
if((ph->ID!=10)||(ph->version=!5)||(ph->bits_per_pixel!=8))
{
close(handle); //Ungültige Daten gefunden
return NULL;
}
/******* Anzahl der entpackten Bytes *********/
length=
((long int)ph->xmax-ph->xmin+1)*((long int)ph->ymax-ph->ymin+1);
/******* Buffer und Speicher für Bild reservieren *******/
if((buffer=(UBYTE*)malloc(V_PCX_BUFLEN))==NULL)return NULL;
if((addr=(UBYTE*)malloc(length))==NULL)
{
free(buffer);
close(handle);
return NULL;
}
startaddr=addr;
/***********************************************************/
/******* Daten entpacken *******/
/***********************************************************/
for (i=0; i<length; i++)
{
/******* nächstes Byte bearbeiten *******/
if (mode==PCX_BYTEMODE)
{
/******* Buffer wieder auffüllen? *******/
if (bufptr>=readlen)
{
bufptr=0;
if ((readlen=read(handle,buffer,PCX_BUFLEN))==0)
break;
}
/******* nächstes Byte holen *******/
outbyte=buffer[bufptr++];
/******* gepackt? *******/
if (outbyte>0xbf)
{
/******* Wiederholungszähler berechnen *******/
bytecount = (int)((int)outbyte & 0x3f);
/******* Buffer wieder auffüllen? *******/
if (bufptr>=readlen)
{
bufptr=0;
if ((readlen=read(handle,buffer,PCX_BUFLEN))==0)
break;
}
/******* Farbwert holen *******/
outbyte=buffer[bufptr++];
if (--bytecount>0)
mode=PCX_RUNMODE;
}
}
/******* Wiederholungszähler ablaufen lassen *******/
else
if (--bytecount==0) mode=PCX_BYTEMODE;
/******* Farbwert kopieren *******/
*addr++=outbyte;
}
/******* Palette laden *******/
if(palette!=NULL)
{
/******* Dateizeiger stellen, einlesen und korrigieren *******/
lseek(handle,-768L,SEEK_END);
read(handle,palette,3*256);
for (i=0;i<256*3;i++)
palette[i]=palette[i]>>2; //korrigieren
}
close(handle);
free(buffer);
return startaddr;
}
/******************************************************************/
/******************************************************************/
/************ Hauptprogramm *************/
/******************************************************************/
/******************************************************************/
void main()
{
struct pcxheader ph;
UBYTE palette[3*256];
UBYTE * picture;
picture=V_loadpcx("Gonzo.pcx",&ph,palette);
....
free(picture); //Wichtig!!!
}
Wie man die Bilddaten auf den Bildschirm bekommt, zeigt der nächste Abschnitt.