initial commit

This commit is contained in:
Lauchmelder 2022-01-17 05:35:18 +01:00
commit 7dceb85240
15 changed files with 330384 additions and 0 deletions

14
COMPILE.txt Normal file
View file

@ -0,0 +1,14 @@
Um lediglich die Bild.cpp zu kompilieren:
clang++ -c src/imageviewer src/Bild.cpp -I include -std=c++14 -Weverything -Wno-c++98-compat
Alle commands sollen im root Verzeichnis des Projekts ausgeführt werden, und es
wird davon ausgegangen dass der Unterordner "build" existiert
Um den ImageViewer zu kompilieren:
clang++ -o build/imageviewer src/Bild.cpp src/ImageViewer.cpp -I include -std=c++14 -Weverything -Wno-c++98-compat
Um das Glättungs Programm zu kompilieren:
clang++ -o build/smooth src/Bild.cpp src/Smooth.cpp -I include -std=c++14 -Weverything -Wno-c++98-compat
Um das Kantenerkennungs Programm zu kompilieren:
clang++ -o build/edetect src/Bild.cpp src/EdgeDetect.cpp -I include -std=c++14 -Weverything -Wno-c++98-compat

16
Makefile Normal file
View file

@ -0,0 +1,16 @@
default: imageviewer smooth edgedetect
imageviewer: folder src/Bild.cpp src/ImageViewer.cpp
clang++ -o build/imageviewer src/Bild.cpp src/ImageViewer.cpp -I include -std=c++14 -Weverything -Wno-c++98-compat
smooth: folder src/Bild.cpp src/Smooth.cpp
clang++ -o build/smooth src/Bild.cpp src/Smooth.cpp -I include -std=c++14 -Weverything -Wno-c++98-compat
edgedetect: folder src/Bild.cpp src/EdgeDetect.cpp
clang++ -o build/edetect src/Bild.cpp src/EdgeDetect.cpp -I include -std=c++14 -Weverything -Wno-c++98-compat
folder:
mkdir -p build
clean:
rm -rf build

28
TEST.txt Normal file
View file

@ -0,0 +1,28 @@
Um das Errorhandling zu testen habe ich zwei Dateien im Verzeichnis res/broken/
bereitgestellt welche fehlerhafte PGM Dateien beinhalten:
* wrong_signature.pgm beinhaltet P5 statt P2.
Führt man z.B. "build/imageviewer res/broken/wrong_signature.pgm" aus, sollte das Programm
"Missing/Wrong PGM magic number." ausgeben und sich beenden.
* wrong_dimensions.pgm gibt eine falsche Größe für das Bild an. Es sind nicht genug Daten vorhanden für die angegebenen Bilddimensionen
Führt man z.B. "build/imageviewer res/broken/wrong_dimensions.pgm" aus, sollte das Programm
"Amount of image data doesn't match image dimensions. Expected..." ausgeben und ich beenden.
Ansonsten können die Programme "build/smooth" und "build/edetect" benutzt werden um jeweils die
Glättungs- und Kantenbildfunktion zu testen. Beide Programme verlangen als erstes Commandline Argument
den Namen der Bilddatei die verändert werden soll.
Ich habe einige Bilder im Ordner res/ bereitgestellt um diese Funktionen zu testen.
build/smooth res/f14.ascii.pgm
build/smooth res/mountain.ascii.pgm
build/smooth res/fractal_tree.ascii.pgm
build/edetect res/f14.ascii.pgm
build/edetect res/mountain.ascii.pgm
build/edetect res/fractal_tree.ascii.pgm
Die modifizierten Bilder werden in den Dateien originalerName_modified.pgm gespeichert, und können anschließend
in GIMP o.Ä. Programmen inspiziert werden

77
include/Bild.hpp Normal file
View file

@ -0,0 +1,77 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
#include <fstream>
/**
* @brief Stores PGM image data
*
* This class can read and write PGM images, perform smoothing and
* edge detection on them and display them in the console as ASCII art
*/
class Bild
{
public:
/**
* @brief Creates a new empty picture.
*
* The created picture has no dimensions and contains no pixel data.
*/
Bild() = default;
/**
* @brief Load an image
*
* Reads a file containing image data and populates the pixel array
*
* @param file (Valid) filestream containing image data
* @param image The image object to store the data in
* @return The modified filestream
*/
friend std::ifstream& operator>>(std::ifstream& file, Bild& image);
/**
* @brief Save an image
*
* Writes the stored image data into a file, overwriting any contents in it
*
* @param file (Valid) filestream to store the image in
* @param image The image object to get the data from
* @return The modified filestream
*/
friend std::ofstream& operator<<(std::ofstream& file, const Bild& image);
/**
* @brief Renders the image to the console
*
* Draws the image in the console using ascii characters.
* Das war zwar in der Aufgabe nicht verlangt aber ich wollt schon gern die Bilder sehen
*
* @param os Output stream to draw to
* @param image Image to draw
* @return The modified output stream
*/
friend std::ostream& operator<<(std::ostream& os, const Bild& image);
/**
* @brief Applies smoothing to an image, effectively blurring it
*
* @return The blurred image
*/
Bild Geglaettet() const;
/**
* @brief Performs edge detection on the image
*
* @return An image with only the edges found in the original
*/
Bild Kantenbild() const;
private:
uint32_t width {};
uint32_t height {};
std::string description;
std::vector<uint8_t> pixels;
};

View file

@ -0,0 +1,11 @@
P2
# feep.pgm
24 17
15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0
0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0
0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

View file

@ -0,0 +1,11 @@
P5
# feep.pgm
24 7
15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0
0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0
0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

18075
res/f14.ascii.pgm Normal file

File diff suppressed because it is too large Load diff

11
res/feep.pgm Normal file
View file

@ -0,0 +1,11 @@
P2
# feep.pgm
24 7
15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0
0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0
0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

181661
res/fractal_tree.ascii.pgm Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

18075
res/mountain.ascii.pgm Normal file

File diff suppressed because it is too large Load diff

181
src/Bild.cpp Normal file
View file

@ -0,0 +1,181 @@
#include "Bild.hpp"
#include <iostream>
#include <sstream>
std::ifstream& operator>>(std::ifstream& file, Bild& image)
{
std::string lineContent;
// First line should contain "P2"
std::getline(file, lineContent);
if(lineContent[0] != 'P' || lineContent[1] != '2')
{
throw std::runtime_error("Missing/Wrong PGM magic number.");
}
// The second line contains an image description
std::getline(file, lineContent);
std::string::iterator it;
for(it = lineContent.begin(); it != lineContent.end(); it++) // Flush out any leading spaces to get the description
{
if(*it != ' ' && *it != '#') // Break from loop if the iterator reached a character that isnt '#' or ' '
break;
}
image.description = std::string(it, lineContent.end() - 1); // Store the description (and cut off the newline)
// The third line contains the image dimensions
file >> image.width >> image.height;
std::getline(file, lineContent);
// The fourth line contains the max brightness value
std::getline(file, lineContent); // Do nothing with it
// The remaining file contains image data
uint16_t pixelValue = 0;
while(std::getline(file, lineContent)) // Loop through remaining lines
{
std::stringstream ss(lineContent);
while(ss >> pixelValue) // Loop through numbers in the line
{
image.pixels.push_back(static_cast<uint8_t>(pixelValue)); // Write pixel into the array
}
}
// Check if vector size matches image dimensions
if(image.pixels.size() != static_cast<size_t>(image.width) * image.height)
{
throw std::runtime_error(
"Amount of image data doesn't match image dimensions. Expected " +
std::to_string(image.width * image.height) +
" pixels, got " + std::to_string(image.pixels.size()) +
" pixels instead."
);
}
return file;
}
std::ofstream& operator<<(std::ofstream& file, const Bild& image)
{
file << "P2" << std::endl; // Write magic number to file
file << "# " << image.description << std::endl; // Write image description
file << image.width << " " << image.height << std::endl; // Write image dimensions
file << 255 << std::endl; // Write max brightness value
// Write pixel data
std::string line;
for(uint8_t pixel : image.pixels)
{
uint32_t value = static_cast<uint32_t>(pixel);
if(line.size() + std::to_string(value).length() > 70)
{
file << line << std::endl;
line.clear();
}
line += std::to_string(value) + " ";
}
file << line << std::endl;
return file;
}
std::ostream& operator<<(std::ostream& os, const Bild& image)
{
std::string asciiBrightnessMap(".'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$");
for(uint16_t y = 0; y < image.height; y++)
{
std::stringstream lineBuffer;
for(uint16_t x = 0; x < image.width; x++)
{
uint8_t brightness = image.pixels[y * image.width + x];
// Map the pixel brightness (which is in the interval [0, 255]) to the ascii chart length
uint8_t asciiMapIndex = static_cast<uint8_t>(brightness * asciiBrightnessMap.length() / 255);
lineBuffer << asciiBrightnessMap[asciiMapIndex];
}
std::cout << lineBuffer.str() << std::endl;
}
return os;
}
Bild Bild::Geglaettet() const
{
// Create new image and copy metadata
Bild smoothedImage;
smoothedImage.width = width;
smoothedImage.height = height;
smoothedImage.description = description + " smoothed";
smoothedImage.pixels.resize(pixels.size()); // Resize the pixel array to match the image dimensions
// Apply smoothing
for(uint32_t y = 0; y < smoothedImage.height; y++)
{
for(uint32_t x = 0; x < smoothedImage.width; x++)
{
// If this is a border pixel just copy it from the original
if(y == 0 || x == 0 || y == smoothedImage.height - 1 || x == smoothedImage.width - 1)
{
smoothedImage.pixels[y * smoothedImage.width + x] = pixels[y * smoothedImage.width + x];
continue;
}
// Else, make this pixel the average of its 4 neighbours
uint32_t sumOfPixels =
pixels[(y - 1) * smoothedImage.width + (x + 0)] +
pixels[(y + 1) * smoothedImage.width + (x + 0)] +
pixels[(y + 0) * smoothedImage.width + (x - 1)] +
pixels[(y + 0) * smoothedImage.width + (x + 1)] +
4 * pixels[y * smoothedImage.width + x];
smoothedImage.pixels[y * smoothedImage.width + x] = static_cast<uint8_t>(sumOfPixels / 8);
}
}
return smoothedImage;
}
Bild Bild::Kantenbild() const
{
// Create new image and copy metadata
Bild modifiedImage;
modifiedImage.width = width;
modifiedImage.height = height;
modifiedImage.description = description + " edged";
modifiedImage.pixels.resize(pixels.size()); // Resize the pixel array to match the image dimensions
// Apply edge detection
for(uint32_t y = 0; y < modifiedImage.height; y++)
{
for(uint32_t x = 0; x < modifiedImage.width; x++)
{
// If this is a border pixel set its brightness to 0
if(y == 0 || x == 0 || y == modifiedImage.height - 1 || x == modifiedImage.width - 1)
{
modifiedImage.pixels[y * modifiedImage.width + x] = 0;
continue;
}
// Else, apply the formula to calculate pixel brightness
int32_t sumOfPixels =
pixels[(y - 1) * modifiedImage.width + (x + 0)] +
pixels[(y + 1) * modifiedImage.width + (x + 0)] +
pixels[(y + 0) * modifiedImage.width + (x - 1)] +
pixels[(y + 0) * modifiedImage.width + (x + 1)] -
4 * pixels[y * modifiedImage.width + x];
if(sumOfPixels < 0)
sumOfPixels = 0;
modifiedImage.pixels[y * modifiedImage.width + x] = static_cast<uint8_t>(sumOfPixels / 8);
}
}
return modifiedImage;
}

48
src/EdgeDetect.cpp Normal file
View file

@ -0,0 +1,48 @@
/**
* Reads a PGM file and performs edge detection, then writes the result to disk
*/
#include <iostream>
#include "Bild.hpp"
int main(int argc, char** argv)
{
if(argc < 2)
{
std::cout << "Usage: edetect <pgm file>" << std::endl;
return 0;
}
std::string filename = argv[1];
// Load file
std::ifstream file(filename);
if(!file.good())
{
std::cerr << "Failed to open file" << std::endl;
return -1;
}
// Create image
Bild image;
try
{
file >> image;
}
catch(const std::runtime_error& err)
{
std::cerr << err.what() << std::endl;
file.close();
return -1;
}
file.close();
// Print image
image = image.Kantenbild();
filename.insert(filename.find('.'), "_modified");
std::ofstream outFile(filename);
outFile << image;
outFile.close();
return 0;
}

42
src/ImageViewer.cpp Normal file
View file

@ -0,0 +1,42 @@
/**
* Displays a given PGM image in the console using ASCII characters
*/
#include <iostream>
#include "Bild.hpp"
int main(int argc, char** argv)
{
// If there are no additional arguments print usage info
if(argc < 2)
{
std::cout << "Usage: imageviewer <pgm file>" << std::endl;
return 0;
}
// Load file
std::ifstream file(argv[1]);
if(!file.good())
{
std::cerr << "Failed to open file" << std::endl;
return -1;
}
// Create image
Bild image;
try
{
file >> image;
}
catch(const std::runtime_error& err)
{
std::cerr << err.what() << std::endl;
file.close();
return -1;
}
file.close();
// Print image
std::cout << image << std::endl;
return 0;
}

53
src/Smooth.cpp Normal file
View file

@ -0,0 +1,53 @@
/**
* Reads a PGM image and smooths it by some amount, then writes it back to disk
*/
#include <iostream>
#include "Bild.hpp"
int main(int argc, char** argv)
{
if(argc < 2)
{
std::cout << "Usage: smooth <pgm file>" << std::endl;
return 0;
}
std::string filename = argv[1];
uint16_t smoothing;
std::cout << "Wie oft soll das Bild geglaettet werden? ";
std::cin >> smoothing;
// Load file
std::ifstream file(filename);
if(!file.good())
{
std::cerr << "Failed to open file" << std::endl;
return -1;
}
// Create image
Bild image;
try
{
file >> image;
}
catch(const std::runtime_error& err)
{
std::cerr << err.what() << std::endl;
file.close();
return -1;
}
file.close();
// Print image
for(uint8_t i = 0; i < smoothing; i++)
image = image.Geglaettet();
filename.insert(filename.find('.'), "_modified");
std::ofstream outFile(filename);
outFile << image;
outFile.close();
return 0;
}