Skip to content

Instantly share code, notes, and snippets.

@VTroyanGolovyan
Created March 16, 2022 19:26
Show Gist options
  • Save VTroyanGolovyan/19419aaf4c06f1aaa6423c987fdd61d2 to your computer and use it in GitHub Desktop.
Save VTroyanGolovyan/19419aaf4c06f1aaa6423c987fdd61d2 to your computer and use it in GitHub Desktop.
BMPProc BMPProc
#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__));
//
// Created by Владислав on 16.03.2022.
//
#include "BMPImage.h"
#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;
};
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)
//
// Created by Владислав on 16.03.2022.
//
#include "ImageProcessor.h"
#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_;
};
#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