NHollmann

English | German

Hi, ich bin Nicolas Hollmann und das ist meine Seite und Blog.
Viel Spaß beim Lesen! 📖

Bildformate schreiben: Portable Anymap

September 22, 20196 Min. Lesedauer
Tags:

Bildformate schreiben: Portable Anymap

Image Output

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

Bitmap Beispiel

Graymap

P2
3 3
255
  0 100 200
 50 150 250
 75 175 255

Graymap Beispiel

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

Pixmap Beispiel

Vollständiger Quellcode

Du kannst den vollständigen Quellcode in C und C++ auf GitHub finden.

Format Spezifikationen (Englisch)

  • Portable Bitmap: PBM
  • Portable Graymap: PGM
  • Portable Pixmap: PPM


© 2021, Nicolas Hollmann