Bildformate schreiben: Portable Anymap
Meistens sind Bildformate recht komplex und benötigen eine Form der Komprimierung. Aber es gibt auch Ausnahmen und Portable Anymaps ist eine solche. Portable Anymaps ist kein einzieges Format sondern eine Gruppe unterschiedlicher Formate für Bitmaps, Graustufen- und RGB-Bildern.
Es gibt 7 unterschiedliche Formate:
Format | Magic Number | Dateiendung |
---|---|---|
ASCII Bitmap | P1 | .pbm |
ASCII Graymap | P2 | .pgm |
ASCII Pixmap | P3 | .ppm |
Binary Bitmap | P4 | .pbm |
Binary Graymap | P5 | .pgm |
Binary Pixmap | P6 | .ppm |
Das siebte Format ist eine Verallgemeinerung der Binärvarianten und wir betrachten es nicht in diesem Artikel.
ASCII
Das ASCII Format ist wirklich einfach. Tatsächlich ist es so einfach, dass man Bilder in diesem Format aus jeder Programmiersprache schreiben kann, die Text in eine Datei schreiben oder auf die Konsole schreiben kann. Ja, man könnte sogar Brainfuck benutzen um ein Bild zu schreiben.
Alle Portable Anymaps fangen mit einem ASCII Header im folgenden Format an:
MAGIC_NUMBER
WIDTH HEIGHT
MAX_VALUE
Die MAGIC_NUMBER
ist einer der 7 Format-Nummern aus der Tabelle. WIDTH
und HEIGHT
sind einfach die Größe des Bildes in Pixeln. MAX_VALUE
schauen wir uns etwas später an.
Die Felder im Header sind durch Whitespace seperiert. Man könnte also jedes Feld in eine eigene Zeile schreiben, oder aber auch den gesammten Header in einer Zeile unterbringen.
Die richtigen Bilddaten kommen gleich nach dem Header. Sie sind kodiert als menschenlesbare ASCII Zahlen,
die durch Whitespace voneinander getrennt sind. Bitmaps benutzen nur die 0
und 1
Zeichen.
Graustufenbilder (Graymaps) und Farbbilder (Pixmaps) können Zahlen zwischen 0
und MAX_VALUE
verwenden.
Graymaps verwenden eine Zahl pro Pixel während Pixmaps 3 Zahlen pro Pixel benötigen, eine für jeden Kanal.
Die Kanalreihenfolge ist: Rot, Grün, Blau.
Auch wenn es nicht nötig ist, ist es üblich Zeilen im Bild durch ‘Newline’-Zeichen zu trennen und auch kleinere Zahlen mit Leerzeichen auszurichten, sodass die Spalten des Bildes in der Datei untereinander stehen. Schau dir die Beispiele für solche Bilder weiter unten an, um besser zu verstehen was ich meine.
Kommentare
Portable Anymaps können auch Kommentare enthalten. Kommentare fangen mit einem #
Zeichen an und
enden mit einem ‘Carriage Return’ oder einem ‘Newline’, sprich sie gehen bis zum Ende der Zeile.
Tiefe
Jetzt ist es Zeit, das MAX_VALUE
aus dem Header anzuschauen. Es beschreibt, welches der
größtmöglicheste Wert im Bild ist. MAX_VALUE
muss größer als 0 und kleiner als 65536 sein.
In einer Graymap würde ein Pixel mit einem Wert gleich MAX_VALUE
weiß sein. In einer Pixmap
müssten alle 3 Kanäle diesen Wert haben, damit der Pixel weiß erscheint. Der üblichste Wert für
MAX_VALUE
ist 255, da dies der größtmöglichste Wert ist, der in einem einzelnen Byte gespeichert
werden kann.
Da Portable Bitmaps nur 0
und 1
als Werte kennen, gibt es hier kein MAX_VALUE
Headerfeld.
Binär
Während die ASCII Variante von Menschen gelesen werden kann und leicht zu schreiben ist, benötigt sie doch eine menge Speicherplatz. Für einen einziegen Wert benötigen wir mindestens 2 Byte (1 Whitespace + 1 Datenbyte). Wird ein Bild im Binärmodus gespeichert, können wir eine menge Platz sparen, allerdings verlieren wir dadurch die Lesbarkeit.
Um eine Portable Anymap im Binärmodus zu speichern geben wir einfach den Header wie gewohnt in ASCII kodiert aus. Direkt nach dem letzten Whitespace des Headers folgen dann die Bilddaten. Pass auf, das du nur ein einzieges Whitespace-Zeichen nach dem Header ausgiebst, da alles weitere sonst als Bilddaten interpretiert werden würde.
Es gibt eine kleine Sonderbarkeit im Binärmodus: Wenn MAX_VALUE
kleiner als 256 ist,
werden Graymaps mit einem Byte pro Pixel oder ein Byte pro RGB Kanal im Fall von Pixmaps
gespeichert. Wenn MAX_VALUE
aber größer oder gleich 256 ist, werden Werte mit zwei Byte
in Big Endian gespeichert.
ASCII Implementierung
Es gibt nichts wirklich Besonderes an der Implementierung eines portablen Anymap-Exporters. Ich habe mich dazu entschieden ein einfaches Programm in C zu schreiben, dass ein RGB Bild generiert und abspeichert. Es gibt auch eine C++ Version im Vollständigen Quellcode. Der Code sollte auch für Anfänger leicht zu lesen sein.
#include <stdio.h>
#define WIDTH 300
#define HEIGHT 200
int main()
{
FILE *outfile = fopen("out.ppm", "wb");
fprintf(outfile, "P3");
fprintf(outfile, "\n%d %d\n255\n", WIDTH, HEIGHT);
for (int y = 0; y < HEIGHT; y++)
{
for (int x = 0; x < WIDTH; x++)
{
int r = (x / (float)WIDTH) * 255;
int g = (x / (float)WIDTH) * 255;
int b = (y / (float)HEIGHT) * 255;
fprintf(outfile, "%3d %3d %3d ", r, g, b);
}
// Zeilenumbrüche ausgeben, damit das erzeugte Bild etwas einfacher zu lesen ist
fprintf(outfile, "\n");
}
fclose(outfile);
return 0;
}
Binär Implementierung
Diese Implementierung ist nicht wirklich anders als die der ASCII Version. Beachte auch, dass der Header immer noch mit ASCII Codierung abgespeichert wird.
#include <stdio.h>
#define WIDTH 300
#define HEIGHT 200
int main()
{
FILE *outfile = fopen("out.ppm", "wb");
// P6 statt P3 für binär
fprintf(outfile, "P6");
fprintf(outfile, "\n%d %d\n255\n", WIDTH, HEIGHT);
for (int y = 0; y < HEIGHT; y++)
{
for (int x = 0; x < WIDTH; x++)
{
int r = (x / (float)WIDTH) * 255;
int g = (x / (float)WIDTH) * 255;
int b = (y / (float)HEIGHT) * 255;
unsigned char colors[3] = {r, g, b};
fwrite(colors, 3, 1, outfile);
}
}
fclose(outfile);
return 0;
}
Beispiele
Hier sind einige Beispiele für ASCII Anymaps. Alle wurden von Hand geschrieben.
Bitmap
P1
3 3
1 1 1
0 1 0
0 1 0
Graymap
P2
3 3
255
0 100 200
50 150 250
75 175 255
Pixmap
P3
3 3
255
0 0 0 100 100 100 255 255 255
255 0 0 0 255 0 0 0 255
255 255 0 0 255 255 255 0 255
Vollständiger Quellcode
Du kannst den vollständigen Quellcode in C und C++ auf GitHub finden.