initial commit
This commit is contained in:
commit
7dceb85240
14
COMPILE.txt
Normal file
14
COMPILE.txt
Normal 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
16
Makefile
Normal 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
28
TEST.txt
Normal 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
77
include/Bild.hpp
Normal 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;
|
||||
};
|
11
res/broken/wrong_dimensions.pgm
Normal file
11
res/broken/wrong_dimensions.pgm
Normal 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
|
11
res/broken/wrong_signature.pgm
Normal file
11
res/broken/wrong_signature.pgm
Normal 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
18075
res/f14.ascii.pgm
Normal file
File diff suppressed because it is too large
Load diff
11
res/feep.pgm
Normal file
11
res/feep.pgm
Normal 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
181661
res/fractal_tree.ascii.pgm
Normal file
File diff suppressed because it is too large
Load diff
112081
res/fractal_tree_modified.ascii.pgm
Normal file
112081
res/fractal_tree_modified.ascii.pgm
Normal file
File diff suppressed because it is too large
Load diff
18075
res/mountain.ascii.pgm
Normal file
18075
res/mountain.ascii.pgm
Normal file
File diff suppressed because it is too large
Load diff
181
src/Bild.cpp
Normal file
181
src/Bild.cpp
Normal 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
48
src/EdgeDetect.cpp
Normal 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
42
src/ImageViewer.cpp
Normal 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
53
src/Smooth.cpp
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue