Skip to content

Instantly share code, notes, and snippets.

@VTroyanGolovyan
Created March 23, 2022 19:27
Show Gist options
  • Save VTroyanGolovyan/6fa000e0da4dc3870c9639248d7e0769 to your computer and use it in GitHub Desktop.
Save VTroyanGolovyan/6fa000e0da4dc3870c9639248d7e0769 to your computer and use it in GitHub Desktop.
Img
#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__));
#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();
}
#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;
};
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)
//
// Created by Илья Барымов on 19.03.2022.
//
#include "ImageProcessor.h"
#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_;
};
#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