#include // std::cout #include // 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 pixel[] speichern mit width * height * 3 für alle Werte 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; }