PPM-Datei in C++ lesen

Abdul Mateen 15 Februar 2024
  1. PPM-Datei
  2. PPM-Datei in C++ lesen
PPM-Datei in C++ lesen

In diesem Tutorial lernen wir PPM-Dateien kennen und lesen sie mit C++.

Wir werden zuerst das PPM-Dateiformat besprechen und verstehen. Später lernen wir die schrittweise Vorgehensweise zum Lesen einer PPM-Datei in C++ kennen.

PPM-Datei

Jede Bilddatei hat einen Header/Metadaten, die an die Bilddaten angehängt sind. Der Header enthält verschiedene Informationen über das Bild und die Datei.

Am Anfang jeder Bilddatei steht eine Kennung des Dateiformats. Zum Beispiel wird BM am Anfang jeder BMP-Datei geschrieben.

Der Rest des Headers enthält Informationen wie Breite, Höhe, Anzahl der Farben, Bildtyp (Binär, Graustufen, 16 Farben, 256 Farben, 24-Bit-Farben), Komprimierung, Offset usw.

Das Format Portable Pixel Map (PPM) ist ein farbiges Bild mit einem relativ einfachen Format. Es gibt zwei Varianten von PPM: Die eine ist P3 und die andere P6.

P3 ist das ASCII-Format, während P6 ein Binärformat ist; Das ASCII-Format benötigt normalerweise mehr Platz als das Binärformat. Die meisten Bilder enthalten viele Daten; Daher ist der Platz entscheidend.

Ein kleines Bild von 100 x 100 hat beispielsweise 10.000 Pixel. Bei einem farbigen 24-Bit-Bild benötigt jedes Pixel 3 Bytes; daher werden 30000 Byte benötigt, um das Bild zu speichern.

In diesem Tutorial besprechen wir das P6-Format. P3 ist relativ einfacher zu lesen und zu verstehen; es wird Ihnen jedoch leichter fallen, nachdem Sie das P6-Format gelernt haben.

P6 ist ein farbiges 24-Bit-Bild. Der Header von P6 ist relativ unkomplizierter; Sie können es mit dem BMP-Format vergleichen.

Das PPM-Dateiformat hat drei Kopfzeilen, die 15 Byte Platz beanspruchen. Die erste Zeile enthält eine Kennung/Signatur des PPM (entweder P3 oder P6).

Die zweite Zeile enthält Informationen zur Breite und zum Abstand des Bildes (durch Leerzeichen getrennt), und die dritte Zeile enthält Informationen zum maximalen Farbwert (z. B. 15 oder 255).

Betrachten Sie zum Beispiel das folgende Bild:

Hausbild-PPM-Datei

Schauen wir uns die Metainformationen des Hausbildes an (Sie glauben vielleicht nicht, dass Daten zu diesem Bild gehören).

P6
111 132
255
U`6Xe8Xk8Ul9Tg:Ve<Wd7Wd5Td5N_0MY/NZ3P^5Ub5Wc4T`2R`4T`4[d6Yd7NY1CM,@J-FQ/O\2Vg8Ra5FW.?O+@M,BQ/:F-0:**5)*1(6@-CQ5=H1;H-

Wie bereits erwähnt, enthalten die ersten drei Zeilen Header-Informationen. P6 in der ersten Zeile kennzeichnet den Bildtyp.

Die Programmierer können die Kennung prüfen und das Bild gemäß dem Format lesen.

Die zweite Zeile beschreibt die Breite als 111 und die Höhe als 132. In der dritten Zeile steht 255 als maximaler Farbwert.

Ab der vierten Zeile haben wir die Bilddaten im Binärformat gespeichert (auf dem Bildschirm in Form von Zeichen dargestellt, von denen einige erkennbar sind, aber nicht alle).

PPM-Datei in C++ lesen

Um eine PPM-Datei zu lesen, müssen wir zunächst ihren Header lesen, um Informationen über Breite und Höhe zu erhalten. Dies ist wichtig, da wir den dynamischen Speicher entsprechend deklarieren und die Daten des Bildes lesen müssen.

P6 ist ein Binärformat; Daher müssen wir das Bild im Binärformat öffnen und entsprechend lesen:

FILE *read;
read = fopen("west_1.ppm", "rb");

In der ersten Zeile wird der Pointer FILE deklariert, um einen Handler der Datei in der zweiten Zeile zu speichern. Beachten Sie, dass der zweite Parameter in der Funktion fopen rb ist, wobei r für das Lesen und b für binär (d. h. binärer Lesemodus für die Datei) steht.

Wenn Sie mit Binärspeicherung nicht vertraut sind, lesen Sie diesen Artikel über Binärdateien. Nachdem Sie die Datei geöffnet haben, müssen Sie den Header der Datei lesen.

Hier ist der Code dafür:

unsigned char header[15];
fread(header, 15, 1, read);

Mehr zu fread finden Sie hier.

Sobald der Header erfolgreich gelesen wurde, müssen Sie als Nächstes die Breiten- und Höheninformationen extrahieren, um fortzufahren. Da diese Informationen in Zeichenform gespeichert sind, können wir die Zeicheninformationen einzeln lesen und mithilfe des folgenden Codes in einen ganzzahligen Wert umwandeln (Sie können alternativ die Funktion atoi verwenden):

int x = 0;
for (pos = 3; header[pos] != '\n' && header[pos] != ' '; pos++)
  x = x * 10 + (header[pos] - '0');

Wie bereits erwähnt, enthalten die ersten beiden Bytes des Headers einen P6-Wert, und das dritte ist das Trennzeichen für die nächste Zeile. Daher beginnen wir mit dem 4. Element, das mit Index 3 beginnt.

Wir lesen Zeichen für Zeichen, um einen ganzzahligen Wert zu vervollständigen (getrennt durch ein Leerzeichen oder Zeilenvorschubzeichen). Für jedes Zeichen müssen wir den ASCII-Wert 0 subtrahieren, da der ASCII-Wert von Ziffern bei 48 oder 32 hex beginnt (ASCII-Wert von 0).

Beispielsweise ist der ASCII-Wert von 6 dezimal 54; Wenn wir also 48 von 54 subtrahieren, erhalten wir 6. Mit dieser Tatsache können wir einen ganzzahligen Wert aus einem Zeichenarray erhalten.

Sehen Sie sich ein kleines Beispiel an, um ein Zeichen-Array 135 in ein Integer-Äquivalent umzuwandeln.

integer x = 0                             // anfänglicher ganzzahliger Wert
    x = x* 10 + 1 = 1 x = x* 10 + 3 = 13  // finde 10er heraus durch
    x = x* 10 + 5 = 135  // finde 100er heraus und x wird den endgültigen
                         // ganzzahligen Wert haben

Nachdem wir die Breiten- und Höheninformationen erhalten haben, deklarieren wir ein dynamisches Array der Größe Breite x Höhe x 3, da das Bild Pixel Breite x Höhe enthält und jedes Bild 3 Bytes hat (rot, grün und blau, bekannt als RGB).

unsigned char *image;
image = new unsigned char[width * height * 3];

Wir verwenden unsigned char anstelle von char, da beim einfachen char-Typ das erste Bit als Vorzeichen verwendet wird (0 für eine positive Zahl und 1 für eine negative Zahl).

Im Fall von char ist der maximale Bereich positiver Werte 0 bis 127, während wir Informationen von 0 bis 255 lesen müssen. Daher benötigen wir ein Zeichen ohne Vorzeichen.

Der letzte Schritt besteht darin, das Bild zu lesen, was ziemlich einfach ist. Rufen Sie einfach die Funktion fread auf als:

fread(image, size, 1, file);  // where size is width x height x 3

Auch hier ist der erste Parameter ein Char-Array zum Speichern von Bilddaten, und die Größe ist bereits gut erklärt.

Die Variable Bild enthält alle Pixel – rote, grüne und blaue Werte. Wir können drei separate Arrays der Größe Breite x Höhe deklarieren, oder wir können Operationen handhaben, indem wir uns daran erinnern, dass der erste Wert rot, der zweite grün und der dritte blau ist.

Wenn wir zum Beispiel den roten Anteil aus dem Bild entfernen wollen, müssen wir im ersten Index aller Pixel 0 zuweisen.

Das können wir wie folgt machen:

for (i = 0; i < size; i = i + 3) {
  image[i] = 0;

Bitte beachten Sie den Inkrementschritt in der for-Schleife, wo wir i mit 3 statt mit 1 erhöht haben. Dies liegt daran, dass der zweite und der dritte Wert für Grün und Blau gelten und wir die Grün- und Blauwerte nicht ändern möchten.

Sehen Sie sich das Beispielbild unten an, um den Unterschied nach dem Entfernen einer roten Komponente aus dem Bild zu sehen. Wir haben das Originalbild auf der linken Seite und das modifizierte Bild ohne die rote Komponente auf der rechten Seite.

Original VS ohne Rotanteil

Wir hoffen, dass Sie die ganze Idee über das Lesen von PPM-Dateien verstanden haben. Betrachten wir zum Schluss ein vollständiges C++-Programm zum Lesen, Ändern und Schreiben einer PPM-Datei:

#include <cstdio>
#include <iostream>

using namespace std;

void readPPMHeader(FILE *file, unsigned char *header) {
  fread(header, 15, 1, file);
}
void readPPMImage(FILE *file, unsigned char *image, int size) {
  fread(image, size, 1, file);
}
void writePPM(FILE *file, unsigned char *header, unsigned char *image,
              int size) {
  fwrite(header, 15, 1, file);   // writing header information
  fwrite(image, size, 1, file);  // writing image information
}
void removeRed(unsigned char *image, unsigned char *withoutredimage, int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutredimage[i] = 0;  // red component is set to 0
    withoutredimage[i + 1] = image[i + 1];
    withoutredimage[i + 2] = image[i + 2];
  }
}
void removeGreen(unsigned char *image, unsigned char *withoutgreenimage,
                 int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutgreenimage[i] = image[i];
    withoutgreenimage[i + 1] = 0;  // green component is set to 0
    withoutgreenimage[i + 2] = image[i + 2];
  }
}
void removeBlue(unsigned char *image, unsigned char *withoutblueimage,
                int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutblueimage[i] = image[i];
    withoutblueimage[i + 1] = image[i + 1];
    withoutblueimage[i + 2] = 0;  // blue component is set to 0
  }
}
// To extract width & height from header
int getDimension(unsigned char *header, int &pos) {
  int dim = 0;
  for (; header[pos] != '\n' && header[pos] != ' '; pos++)
    dim = dim * 10 + (header[pos] - '0');
  return dim;
}
int main() {
  FILE *read, *write1, *write2, *write3;
  read = fopen("west_1.ppm", "rb");
  unsigned char header[15], *image;
  readPPMHeader(read, header);
  if (header[0] != 'P' || header[1] != '6') {
    cout << "Wrong file format\n";
    return 0;
  }
  write1 = fopen("west_1_without_red.ppm", "wb");
  write2 = fopen("west_1_without_green.ppm", "wb");
  write3 = fopen("west_1_without_blue.ppm", "wb");
  int width, height, clrs, pos = 3;
  width = getDimension(header, pos);
  pos++;
  height = getDimension(header, pos);
  cout << "Width:" << width << "\tHeight:" << height << '\n';
  image = new unsigned char[width * height * 3];
  unsigned char *withoutredimage, *withoutgreenimage, *withoutblueimage;
  withoutredimage = new unsigned char[width * height * 3];
  withoutgreenimage = new unsigned char[width * height * 3];
  withoutblueimage = new unsigned char[width * height * 3];
  readPPMImage(read, image, width * height * 3);
  removeRed(image, withoutredimage, width * height * 3);
  writePPM(write1, header, withoutredimage, width * height * 3);
  removeGreen(image, withoutgreenimage, width * height * 3);
  writePPM(write2, header, withoutgreenimage, width * height * 3);
  removeBlue(image, withoutblueimage, width * height * 3);
  writePPM(write3, header, withoutblueimage, width * height * 3);
  fclose(read);
  fclose(write1);
  fclose(write2);
  fclose(write3);
  return 0;
}

In diesem Programm haben wir eine PPM-Datei gelesen und nach der Änderung drei PPM-Dateien geschrieben.

Schauen wir uns die Ausgabebilder an:

Ohne Rot VS Ohne Grün VS Ohne Blau

Die rote Farbe wird im Bild ganz links entfernt. Der mittlere ist ohne Grün und der dritte hat keinen Blauanteil.