// It reads a 24 bit BMP file and calculates the convolution with a filter

#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <algorithm>

#include <cstdio>
#include <cmath>


typedef unsigned char tbyte;
typedef unsigned int t4bytes;


class Pixel24bit;
class RowPixel24bit;
class Image24bit;

class PixelDouble;
class RowPixelDouble;
class ImageDouble;


class Pixel24bit {
public:
  Pixel24bit(tbyte R, tbyte G, tbyte B) : R(R), G(G), B(B) { }
  tbyte R;
  tbyte G;
  tbyte B;
};

class RowPixel24bit {
public:
  RowPixel24bit() { }
  RowPixel24bit(t4bytes width, Pixel24bit pixel) : pixels(width, pixel) { }
  RowPixel24bit(std::vector<Pixel24bit> pixels) : pixels(pixels) { }
  std::vector<Pixel24bit> pixels;
};

class Image24bit {
public:
  Image24bit() : width(0), height(0) { }
  Image24bit(t4bytes width, t4bytes height, tbyte R = 255, tbyte G = 255, tbyte B = 255) : width(width), height(height) {
    rows = std::vector<RowPixel24bit>(height, RowPixel24bit(width, Pixel24bit(R, G, B)));
  }
  Image24bit(std::vector<RowPixel24bit> rows) : rows(rows) {
    height = rows.size();
    if (height > 0) {
      width = rows[0].pixels.size();
    } else {
      width = 0;
    }
  }
  bool writeToFile(const std::string & fileName);
  bool readFromFile(const std::string & fileName);
  bool isBmpFileFormatSupported(std::ifstream & fin, t4bytes & fileSize, t4bytes & ww, t4bytes & hh, t4bytes & restSize);
  void showPixelsInRectangle(t4bytes x1, t4bytes y1, t4bytes w, t4bytes h);
  ImageDouble getImageDouble();
  std::vector<RowPixel24bit> rows;
  t4bytes width;
  t4bytes height;
};

class PixelDouble {
public:
  PixelDouble(double R, double G, double B) : R(R), G(G), B(B) { }
  double R;
  double G;
  double B;
};

class RowPixelDouble {
public:
  RowPixelDouble() { }
  RowPixelDouble(t4bytes width, PixelDouble pixel) : pixels(width, pixel) { }
  RowPixelDouble(std::vector<PixelDouble> pixels) : pixels(pixels) { }
  std::vector<PixelDouble> pixels;
};

class ImageDouble {
public:
  ImageDouble() : width(0), height(0) { }
  ImageDouble(t4bytes width, t4bytes height, double R = 0.0, double G = 0.0, double B = 0.0) : width(width), height(height) {
    rows = std::vector<RowPixelDouble>(height, RowPixelDouble(width, PixelDouble(R, G, B)));
  }
  ImageDouble(std::vector<RowPixelDouble> rows) : rows(rows) {
    height = rows.size();
    if (height > 0) {
      width = rows[0].pixels.size();
    } else {
      width = 0;
    }
  }
  ImageDouble(double *filter, t4bytes width, t4bytes height);
  bool calculateConvolution(const ImageDouble & filter, t4bytes filterX0, t4bytes filterY0, ImageDouble & result);
  void showPixelsInRectangle(t4bytes x1, t4bytes y1, t4bytes w, t4bytes h);
  Image24bit getImage24bit(bool center = false, double factor = 1.0, double offset = 0.0);
  std::vector<RowPixelDouble> rows;
  t4bytes width;
  t4bytes height;
};


int main(int argc, char** argv) {
  if (argc != 5) {
    std::fprintf(stderr, "Syntax: %s inputBMP24bitFile outputBMP24bitFile factor offset\n", argv[0]);
    std::fprintf(stderr, "Example: %s input.bmp output.bmp 0.5 128.0\n", argv[0]);
    return 1;
  }
  std::string sfileIn(argv[1]);
  std::string sfileOut(argv[2]);
  double factor = std::stod(argv[3]);
  double offset = std::stod(argv[4]);

  Image24bit imageIn;
  imageIn.readFromFile(sfileIn);

//  double filterLaplace[3][3] = {{  0.0, -1.0,  0.0 }, // double filterLaplace[height][width] = {{...
//                                { -1.0,  4.0, -1.0 },
//                                {  0.0, -1.0,  0.0 }};

  double filterLaplace[3][3] = {{ -1.0, -1.0, -1.0 }, // double filterLaplace[height][width] = {{...
                                { -1.0,  8.0, -1.0 },
                                { -1.0, -1.0, -1.0 }};

  ImageDouble filter(&filterLaplace[0][0], 3, 3);

//  filter.showPixelsInRectangle(0, 0, filter.width, filter.height);

  ImageDouble result;
  if (!imageIn.getImageDouble().calculateConvolution(filter, 1, 1, result)) {
    std::cerr << "Error calculating convolution" << std::endl;
    return 2;
  }

  result.getImage24bit(false, factor, offset).writeToFile(sfileOut);

  return 0;
}


bool Image24bit::writeToFile(const std::string & fileName) {
  t4bytes hh = rows.size();
  if (hh < 1) {
    return false;
  }
  t4bytes ww = rows[0].pixels.size();
  if (ww < 1) {
    return false;
  }

  int nBytesPaddingPerRow = (3 * ww % 4 == 0) ? 0 : (4 - 3 * ww % 4);
  t4bytes restSize = 3 * ww * hh + hh*nBytesPaddingPerRow;
  t4bytes fileSize = restSize + 54;

  unsigned char header[54] = {0};
  header[0] = 66;
  header[1] = 77;
  header[10] = 54;
  header[14] = 40;
  header[26] = 1;
  header[28] = 24;

  header[2] = *(reinterpret_cast<unsigned char *>(&fileSize) + 0); // File Size byte 0 (LSB)
  header[3] = *(reinterpret_cast<unsigned char *>(&fileSize) + 1); // File Size byte 1
  header[4] = *(reinterpret_cast<unsigned char *>(&fileSize) + 2); // File Size byte 2
  header[5] = *(reinterpret_cast<unsigned char *>(&fileSize) + 3); // File Size byte 3 (MSB)

  header[18] = *(reinterpret_cast<unsigned char *>(&ww) + 0); // Width byte 0 (LSB)
  header[19] = *(reinterpret_cast<unsigned char *>(&ww) + 1); // Width byte 1
  header[20] = *(reinterpret_cast<unsigned char *>(&ww) + 2); // Width byte 2
  header[21] = *(reinterpret_cast<unsigned char *>(&ww) + 3); // Width byte 3 (MSB)

  header[22] = *(reinterpret_cast<unsigned char *>(&hh) + 0); // Height byte 0 (LSB)
  header[23] = *(reinterpret_cast<unsigned char *>(&hh) + 1); // Height byte 1
  header[24] = *(reinterpret_cast<unsigned char *>(&hh) + 2); // Height byte 2
  header[25] = *(reinterpret_cast<unsigned char *>(&hh) + 3); // Height byte 3 (MSB)

  header[34] = *(reinterpret_cast<unsigned char *>(&restSize) + 0); // Rest Size byte 0 (LSB)
  header[35] = *(reinterpret_cast<unsigned char *>(&restSize) + 1); // Rest Size byte 1
  header[36] = *(reinterpret_cast<unsigned char *>(&restSize) + 2); // Rest Size byte 2
  header[37] = *(reinterpret_cast<unsigned char *>(&restSize) + 3); // Rest Size byte 3 (MSB)

  std::ofstream fout(fileName.c_str(), std::ios::out | std::ios::binary);

  fout.write(reinterpret_cast<char *>(&header[0]), 54);

  tbyte * rest = new tbyte[restSize];

  t4bytes yy;
  t4bytes xx;
  t4bytes pos = 0;
  for (yy = 0; yy < hh; yy++) {
    for (xx = 0; xx < ww; xx++) {
      rest[pos++] = rows[yy].pixels[xx].B;
      rest[pos++] = rows[yy].pixels[xx].G;
      rest[pos++] = rows[yy].pixels[xx].R;
    }
    for (xx = 0; xx < nBytesPaddingPerRow; xx++) {
      rest[pos++] = '\x0';
    }
  }

  fout.write(reinterpret_cast<char *>(rest), restSize);

  fout.close();

  delete[] rest;

  return true;
}


bool Image24bit::readFromFile(const std::string & fileName) {
  std::ifstream fin(fileName.c_str(), std::ios::in | std::ios::binary);

  t4bytes fileSize = 0;
  t4bytes ww = 0;
  t4bytes hh = 0;
  t4bytes restSize = 0;
  if (!isBmpFileFormatSupported(fin, fileSize, ww, hh, restSize)) {
    return false;
  }

  tbyte * rest = new tbyte[restSize];

  fin.read(reinterpret_cast<char *>(rest), restSize);

  fin.close();

  rows = std::vector<RowPixel24bit>(hh, RowPixel24bit(ww, Pixel24bit(255, 255, 255)));

  int nBytesPaddingPerRow = (3 * ww % 4 == 0) ? 0 : (4 - 3 * ww % 4);

  t4bytes yy;
  t4bytes xx;
  t4bytes pos = 0;
  tbyte padding;
  for (yy = 0; yy < hh; yy++) {
    for (xx = 0; xx < ww; xx++) {
      rows[yy].pixels[xx].B = rest[pos++];
      rows[yy].pixels[xx].G = rest[pos++];
      rows[yy].pixels[xx].R = rest[pos++];
    }
    for (xx = 0; xx < nBytesPaddingPerRow; xx++) {
      padding = rest[pos++];
      if (padding != '\x0') {
        std::cerr << "Error reading BMP file (padding not null)" << std::endl;
        return false;
      }
    }
  }

  delete[] rest;

  width = ww;
  height = hh;

  return true;
}


bool Image24bit::isBmpFileFormatSupported(std::ifstream & fin, t4bytes & fileSize, t4bytes & ww, t4bytes & hh, t4bytes & restSize) {
  unsigned char header[54] = {0};

  fin.seekg(0, std::ios_base::end);
  std::size_t fileSizeT = fin.tellg();
  fin.seekg(0, std::ios_base::beg);

  if (fileSizeT < 54) {
    std::cerr << "BMP file format not supported (fileSizeT < 54)" << std::endl;
    return false;
  }

  fin.read(reinterpret_cast<char *>(&header[0]), 54);

  if (header[10] != 54 || header[28] != 24) {
    std::cerr << "BMP file format not supported (header[10] != 54 || header[28] != 24)" << std::endl;
    return false;
  }

  *(reinterpret_cast<unsigned char *>(&fileSize) + 0) = header[2]; // File Size byte 0 (LSB)
  *(reinterpret_cast<unsigned char *>(&fileSize) + 1) = header[3]; // File Size byte 1
  *(reinterpret_cast<unsigned char *>(&fileSize) + 2) = header[4]; // File Size byte 2
  *(reinterpret_cast<unsigned char *>(&fileSize) + 3) = header[5]; // File Size byte 3 (MSB)

  if (fileSize != fileSizeT) {
    std::cerr << "BMP file format not supported (fileSize != fileSizeT)" << std::endl;
    return false;
  }

  *(reinterpret_cast<unsigned char *>(&ww) + 0) = header[18]; // Width byte 0 (LSB)
  *(reinterpret_cast<unsigned char *>(&ww) + 1) = header[19]; // Width byte 1
  *(reinterpret_cast<unsigned char *>(&ww) + 2) = header[20]; // Width byte 2
  *(reinterpret_cast<unsigned char *>(&ww) + 3) = header[21]; // Width byte 3 (MSB)

  *(reinterpret_cast<unsigned char *>(&hh) + 0) = header[22]; // Height byte 0 (LSB)
  *(reinterpret_cast<unsigned char *>(&hh) + 1) = header[23]; // Height byte 1
  *(reinterpret_cast<unsigned char *>(&hh) + 2) = header[24]; // Height byte 2
  *(reinterpret_cast<unsigned char *>(&hh) + 3) = header[25]; // Height byte 3 (MSB)

  *(reinterpret_cast<unsigned char *>(&restSize) + 0) = header[34]; // Rest Size byte 0 (LSB)
  *(reinterpret_cast<unsigned char *>(&restSize) + 1) = header[35]; // Rest Size byte 1
  *(reinterpret_cast<unsigned char *>(&restSize) + 2) = header[36]; // Rest Size byte 2
  *(reinterpret_cast<unsigned char *>(&restSize) + 3) = header[37]; // Rest Size byte 3 (MSB)

  if (fileSize != (restSize + 54)) {
    std::cerr << "BMP file format not supported (fileSize != (restSize + 54))" << std::endl;
    return false;
  }

  return true;
}


void Image24bit::showPixelsInRectangle(t4bytes x1, t4bytes y1, t4bytes w, t4bytes h) {
  std::cout << "(x, y) : (R, G, B)" << std::endl;
  for (t4bytes yy = y1; yy < (y1 + h); yy++) {
    for (t4bytes xx = x1; xx < (x1 + w); xx++) {
      std::cout << "(" << xx << ", " << yy << ") : (" << static_cast<int>(rows.at(yy).pixels.at(xx).R) << ", " << static_cast<int>(rows.at(yy).pixels.at(xx).G) << ", " << static_cast<int>(rows.at(yy).pixels.at(xx).B) << ")" << std::endl;
    }
  }
}


ImageDouble Image24bit::getImageDouble() {
  ImageDouble result(width, height, 0.0, 0.0, 0.0);
  for (t4bytes yy = 0; yy < height; yy++) {
    for (t4bytes xx = 0; xx < width; xx++) {
      result.rows[yy].pixels[xx].R = rows[yy].pixels[xx].R;
      result.rows[yy].pixels[xx].G = rows[yy].pixels[xx].G;
      result.rows[yy].pixels[xx].B = rows[yy].pixels[xx].B;
    }
  }
  return result;
}


ImageDouble::ImageDouble(double *filter, t4bytes width, t4bytes height) : width(width), height(height) {
  rows = std::vector<RowPixelDouble>(height, RowPixelDouble(width, PixelDouble(0.0, 0.0, 0.0)));
  for (t4bytes yy = 0; yy < height; yy++) {
    for (t4bytes xx = 0; xx < width; xx++) {
      rows[yy].pixels[xx].R = *(filter + yy * width + xx);
      rows[yy].pixels[xx].G = *(filter + yy * width + xx);
      rows[yy].pixels[xx].B = *(filter + yy * width + xx);
    }
  }
}


bool ImageDouble::calculateConvolution(const ImageDouble & filter, t4bytes filterX0, t4bytes filterY0, ImageDouble & result) {
  long long xx, yy;

  if (filterX0 >= filter.width || filterY0 >= filter.height) {
    return false;
  }

  result = *this;

  for (long long nn = 0; nn < result.height; nn++) {
    for (long long mm = 0; mm < result.width; mm++) {
      for (long long jj = 0; jj < filter.height; jj++) {
        for (long long ii = 0; ii < filter.width; ii++) {
          xx = mm + ii - filterX0;
          yy = nn + jj - filterY0;
          if (xx >= 0 && xx < width && yy >= 0 && yy < height) {
            result.rows[nn].pixels[mm].R += rows[yy].pixels[xx].R * filter.rows[jj].pixels[ii].R;
            result.rows[nn].pixels[mm].G += rows[yy].pixels[xx].G * filter.rows[jj].pixels[ii].G;
            result.rows[nn].pixels[mm].B += rows[yy].pixels[xx].B * filter.rows[jj].pixels[ii].B;
          }
        }
      }
    }
  }

  return true;
}


void ImageDouble::showPixelsInRectangle(t4bytes x1, t4bytes y1, t4bytes w, t4bytes h) {
  std::cout << "(x, y) : (R, G, B)" << std::endl;
  for (t4bytes yy = y1; yy < (y1 + h); yy++) {
    for (t4bytes xx = x1; xx < (x1 + w); xx++) {
      std::cout << "(" << xx << ", " << yy << ") : (" << rows.at(yy).pixels.at(xx).R << ", " << rows.at(yy).pixels.at(xx).G << ", " << rows.at(yy).pixels.at(xx).B << ")" << std::endl;
    }
  }
}


Image24bit ImageDouble::getImage24bit(bool center, double factor, double offset) {
  double RR, GG, BB;
  double minValue = rows[0].pixels[0].R;
  double maxValue = rows[0].pixels[0].R;
  bool valid = true;

  if (center) {
    for (t4bytes yy = 0; yy < height; yy++) {
      for (t4bytes xx = 0; xx < width; xx++) {
        if (rows[yy].pixels[xx].R < minValue) {
          minValue = rows[yy].pixels[xx].R;
        } else if (rows[yy].pixels[xx].R > maxValue) {
          maxValue = rows[yy].pixels[xx].R;
        }
        if (rows[yy].pixels[xx].G < minValue) {
          minValue = rows[yy].pixels[xx].G;
        } else if (rows[yy].pixels[xx].G > maxValue) {
          maxValue = rows[yy].pixels[xx].G;
        }
        if (rows[yy].pixels[xx].B < minValue) {
          minValue = rows[yy].pixels[xx].B;
        } else if (rows[yy].pixels[xx].B > maxValue) {
          maxValue = rows[yy].pixels[xx].B;
        }
      }
    }
    factor = 255.0 / (maxValue - minValue);
    offset = -minValue;
  }

  std::cout << "Using factor = " << factor << " offset = " << offset << std::endl;

  Image24bit result(width, height);

  for (t4bytes yy = 0; yy < height; yy++) {
    for (t4bytes xx = 0; xx < width; xx++) {
      RR = factor * (rows[yy].pixels[xx].R + offset);
      GG = factor * (rows[yy].pixels[xx].G + offset);
      BB = factor * (rows[yy].pixels[xx].B + offset);

      if (RR >= 0.0 && RR <= 255.0) {
        result.rows[yy].pixels[xx].R = static_cast<tbyte>(RR);
      } else {
        valid = false;
        result.rows[yy].pixels[xx].R = static_cast<tbyte>(std::min(255.0, std::max(0.0, RR)));
      }
      if (GG >= 0.0 && GG <= 255.0) {
        result.rows[yy].pixels[xx].G = static_cast<tbyte>(GG);
      } else {
        valid = false;
        result.rows[yy].pixels[xx].G = static_cast<tbyte>(std::min(255.0, std::max(0.0, GG)));
      }
      if (BB >= 0.0 && BB <= 255.0) {
        result.rows[yy].pixels[xx].B = static_cast<tbyte>(BB);
      } else {
        valid = false;
        result.rows[yy].pixels[xx].B = static_cast<tbyte>(std::min(255.0, std::max(0.0, BB)));
      }

      if (!center) {
        if (rows[yy].pixels[xx].R < minValue) {
          minValue = rows[yy].pixels[xx].R;
        } else if (rows[yy].pixels[xx].R > maxValue) {
          maxValue = rows[yy].pixels[xx].R;
        }
        if (rows[yy].pixels[xx].G < minValue) {
          minValue = rows[yy].pixels[xx].G;
        } else if (rows[yy].pixels[xx].G > maxValue) {
          maxValue = rows[yy].pixels[xx].G;
        }
        if (rows[yy].pixels[xx].B < minValue) {
          minValue = rows[yy].pixels[xx].B;
        } else if (rows[yy].pixels[xx].B > maxValue) {
          maxValue = rows[yy].pixels[xx].B;
        }
      }
    }
  }

  if (!valid && !center) {
    std::cout << "OVERFLOW" << std::endl;
    std::cout << "Try factor <= " << (255.0 / (maxValue - minValue)) << " offset ~= " << (-minValue) << std::endl;
  }

  return result;
}
      

// Generated by xformulas.net on 20190917_100836