Created
March 23, 2022 19:27
-
-
Save VTroyanGolovyan/6fa000e0da4dc3870c9639248d7e0769 to your computer and use it in GitHub Desktop.
Img
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <cstdint> | |
struct BMPFileHeader { | |
uint16_t file_type{0x4D42}; | |
uint32_t file_size{0}; | |
uint16_t reserved1{0}; | |
uint16_t reserved2{0}; | |
uint32_t offset_data{0}; | |
} __attribute ((__packed__)); | |
struct BMPInfoHeader { | |
uint32_t size{0}; | |
int32_t width{0}; | |
int32_t height{0}; | |
uint16_t planes{1}; | |
uint16_t bit_count{0}; | |
uint32_t compression{0}; | |
uint32_t size_image{0}; | |
int32_t x_pixels_per_meter{0}; | |
int32_t y_pixels_per_meter{0}; | |
uint32_t colors_used{0}; | |
uint32_t colors_important{0}; | |
} __attribute ((__packed__)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "BMPImage.h" | |
BMPImage::LineAccessor::LineAccessor(BMPInfoHeader* info, uint8_t *line_ptr) : info(info), line_data(line_ptr) { | |
} | |
BMPImage::PixelReference BMPImage::LineAccessor::operator[](int64_t i) { | |
if (i == -1) { | |
return {line_data}; | |
} | |
if (i == info->width) { | |
return line_data + (3 * info->width - 1); | |
} | |
return {line_data + (3 * i)}; | |
} | |
BMPImage::LineAccessor BMPImage::operator[](int64_t i) { | |
if (i == -1) { | |
return { | |
std::addressof(bmp_info_header), data.data() | |
}; | |
} | |
if (i == bmp_info_header.height) { | |
return { | |
std::addressof(bmp_info_header), data.data() + 3 * bmp_info_header.width * (bmp_info_header.height - 1) | |
}; | |
} | |
return { | |
std::addressof(bmp_info_header), data.data() + 3 * bmp_info_header.width * i, | |
}; | |
}; | |
BMPImage::BMPImage(const std::string& file_name) { | |
Read(file_name); | |
} | |
void BMPImage::Read(const std::string &file_name) { | |
std::ifstream inp{file_name, std::ios_base::binary}; | |
if (inp) { | |
inp.read(reinterpret_cast<char *>(&file_header), sizeof(file_header)); | |
if (file_header.file_type != 0x4D42) { | |
throw std::runtime_error("Error! Unrecognized file format."); | |
} | |
inp.read(reinterpret_cast<char *>(&bmp_info_header), sizeof(bmp_info_header)); | |
inp.seekg(file_header.offset_data, inp.beg); | |
bmp_info_header.size = sizeof(BMPInfoHeader); | |
file_header.offset_data = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader); | |
file_header.file_size = file_header.offset_data; | |
if (bmp_info_header.height < 0) { | |
throw std::runtime_error("Bad file"); | |
} | |
data.resize(bmp_info_header.width * bmp_info_header.height * bmp_info_header.bit_count / 8); | |
if (bmp_info_header.width % 4 == 0) { | |
inp.read(reinterpret_cast<char *>(data.data()), data.size()); | |
file_header.file_size += data.size(); | |
} else { | |
row_stride = bmp_info_header.width * bmp_info_header.bit_count / 8; | |
uint32_t new_stride = MakeStrideAligned(4); | |
std::vector<uint8_t> padding_row(new_stride - row_stride); | |
for (int y = 0; y < bmp_info_header.height; ++y) { | |
inp.read(reinterpret_cast<char *>(data.data() + row_stride * y), row_stride); | |
inp.read(reinterpret_cast<char *>(padding_row.data()), padding_row.size()); | |
} | |
file_header.file_size += data.size() + bmp_info_header.height * padding_row.size(); | |
} | |
} else { | |
throw std::runtime_error("Unable to open the input image file."); | |
} | |
inp.close(); | |
} | |
BMPImage::BMPImage(int32_t width, int32_t height) { | |
if (width <= 0 || height <= 0) { | |
throw std::runtime_error("The image width and height must be positive numbers."); | |
} | |
bmp_info_header.width = width; | |
bmp_info_header.height = height; | |
bmp_info_header.size = sizeof(BMPInfoHeader); | |
file_header.offset_data = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader); | |
bmp_info_header.bit_count = 24; | |
bmp_info_header.compression = 0; | |
row_stride = width * 3; | |
data.resize(row_stride * height); | |
uint32_t new_stride = MakeStrideAligned(4); | |
file_header.file_size = | |
file_header.offset_data + data.size() + bmp_info_header.height * (new_stride - row_stride); | |
} | |
void BMPImage::Write(const std::string file_name) { | |
std::ofstream of{file_name, std::ios_base::binary}; | |
if (of) { | |
if (bmp_info_header.width % 4 == 0) { | |
WriteHeadersAndData(of); | |
} else { | |
uint32_t new_stride = MakeStrideAligned(4); | |
std::vector<uint8_t> padding_row(new_stride - row_stride); | |
WriteHeaders(of); | |
for (size_t y = 0; y < bmp_info_header.height; ++y) { | |
of.write(reinterpret_cast<const char *>(data.data() + row_stride * y), row_stride); | |
of.write(reinterpret_cast<const char *>(padding_row.data()), padding_row.size()); | |
} | |
} | |
} else { | |
throw std::runtime_error("The program can treat only 24 bits per pixel BMP files"); | |
} | |
of.close(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <vector> | |
#include <fstream> | |
#include <iostream> | |
#include <optional> | |
#include "BMPImage.h" | |
#include "BMPHeaders.h" | |
class BMPImage { | |
public: | |
struct PixelReference { | |
PixelReference(uint8_t* pixel_ptr) | |
: B(*pixel_ptr), G(*(pixel_ptr + 1)), | |
R(*(pixel_ptr + 2)) { | |
} | |
std::reference_wrapper<uint8_t> B; | |
std::reference_wrapper<uint8_t> G; | |
std::reference_wrapper<uint8_t> R; | |
}; | |
class LineAccessor { | |
public: | |
LineAccessor(BMPInfoHeader* info, uint8_t* line_ptr); | |
PixelReference operator[](int64_t i); | |
private: | |
BMPInfoHeader* info; | |
uint8_t* line_data; | |
}; | |
LineAccessor operator [](int64_t i); | |
BMPImage(const std::string& file_name); | |
void Read(const std::string& file_name); | |
BMPImage(int32_t width, int32_t height); | |
void Write(const std::string file_name); | |
size_t Height() { | |
return bmp_info_header.height; | |
} | |
size_t Width() { | |
return bmp_info_header.width; | |
} | |
void Crop(size_t width, size_t height) { | |
BMPImage& to_crop = *this; | |
width = std::min(width, to_crop.Width()); | |
height = std::min(height, to_crop.Height()); | |
std::vector<uint8_t> new_data; | |
for (size_t i = to_crop.Height() - height; i < to_crop.Height(); ++i) { | |
for (size_t j = 0; j < width; ++j) { | |
new_data.push_back(to_crop[i][j].B.get()); | |
new_data.push_back(to_crop[i][j].G.get()); | |
new_data.push_back(to_crop[i][j].R.get()); | |
} | |
} | |
file_header.file_size -= data.size() - new_data.size(); | |
data = new_data; | |
bmp_info_header.height = static_cast<int32_t>(height); | |
bmp_info_header.width = static_cast<int32_t>(width); | |
} | |
private: | |
void WriteHeaders(std::ofstream &of) { | |
of.write(reinterpret_cast<const char *>(&file_header), sizeof(file_header)); | |
of.write(reinterpret_cast<const char *>(&bmp_info_header), sizeof(bmp_info_header)); | |
} | |
void WriteHeadersAndData(std::ofstream &of) { | |
WriteHeaders(of); | |
of.write(reinterpret_cast<const char *> (data.data()), data.size()); | |
} | |
uint32_t MakeStrideAligned(uint32_t align_stride) { | |
uint32_t new_stride = row_stride; | |
while (new_stride % align_stride != 0) { | |
++new_stride; | |
} | |
return new_stride; | |
} | |
uint32_t row_stride{0}; | |
BMPFileHeader file_header; | |
BMPInfoHeader bmp_info_header; | |
std::vector<uint8_t> data; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cmake_minimum_required(VERSION 3.21) | |
project(IMGP) | |
set(CMAKE_CXX_STANDARD 20) | |
add_executable(IMGP main.cpp ImageProcessor.cpp ImageProcessor.h BMPImage.cpp BMPImage.h BMPHeaders.h) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Created by Илья Барымов on 19.03.2022. | |
// | |
#include "ImageProcessor.h" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "BMPImage.h" | |
#include <vector> | |
static void MatrixFilter(BMPImage &bmp, const std::vector<std::vector<int>>& matrix) { | |
BMPImage new_bmp = bmp; | |
for (int64_t i = 0; i < bmp.Height(); ++i) { | |
for (int64_t j = 0; j < bmp.Width(); ++j) { | |
new_bmp[i][j].B.get() = | |
std::min(255, std::max(0, | |
matrix[0][0] * bmp[i - 1][j - 1].B.get() + | |
matrix[0][1] * bmp[i][j - 1].B.get() + | |
matrix[0][2] * bmp[i + 1][j - 1].B.get() + | |
matrix[1][0] * bmp[i - 1][j].B.get() + matrix[1][1] * bmp[i][j].B.get() + | |
matrix[1][2] * bmp[i + 1][j].B.get() + | |
matrix[2][0] * bmp[i - 1][j + 1].B.get() + | |
matrix[2][1] * bmp[i][j + 1].B.get() + matrix[2][2] * bmp[i + 1][j + 1].B.get() | |
)); | |
new_bmp[i][j].G.get() = | |
std::min(255, std::max(0, | |
matrix[0][0] * bmp[i - 1][j - 1].G.get() + | |
matrix[0][1] * bmp[i][j - 1].G.get() + | |
matrix[0][2] * bmp[i + 1][j - 1].G.get() + | |
matrix[1][0] * bmp[i - 1][j].G.get() + matrix[1][1] * bmp[i][j].G.get() + | |
matrix[1][2] * bmp[i + 1][j].G.get() + | |
matrix[2][0] * bmp[i - 1][j + 1].G.get() + | |
matrix[2][1] * bmp[i][j + 1].G.get() + matrix[2][2] * bmp[i + 1][j + 1].G.get() | |
)); | |
new_bmp[i][j].R.get() = | |
std::min(255, std::max(0, | |
matrix[0][0] * bmp[i - 1][j - 1].R.get() + | |
matrix[0][1] * bmp[i][j - 1].R.get() + | |
matrix[0][2] * bmp[i + 1][j - 1].R.get() + | |
matrix[1][0] * bmp[i - 1][j].R.get() + matrix[1][1] * bmp[i][j].R.get() + | |
matrix[1][2] * bmp[i + 1][j].R.get() + | |
matrix[2][0] * bmp[i - 1][j + 1].R.get() + | |
matrix[2][1] * bmp[i][j + 1].R.get() + matrix[2][2] * bmp[i + 1][j + 1].R.get() | |
)); | |
} | |
} | |
bmp = new_bmp; | |
} | |
class Filter { | |
public: | |
static constexpr size_t kArgsCount = 0; | |
virtual ~Filter() = default; | |
virtual void Apply(BMPImage &bmp) const = 0; | |
}; | |
class NegativeFilter : public Filter { | |
public: | |
static constexpr size_t kArgsCount = 0; | |
NegativeFilter() = default; | |
void Apply(BMPImage &bmp) const override { | |
for (int64_t i = 0; i < bmp.Height(); ++i) { | |
for (int64_t j = 0; j < bmp.Width(); ++j) { | |
bmp[i][j].B.get() = 255 - bmp[i][j].B.get(); | |
bmp[i][j].G.get() = 255 - bmp[i][j].G.get(); | |
bmp[i][j].R.get() = 255 - bmp[i][j].R.get(); | |
} | |
} | |
} | |
}; | |
class GreyscaleFilter : public Filter { | |
public: | |
static constexpr size_t kArgsCount = 0; | |
GreyscaleFilter() = default; | |
void Apply(BMPImage &bmp) const override { | |
for (int64_t i = 0; i < bmp.Height(); ++i) { | |
for (int64_t j = 0; j < bmp.Width(); ++j) { | |
uint8_t val = bmp[i][j].B.get() * 0.114 + bmp[i][j].G.get() * 0.587 + bmp[i][j].R.get() * 0.299; | |
bmp[i][j].B.get() = val; | |
bmp[i][j].G.get() = val; | |
bmp[i][j].R.get() = val; | |
} | |
} | |
} | |
}; | |
class SharpeningFilter : public Filter { | |
public: | |
static constexpr size_t kArgsCount = 0; | |
SharpeningFilter() = default; | |
void Apply(BMPImage &bmp) const override { | |
MatrixFilter(bmp, { | |
{0, -1, 0}, | |
{-1, 5, -1}, | |
{0, -1, 0}}); | |
}; | |
}; | |
class CropFilter : public Filter { | |
public: | |
static constexpr size_t kArgsCount = 2; | |
CropFilter(uint32_t height, uint32_t width) | |
: height(height), width(width) { | |
} | |
void Apply(BMPImage &bmp) const override { | |
bmp.Crop(width, height); | |
}; | |
private: | |
uint32_t height; | |
uint32_t width; | |
}; | |
class EdgeDetection : public GreyscaleFilter { | |
public: | |
static constexpr size_t kArgsCount = 1; | |
EdgeDetection(uint8_t threshold) : threshold(threshold) {} | |
void Apply(BMPImage &bmp) const override { | |
GreyscaleFilter::Apply(bmp); | |
MatrixFilter(bmp, { | |
{0, -1, 0}, | |
{-1, 4, -1}, | |
{0, -1, 0}}); | |
for (int64_t i = 0; i < bmp.Height(); ++i) { | |
for (int64_t j = 0; j < bmp.Width(); ++j) { | |
uint8_t val = bmp[i][j].B.get() > threshold ? 255 : 0; | |
bmp[i][j].B.get() = val; | |
bmp[i][j].G.get() = val; | |
bmp[i][j].R.get() = val; | |
} | |
} | |
}; | |
private: | |
int64_t threshold; | |
}; | |
class ImageProcessor { | |
public: | |
explicit ImageProcessor(const std::string &name) : img_(name) { | |
} | |
void Apply(const Filter &f) { | |
f.Apply(img_); | |
} | |
void Crop(size_t height, size_t width) { | |
img_.Crop(width, height); | |
} | |
void Save(const std::string &new_name) { | |
img_.Write(new_name); | |
} | |
private: | |
BMPImage img_; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include "ImageProcessor.h" | |
#include <unordered_map> | |
class ArgsParser { | |
public: | |
ArgsParser(int argc, char** argv) { | |
for (size_t i = 1; i < argc; ++i) { | |
args_deq_.emplace_back(argv[i]); | |
} | |
} | |
void ParseData() { | |
in_file = GetNext(args_deq_); | |
out_file = GetNext(args_deq_); | |
ParseFilters(args_deq_); | |
} | |
~ArgsParser() { | |
for (auto* f : filters_) { | |
delete f; | |
} | |
} | |
std::vector<Filter*>::iterator begin() { | |
return filters_.begin(); | |
} | |
std::vector<Filter*>::iterator end() { | |
return filters_.end(); | |
} | |
std::string GetInputFileName() { | |
return in_file; | |
} | |
std::string GetOutputFileName() { | |
return out_file; | |
} | |
private: | |
void ParseFilters(std::deque<std::string>& args_deq) { | |
std::string in; | |
while (!args_deq.empty()) { | |
in = GetNext(args_deq); | |
if (in == "-crop") { | |
filters_.push_back( | |
new CropFilter(std::stoi(GetNext(args_deq)), std::stoi(GetNext(args_deq))) | |
); | |
} | |
if (in == "-gs") { | |
filters_.push_back( | |
new GreyscaleFilter() | |
); | |
} | |
if (in == "-neg") { | |
filters_.push_back( | |
new NegativeFilter() | |
); | |
} | |
if (in == "-sharp") { | |
filters_.push_back( | |
new SharpeningFilter() | |
); | |
} | |
if (in == "-edge") { | |
filters_.push_back( | |
new EdgeDetection(std::stoi(GetNext(args_deq))) | |
); | |
} | |
} | |
} | |
static std::string GetNext(std::deque<std::string>& argv) { | |
if (argv.empty()) { | |
return ""; | |
} | |
auto result = argv.front(); | |
argv.pop_front(); | |
return result; | |
} | |
std::vector<Filter*> filters_; | |
std::deque<std::string> args_deq_; | |
std::string in_file; | |
std::string out_file; | |
}; | |
void PrintHelp() { | |
std::cout << "HELP\n"; | |
} | |
int main(int argc, char** argv) { | |
if (argc < 3) { | |
return 0; | |
} | |
if (argc <= 1) { | |
PrintHelp(); | |
return 0; | |
} | |
try { | |
ArgsParser parser(argc, argv); | |
parser.ParseData(); // to avoid exceptions in constructor | |
ImageProcessor image(parser.GetInputFileName()); | |
for (auto* filter : parser) { | |
image.Apply(*filter); | |
} | |
image.Save(parser.GetOutputFileName()); | |
} catch (std::runtime_error& e) { | |
std::cout << "Error, sorry"; | |
PrintHelp(); | |
} catch (std::exception& e) { | |
std::cout << "Error, sorry"; | |
PrintHelp(); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment