removed folder structure
This commit is contained in:
parent
ffb486b0b9
commit
ab40eeb4e2
8
Makefile
8
Makefile
|
@ -5,12 +5,8 @@ all: bin/imageviewer bin/smooth bin/edgedetect
|
||||||
|
|
||||||
rebuild: clean all
|
rebuild: clean all
|
||||||
|
|
||||||
imageviewer: bin/imageviewer
|
%.o: %.cpp bild.hpp
|
||||||
smooth: bin/smooth
|
$(CC) -c $< -o $@ $(CCFLAGS)
|
||||||
edgedetect: bin/edgedetect
|
|
||||||
|
|
||||||
%.o: src/%.cpp include/bild.hpp
|
|
||||||
$(CC) -Iinclude -c $< -o $@ $(CCFLAGS)
|
|
||||||
|
|
||||||
bin/%: %.o bild.o
|
bin/%: %.o bild.o
|
||||||
@mkdir -p $(@D)
|
@mkdir -p $(@D)
|
||||||
|
|
183
bild.cpp
Normal file
183
bild.cpp
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
#include "bild.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
const std::string Bild::asciiBrightnessMap = ".'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$";
|
||||||
|
|
||||||
|
Bild::Bild(uint32_t width, uint32_t height, const std::string& description) :
|
||||||
|
width(width), height(height), description(description)
|
||||||
|
{
|
||||||
|
pixels.resize(width * height);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 the current line would become longer than 70 characters, write the line to
|
||||||
|
// the file, insert a newline and clear the line
|
||||||
|
if(line.size() + std::to_string(value).length() > 70)
|
||||||
|
{
|
||||||
|
file << line << std::endl;
|
||||||
|
line.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
line += std::to_string(value) + " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write remaining lines to file
|
||||||
|
file << line << std::endl;
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, const Bild& image)
|
||||||
|
{
|
||||||
|
// Loop over pixels in image
|
||||||
|
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 * (Bild::asciiBrightnessMap.length() - 1) / 255);
|
||||||
|
lineBuffer << Bild::asciiBrightnessMap[asciiMapIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << lineBuffer.str() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bild Bild::Geglaettet() const
|
||||||
|
{
|
||||||
|
// Create new image with same dimensions
|
||||||
|
Bild smoothedImage(width, height, description + " smoothed");
|
||||||
|
|
||||||
|
// 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(width, height, description + " edged");
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
102
bild.hpp
Normal file
102
bild.hpp
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
#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:
|
||||||
|
// ASCII characters used when printing the image to stdout
|
||||||
|
static const std::string asciiBrightnessMap;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Creates a new empty picture.
|
||||||
|
*
|
||||||
|
* The created picture has no dimensions and contains no pixel data.
|
||||||
|
*/
|
||||||
|
Bild() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Construct a new empty picture with meta data
|
||||||
|
*
|
||||||
|
* The pixel buffer will be empty, but the meta data (width, height, description)
|
||||||
|
* will be set, and the pixel buffer will be resized to match the dimensions
|
||||||
|
*
|
||||||
|
* @param width Width of the image
|
||||||
|
* @param height Height of the image
|
||||||
|
* @param description Description of the image
|
||||||
|
*/
|
||||||
|
Bild(uint32_t width, uint32_t height, const std::string& description);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy an image
|
||||||
|
*
|
||||||
|
* The copy will copy the image data from the original image to a new buffer
|
||||||
|
*
|
||||||
|
* @param other The image to copy from
|
||||||
|
*/
|
||||||
|
Bild(const Bild& other) = 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 wollte 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;
|
||||||
|
};
|
48
edgedetect.cpp
Normal file
48
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
imageviewer.cpp
Normal file
42
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
smooth.cpp
Normal file
53
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