Initial Commit
commit
f6ce14a1a7
|
@ -0,0 +1,231 @@
|
|||
#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;
|
||||
}
|
Loading…
Reference in New Issue