ICT290 / src / engine / BMP.cpp
BMP.cpp
Raw
#include "BMP.h"
#include <stdexcept>

// destructor
BMP::~BMP() {
    // dtor
    Clear();
}

// Copy constructor
BMP::BMP(const BMP& other) {
    copy(other);
}

// Assignment operator
BMP& BMP::operator=(const BMP& other) {
    if (&other != this) {
        Clear();
        copy(other);
    }
    return *this;
}

// copy helper method
bool BMP::copy(const BMP& other) {
    this->m_fileHeader = new BMPFILEHEADER;
    this->m_infoHeader = new BMPINFOHEADER;
    this->m_pixels = new unsigned char[other.m_pixelsSize];

    // if memory creation fails, return false
    if (this->m_fileHeader == nullptr || this->m_infoHeader == nullptr
        || this->m_pixels == nullptr) {
        return false;
    }

    // copy contents Headers from other BMP object to this
    memcpy(this->m_fileHeader, other.m_fileHeader, sizeof(BMPFILEHEADER));
    memcpy(this->m_infoHeader, other.m_infoHeader, sizeof(BMPINFOHEADER));

    // copy pixel data from other BMP to this
    memcpy(this->m_pixels, other.m_pixels, other.m_pixelsSize);

    return true;
}

void BMP::Clear() {
    delete[] m_fileHeader;
    delete[] m_infoHeader;
    delete[] m_pixels;
    m_fileHeader = nullptr;
    m_infoHeader = nullptr;
    m_pixels = nullptr;
}

// reads in a BMP file of the fileName string
bool BMP::readInBMP(const std::string& fileName) {
    // check filename contains a .bmp ext
    if (fileName.find(".bmp") == std::string::npos) {
        throw std::invalid_argument("Error: \"" + fileName
                                    + "\" does not contain a .bmp extension");
    }

    unsigned char* datBuff[2] = {nullptr, nullptr};  // Header buffers

    // File needs to be read in binary
    std::ifstream file(fileName, std::ios::binary);
    if (!file) {
        throw std::runtime_error("Failed to open bitmap: \"" + fileName + "\"");
    }
    file.exceptions(std::ifstream::failbit | std::ifstream::badbit);

    // Allocate byte memory that will hold the two headers
    datBuff[0] = new unsigned char[sizeof(BMPFILEHEADER)];
    datBuff[1] = new unsigned char[sizeof(BMPINFOHEADER)];

    try {
        file.read((char*)datBuff[0], sizeof(BMPFILEHEADER));
        file.read((char*)datBuff[1], sizeof(BMPINFOHEADER));
    } catch (const std::ifstream::failure&) {
        throw std::runtime_error("Exception reading headers in file: \""
                                 + fileName + "\"");
    }

    // Construct the values from the buffers
    m_fileHeader = reinterpret_cast<BMPFILEHEADER*>(datBuff[0]);
    m_infoHeader = reinterpret_cast<BMPINFOHEADER*>(datBuff[1]);

    // Check if the file is an actual BMP file
    if (m_fileHeader->bfType != BMP_BYTE_CODE) {
        throw std::runtime_error("File \"" + fileName
                                 + "\" has non BMP bytecode in header");
    }

    // Get the true size of the image
    m_pixelsSize = m_infoHeader->biWidth * m_infoHeader->biHeight
                   * m_infoHeader->biBitCount;

    // Create memory with size read in from bmp header
    m_pixels = new unsigned char[m_pixelsSize];
    if (m_pixels == nullptr) {
        throw std::runtime_error("Failed to create heap Memory");
    }

    try {
        // Go to where image data starts, then read in image data
        file.seekg(m_fileHeader->bfOffBits);
        file.read((char*)m_pixels, m_infoHeader->biSizeImage);
    } catch (const std::ifstream::failure&) {
        throw std::runtime_error("Exception reading pixel data in file: \""
                                 + fileName + "\"");
    }

    return true;
}

// BMP format File Header getter
BMPFILEHEADER* BMP::getFileHeader() const {
    return m_fileHeader;
}

// BMP format Information Header getter
BMPINFOHEADER* BMP::getInfoHeader() const {
    return m_infoHeader;
}

// pixel pointer getter
unsigned char* BMP::getPixels() {
    return m_pixels;
}

// Rotates the image of a read in BMP image
void BMP::rotateBMP(double degrees) {
    unsigned char* rotatedPixels = new unsigned char[m_pixelsSize];

    double radians = (degrees * glm::pi<double>()) / 180;

    double xOrigin = 0.5
                     * (m_infoHeader->biWidth - 1);  // point to rotate about
    double yOrigin = 0.5 * (m_infoHeader->biHeight - 1);  // center of image

    // static cast unsigned int width and height once
    int width = static_cast<int>(m_infoHeader->biWidth);
    int height = static_cast<int>(m_infoHeader->biHeight);

    // rotation
    for (int x = 0; x < width; ++x) {
        for (int y = 0; y < height; ++y) {
            long double a = x - xOrigin;
            long double b = y - yOrigin;
            int xx = (int)(+a * std::cos(radians) - b * std::sin(radians)
                           + xOrigin);
            int yy = (int)(+a * std::sin(radians) + b * std::cos(radians)
                           + yOrigin);

            if (xx >= 0 && xx < width && yy >= 0 && yy < height) {
                rotatedPixels[(y * height + x) * 3
                              + 0] = m_pixels[(yy * height + xx) * 3 + 0];
                rotatedPixels[(y * height + x) * 3
                              + 1] = m_pixels[(yy * height + xx) * 3 + 1];
                rotatedPixels[(y * height + x) * 3
                              + 2] = m_pixels[(yy * height + xx) * 3 + 2];
            }
        }
    }
    delete[] m_pixels;
    m_pixels = rotatedPixels;
}