TEI2-Aufgabenblatt4/Aufgabe1_2.cpp

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;
}