Created
March 16, 2022 19:26
-
-
Save VTroyanGolovyan/19419aaf4c06f1aaa6423c987fdd61d2 to your computer and use it in GitHub Desktop.
BMPProc
BMPProc
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__)); | |
struct BMPColorHeader { | |
uint32_t red_mask{ 0x00ff0000 }; // Bit mask for the red channel | |
uint32_t green_mask{ 0x0000ff00 }; // Bit mask for the green channel | |
uint32_t blue_mask{ 0x000000ff }; // Bit mask for the blue channel | |
uint32_t alpha_mask{ 0xff000000 }; // Bit mask for the alpha channel | |
uint32_t color_space_type{ 0x73524742 }; // Default "sRGB" (0x73524742) | |
uint32_t unused[16]{ 0 }; // Unused data for sRGB color space | |
} __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
// | |
// Created by Владислав on 16.03.2022. | |
// | |
#include "BMPImage.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 <vector> | |
#include <fstream> | |
#include <iostream> | |
#include <optional> | |
#include "BMPImage.h" | |
#include "BmpHeaders.h" | |
class BMPImage { | |
public: | |
struct PixelReference { | |
PixelReference(uint8_t* pixel_ptr, bool has_a) | |
: B(*pixel_ptr), G(*(pixel_ptr + 1)), | |
R(*(pixel_ptr + 2)), | |
A(has_a ? std::optional<std::reference_wrapper<uint8_t>>(*(pixel_ptr + 3)) : std::nullopt){ | |
} | |
std::reference_wrapper<uint8_t> B; | |
std::reference_wrapper<uint8_t> G; | |
std::reference_wrapper<uint8_t> R; | |
std::optional<std::reference_wrapper<uint8_t>> A; | |
}; | |
class LineAccessor { | |
public: | |
LineAccessor(uint8_t* line_ptr, bool has_a) : line_data(line_ptr), has_a(has_a) { | |
} | |
PixelReference operator[](size_t i) { | |
return PixelReference(line_data + (3 + static_cast<int8_t>(has_a)) * i, has_a); | |
} | |
private: | |
uint8_t* line_data; | |
bool has_a; | |
}; | |
LineAccessor operator [](size_t i) { | |
return { | |
data.data() + Channels() * bmp_info_header.width * i, | |
Channels() == 4 | |
}; | |
} | |
BMPImage(const std::string& file_name) { | |
Read(file_name); | |
} | |
void 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)); | |
// The BMPColorHeader is used only for transparent images | |
if (bmp_info_header.bit_count == 32) { | |
// Check if the file has bit mask color information | |
if (bmp_info_header.size >= (sizeof(BMPInfoHeader) + sizeof(BMPColorHeader))) { | |
inp.read(reinterpret_cast<char *>(&bmp_color_header), sizeof(bmp_color_header)); | |
// Check if the pixel data is stored as BGRA and if the color space type is sRGB | |
CheckColorHeader(bmp_color_header); | |
} else { | |
std::cerr << "Warning! The file \"" << file_name | |
<< "\" does not seem to contain bit mask information\n"; | |
throw std::runtime_error("Error! Unrecognized file format."); | |
} | |
} | |
// Jump to the pixel data location | |
inp.seekg(file_header.offset_data, inp.beg); | |
// Adjust the header fields for output. | |
// Some editors will put extra info in the image file, we only save the headers and the data. | |
if (bmp_info_header.bit_count == 32) { | |
bmp_info_header.size = sizeof(BMPInfoHeader) + sizeof(BMPColorHeader); | |
file_header.offset_data = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + sizeof(BMPColorHeader); | |
} else { | |
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( | |
"The program can treat only BMP images with the origin in the bottom left corner!"); | |
} | |
data.resize(bmp_info_header.width * bmp_info_header.height * bmp_info_header.bit_count / 8); | |
// Here we check if we need to take into account row padding | |
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(int32_t width, int32_t height, bool has_alpha = true) { | |
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; | |
if (has_alpha) { | |
bmp_info_header.size = sizeof(BMPInfoHeader) + sizeof(BMPColorHeader); | |
file_header.offset_data = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + sizeof(BMPColorHeader); | |
bmp_info_header.bit_count = 32; | |
bmp_info_header.compression = 3; | |
row_stride = width * 4; | |
data.resize(row_stride * height); | |
file_header.file_size = file_header.offset_data + data.size(); | |
} else { | |
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 Write(const std::string file_name) { | |
std::ofstream of{file_name, std::ios_base::binary}; | |
if (of) { | |
if (bmp_info_header.bit_count == 32) { | |
WriteHeadersAndData(of); | |
} else if (bmp_info_header.bit_count == 24) { | |
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 or 32 bits per pixel BMP files"); | |
} | |
} else { | |
throw std::runtime_error("Unable to open the output image file."); | |
} | |
of.close(); | |
} | |
size_t Channels() { | |
return bmp_info_header.bit_count / 8; | |
} | |
size_t Height() { | |
return bmp_info_header.height; | |
} | |
size_t Width() { | |
return bmp_info_header.width; | |
} | |
private: | |
// Check if the pixel data is stored as BGRA and if the color space type is sRGB | |
void CheckColorHeader(const BMPColorHeader& bmp_color_header) { | |
BMPColorHeader expected_color_header; | |
if (expected_color_header.red_mask != bmp_color_header.red_mask || | |
expected_color_header.blue_mask != bmp_color_header.blue_mask || | |
expected_color_header.green_mask != bmp_color_header.green_mask || | |
expected_color_header.alpha_mask != bmp_color_header.alpha_mask) { | |
throw std::runtime_error( | |
"Unexpected color mask format! The program expects the pixel data to be in the BGRA format"); | |
} | |
if (expected_color_header.color_space_type != bmp_color_header.color_space_type) { | |
throw std::runtime_error("Unexpected color space type! The program expects sRGB values"); | |
} | |
} | |
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)); | |
if (bmp_info_header.bit_count == 32) { | |
of.write(reinterpret_cast<const char *> (&bmp_color_header), sizeof(bmp_color_header)); | |
} | |
} | |
void WriteHeadersAndData(std::ofstream &of) { | |
WriteHeaders(of); | |
of.write(reinterpret_cast<const char *> (data.data()), data.size()); | |
} | |
// Add 1 to the row_stride until it is divisible with align_stride | |
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; | |
BMPColorHeader bmp_color_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(BMPPhotoshop) | |
set(CMAKE_CXX_STANDARD 20) | |
add_executable(BMPPhotoshop main.cpp BmpHeaders.h BMPImage.cpp BMPImage.h ImageProcessor.cpp 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
// | |
// Created by Владислав on 16.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" | |
class Filter { | |
public: | |
virtual void Apply(BMPImage& bmp) const = 0; | |
}; | |
class NegativeFilter : public Filter { | |
public: | |
NegativeFilter() = default; | |
void Apply(BMPImage& bmp) const override { | |
for (size_t i = 0; i < bmp.Height(); ++i) { | |
for (size_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 WhiteBlack : public Filter { | |
public: | |
WhiteBlack() = default; | |
void Apply(BMPImage& bmp) const override { | |
for (size_t i = 0; i < bmp.Height(); ++i) { | |
for (size_t j = 0; j < bmp.Width(); ++j) { | |
uint8_t val = bmp[i][j].B.get() / 3 + bmp[i][j].G.get() / 3 + bmp[i][j].R.get() / 3; | |
bmp[i][j].B.get() = val; | |
bmp[i][j].G.get() = val; | |
bmp[i][j].R.get() = val; | |
} | |
} | |
} | |
}; | |
class AlphaFilter : public Filter { | |
public: | |
AlphaFilter(double alpha) : alpha(alpha) { | |
} | |
void Apply(BMPImage& bmp) const override { | |
if (!bmp[0][0].A) { | |
return; | |
} | |
for (size_t i = 0; i < bmp.Height(); ++i) { | |
for (size_t j = 0; j < bmp.Width(); ++j) { | |
bmp[i][j].A.value().get() = static_cast<uint8_t>(bmp[i][j].A.value().get() * alpha); | |
} | |
} | |
} | |
private: | |
double alpha; | |
}; | |
class ImageProcessor { | |
public: | |
explicit ImageProcessor(const std::string& name) : img_(name) { | |
} | |
void Apply(const Filter& f) { | |
f.Apply(img_); | |
} | |
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" | |
int main() { | |
ImageProcessor processor("/Users/vhdev/CLionProjects/BMPPhotoshop/example/example.bmp"); | |
//processor.Apply(NegativeFilter()); | |
processor.Apply(AlphaFilter(0.3)); | |
processor.Save("/Users/vhdev/CLionProjects/BMPPhotoshop/example/lol.bmp"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment