232 lines
9.3 KiB
C++
232 lines
9.3 KiB
C++
|
#include <iostream> // std::cout
|
||
|
#include <fstream> // std::ifstream/ofstream
|
||
|
|
||
|
// Nutzen aller std Methoden Deklaration, damit nicht überall "std::" davor stehen muss
|
||
|
using namespace std;
|
||
|
|
||
|
// Klasse für Portable Pixel Map (PPM) Bilder
|
||
|
class PPM {
|
||
|
private:
|
||
|
int width; // Attribut Breite
|
||
|
int height; // Attribut Höhe
|
||
|
int maxValue; // Maximaler Wertebereich der Farben
|
||
|
int* pixel; // char Pointer für alle rgb Werte der Pixel
|
||
|
|
||
|
public:
|
||
|
// Konstruktor zum initialisieren
|
||
|
PPM(){
|
||
|
this->width = 0;
|
||
|
this->height = 0;
|
||
|
this->maxValue = 0;
|
||
|
this->pixel = nullptr;
|
||
|
}
|
||
|
|
||
|
// Dekonstruktor zur Speicherfreigabe von pixel Pointer
|
||
|
~PPM() {
|
||
|
if (pixel)
|
||
|
delete[] pixel;
|
||
|
}
|
||
|
|
||
|
// Methode zum Einlesen der PPM Datei returned true, wenn einlesen erfolgreich
|
||
|
// String const aus std
|
||
|
bool readPPM(const string& filename) {
|
||
|
|
||
|
// Inputfile Stream von filename, ios::binary zum, damit \n geskipped wird
|
||
|
ifstream file(filename, ios::binary);
|
||
|
|
||
|
// !file gibt true wenn file nicht gefunden werden kann
|
||
|
if (!file.is_open()) {
|
||
|
// Console Error aus std
|
||
|
cerr << "Error: Unable to open file " << filename << endl;
|
||
|
// Einlesen nicht erfolgreich -> returned false
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// magicNumber char Array deklarieren
|
||
|
char magicNumber[3];
|
||
|
// std::istream::read um aus file die ersten 2 chars auszulesen
|
||
|
file.read(magicNumber, 2);
|
||
|
// Ende der magic Number deklarieren
|
||
|
magicNumber[2] = '\0';
|
||
|
// Vergleiche magicNumber mit "P3" wenn sie gleich sind returned strcmp = 0
|
||
|
if (strcmp(magicNumber, "P3") != 0) {
|
||
|
// std::console error, falls falsches magic Number Format verwendet wurde
|
||
|
cerr << "Error: Not a valid PPM file" << endl;
|
||
|
// Falsches Magic Number Format -> returned false
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// von file zuerst die nächsten Zahlen bis nächstes Leerzeichen in width speichern und dann in height
|
||
|
file >> width >> height;
|
||
|
|
||
|
// von File maximaler Wertebereich der Farben in maxValue speichern
|
||
|
file >> maxValue;
|
||
|
|
||
|
// Speicherreservierung für die Pixeldaten
|
||
|
pixel = new int[width * height * 3];
|
||
|
|
||
|
// Alle Pixeldaten bestehend aus rgb in data[] speichern mit width * height * 3 für alle Wertes
|
||
|
for (int i = 0; i < width * height * 3; ++i) {
|
||
|
// file speichert in data[i]
|
||
|
file >> pixel[i];
|
||
|
}
|
||
|
|
||
|
// Schließen des inputfile Streams
|
||
|
file.close();
|
||
|
// returned true, weil PPM erfolreich eingelesen
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Methode zum Speichern einer PPM Datei returned true wenn erfolgreich
|
||
|
// std::string zur Übergabe von output filename
|
||
|
bool savePPM(const string& filename) {
|
||
|
// Outputfile Stream um file zu schreiben mit Name filename in selben Verzeichniss wie cpp Datei
|
||
|
ofstream file(filename);
|
||
|
// Wenn Outputfile Datei nicht erstellt werden / geöffnet werden kann
|
||
|
if (!file.is_open()) {
|
||
|
// std::console error, dass file nicht erstellt / bearbeitet werden kann
|
||
|
cerr << "Error: Unable to open file " << filename << " for writing" << endl;
|
||
|
// file kann nicht bearbeitet werden -> returned false
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// In file zuerst die Magic Number "P3" schreiben, dann Zeilenumbruch
|
||
|
file << "P3" << endl;
|
||
|
// In file width Attribut schreiben, dann ein Leerzeichen und dann height Attribut, anschließend Zeilenumbruch
|
||
|
file << width << " " << height << endl;
|
||
|
// In file maximalen Wertebereich maxValue Attribut schreiben, dann Zeilenumbruch
|
||
|
file << maxValue << endl;
|
||
|
|
||
|
// Schreibe die Pixelwerte mit insgesamt width * height * 3 Werten für alle rgb
|
||
|
for (int i = 0; i < width * height * 3; ++i) {
|
||
|
// In file pixel[i] (einen r oder g oder b Wert) schreiben, dann Leerzeichen und anschließend der Nächste
|
||
|
file << pixel[i] << " ";
|
||
|
// Nach jeder Zeile der Pixel Zeilenumbruch einfügen mit std::endl
|
||
|
if ((i + 1) % (width * 3) == 0)
|
||
|
file << endl;
|
||
|
}
|
||
|
|
||
|
// Outputfile Stream schließen
|
||
|
file.close();
|
||
|
// Erfolgreich PPM gespeichert -> returned true
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Getter für die Breite des Bildes
|
||
|
int getWidth() const {
|
||
|
return width;
|
||
|
}
|
||
|
|
||
|
// Getter für die Höhe des Bildes
|
||
|
int getHeight() const {
|
||
|
return height;
|
||
|
}
|
||
|
|
||
|
// Getter für den maximalen Wertebereich des Bildes
|
||
|
int getMaxValue() const {
|
||
|
return maxValue;
|
||
|
}
|
||
|
|
||
|
// Getter für die Pixeldaten des Bildes
|
||
|
int* getPixels() const {
|
||
|
return pixel;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Klasse für beide Bildverarbeitungsschritte
|
||
|
class ImageProcessing {
|
||
|
private:
|
||
|
// Default Konstruktor private setzen, damit ImageProcessing nicht instanziiert werden kann
|
||
|
ImageProcessing(){
|
||
|
}
|
||
|
public:
|
||
|
// Statische Methode zur Konvertierung in Graustufen mit PPM Objekt Adresse
|
||
|
static void convertToGray(PPM& image) {
|
||
|
// Einlesen aller PPM Attribute mittels public Getter und lokales speichern auf Variablen
|
||
|
int* pixels = image.getPixels();
|
||
|
int width = image.getWidth();
|
||
|
int height = image.getHeight();
|
||
|
|
||
|
// Durch alle rgb Werte durchitterieren mit 3er Schritten um jeweils 3 mal selben Wert in pixels[] zu schreiben,
|
||
|
// damit am Ende die PPM als Schwarz Weiß Bild ausgewertet wird
|
||
|
for (int i = 0; i < width * height * 3; i += 3) {
|
||
|
// int gray deklariert als Graustufe mittels der Graustufen Formel
|
||
|
int gray = (int)(0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2]);
|
||
|
// Die nächsten 3 pixel daten werden jetzt auf diese Graustufe gesetzt, damit am Ende ein schwarz weiß Bild ausgewertet wird
|
||
|
pixels[i] = gray;
|
||
|
pixels[i + 1] = gray;
|
||
|
pixels[i + 2] = gray;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Statische Methode zur Kantenerkennung mittels Faltung
|
||
|
static void edgeDetection(PPM& image) {
|
||
|
// Einlesen aller PPM Attribute mittels public Getter und lokales speichern auf Variablen
|
||
|
int* pixels = image.getPixels();
|
||
|
int width = image.getWidth();
|
||
|
int height = image.getHeight();
|
||
|
int maxValue = image.getMaxValue();
|
||
|
|
||
|
// 3x3 2 dimensionales double Array als Kantenerkennungsfilter deklariert
|
||
|
double filter[3][3] = {
|
||
|
{-1, -1, -1},
|
||
|
{-1, 8, -1},
|
||
|
{-1, -1, -1}
|
||
|
};
|
||
|
|
||
|
// Speicherreservierung für die Kopie des Originalbildes
|
||
|
int* originalPixels = new int[width * height * 3];
|
||
|
// Header bleibt gleich -> nur pixel Daten müssen kopiert werden
|
||
|
// Kopie des Originalbildes für die Berechnung der Kantenerkennung
|
||
|
memcpy(originalPixels, pixels, width * height * 3 * sizeof(int));
|
||
|
|
||
|
|
||
|
// Anwenden des Kantenerkennungsfilters
|
||
|
// Generell x immer mit 3er Vielfachen Schritten, weil für jeden aus rgb alles auf eine Graustufe gelegt wird
|
||
|
// Die ersten 2 for Schleifen zum Itterieren über das Bild per Koordinaten,
|
||
|
// Anfang bei y = 1 und x = 3 und Ende bei y = height - 1 und x = width - 3, damit der 3x3 Filter ganz aufliegen kann
|
||
|
for (int y = 1; y < height - 1; y++) {
|
||
|
for (int x = 3; x < width * 3 - 3; x += 3) {
|
||
|
// int sum Deklaration für die Summe des Filters
|
||
|
int sum = 0;
|
||
|
|
||
|
// Die nächsten 2 for Schleifen zum Itterieren über den 3x3 Filter auf einer Koordinate x|y
|
||
|
// Fängt bei -1 bzw -3 an und endet bei 1 bzw. 3, da die Koordinaten x|y von der Mitte des Filters ausgehen
|
||
|
for (int fy = -1; fy <= 1; ++fy) {
|
||
|
for (int fx = -3; fx <= 3; fx += 3) {
|
||
|
// Der index wird folgendermaßen berechnet, weil es sich um ein 2 dimensionales Bild innerhalb eines
|
||
|
// 1 dimensionalem Datenspeicher handelt: index = (y * width) + x
|
||
|
int index = ((y + fy) * width + (x / 3 + fx / 3)) * 3;
|
||
|
// Zur Summe des Durchgangs vom Filter wird jeder Pixel des Bildes mit dem Filter multipliziert und das Ergebnis hinzugefügt
|
||
|
sum += originalPixels[index] * filter[fy + 1][fx / 3 + 1];
|
||
|
}
|
||
|
}
|
||
|
// Nur Range zwischen 0 und maxValue zulassen -> Alle Minuszahlen aussortieren
|
||
|
sum = min(max(sum, 0), maxValue);
|
||
|
// Die Pixel Daten aus der ursprünglichen PPM aktualisieren mit der aktuellen Filter Summe für den Pixel
|
||
|
// (Alle drei rgb Werte auf den Filter Summen Wert setzen)
|
||
|
pixels[y * width * 3 + x] = sum;
|
||
|
pixels[y * width * 3 + x + 1] = sum;
|
||
|
pixels[y * width * 3 + x + 2] = sum;
|
||
|
|
||
|
|
||
|
}
|
||
|
}
|
||
|
// Speicherfreigabe der Kopie der Pixel Daten
|
||
|
delete[] originalPixels;
|
||
|
|
||
|
}
|
||
|
};
|
||
|
|
||
|
int main() {
|
||
|
PPM image;
|
||
|
if (image.readPPM("obst.ppm")) {
|
||
|
ImageProcessing::convertToGray(image);
|
||
|
ImageProcessing::edgeDetection(image);
|
||
|
image.savePPM("output.ppm");
|
||
|
cout << "Image processing complete. Saved as output.ppm" << endl;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|