BmpPP/Src/BMP.cpp

276 lines
8.1 KiB
C++
Raw Normal View History

2022-03-05 16:15:00 +01:00
#include "BMP.h"
#include <iostream>
#include <fstream>
#include <stdexcept>
#include "BmpHeader.h"
2022-03-05 16:15:00 +01:00
#define CHECK_IF_INITIALIZED if (!isInitialized) throw std::runtime_error("Image not initialized!");
2022-03-05 16:15:00 +01:00
namespace Leonetienne::BmpPP {
BMP::BMP() {
// Do nothing
return;
}
2022-03-05 16:15:00 +01:00
BMP::BMP(const Eule::Vector2i &size, const Colormode& colormode)
:
size { size },
colormode { colormode }
2022-03-05 16:15:00 +01:00
{
2022-03-05 19:51:57 +01:00
ReInitialize(size, colormode);
2022-03-05 16:15:00 +01:00
2022-03-05 19:51:57 +01:00
return;
}
void BMP::ReInitialize(const Eule::Vector2i &size) {
isInitialized = true;
2022-03-05 19:51:57 +01:00
// Carry over new attributes
this->size = size;
// Re-initialize the pixelbuffer
pixelBuffer.clear();
pixelBuffer.resize(size.x * size.y * GetNumColorChannels());
return;
}
void BMP::ReInitialize(const Eule::Vector2i &size, const Colormode &colormode) {
isInitialized = true;
2022-03-05 19:51:57 +01:00
// Carry over new attributes
this->size = size;
this->colormode = colormode;
// Re-initialize the pixelbuffer
2022-03-05 16:15:00 +01:00
pixelBuffer.clear();
2022-03-05 19:37:02 +01:00
pixelBuffer.resize(size.x * size.y * GetNumColorChannels());
2022-03-05 16:15:00 +01:00
return;
}
bool BMP::Write(const std::string &filename) const {
CHECK_IF_INITIALIZED
// Create the bmp header
BmpHeader bmpHeader;
// Populate file header
bmpHeader.fileHeader.addressPixelBuffer =
BmpHeader::FileHeader::NBYTES +
BmpHeader::DibHeader::NBYTES
+ 2; // We have to put two padding-bytes behind the headers.
// The pixel data must start at an address that is a multiple of 4.
// The size of the bmp file is not yet known...
// Populate dib header
bmpHeader.dibHeader.imageWidth = size.x;
bmpHeader.dibHeader.imageHeight = size.y;
2022-03-05 19:37:02 +01:00
bmpHeader.dibHeader.numBitsPerPixel = GetNumColorChannels() * 8;
// The size of the pixel array is not known yet (because rows require to be padded)
// Pack pixel values
std::vector<std::uint8_t> packedPixels;
packedPixels.reserve(pixelBuffer.size());
// How many channels do we have?
2022-03-05 19:37:02 +01:00
const std::size_t numChannels = GetNumColorChannels();
2022-03-05 16:15:00 +01:00
// Calculate how many padding bytes to add per row
std::size_t paddingBytesPerRow = (4 - ((size.x * numChannels) % 4)) % 4;
// Iterate over all pixel rows
for (std::size_t y = 0; y < size.y; y++) {
const std::size_t rowIndex = y * size.x * numChannels;
for (std::size_t x = 0; x < size.x; x++) {
const std::size_t pixelIndex = rowIndex + x * numChannels;
// Write actual pixel values to row
switch (colormode) {
case Colormode::RGBA:
// Write B byte
packedPixels.insert(
packedPixels.cend(),
pixelBuffer[pixelIndex + 2]
);
// Write G byte
packedPixels.insert(
packedPixels.cend(),
pixelBuffer[pixelIndex + 1]
);
// Write R byte
packedPixels.insert(
packedPixels.cend(),
pixelBuffer[pixelIndex + 0]
);
// Write A byte
packedPixels.insert(
packedPixels.cend(),
pixelBuffer[pixelIndex + 3]
);
break;
case Colormode::RGB:
// Write B byte
packedPixels.insert(
packedPixels.cend(),
pixelBuffer[pixelIndex + 2]
);
// Write G byte
packedPixels.insert(
packedPixels.cend(),
pixelBuffer[pixelIndex + 1]
);
// Write R byte
packedPixels.insert(
packedPixels.cend(),
pixelBuffer[pixelIndex + 0]
);
break;
}
// Add row padding
// Since we have to pad to a multiple of 4, the padding will never be more than four bytes
for (std::size_t i = 0; i < paddingBytesPerRow; i++)
packedPixels.insert(packedPixels.cend(), 0x0);
}
2022-03-05 16:15:00 +01:00
}
// Now we can finally set the fileSize field in the file header,
// and the pixelArraySize in the dib header
bmpHeader.fileHeader.filesize =
bmpHeader.fileHeader.addressPixelBuffer +
packedPixels.size();
bmpHeader.dibHeader.pixelArraySize = packedPixels.size();
// Write to file
std::ofstream ofs(filename, std::ofstream::binary);
if (!ofs.good())
return false;
// Write the header
const std::vector<std::uint8_t> headerBytes = bmpHeader.ToBytes();
ofs.write((const char*)headerBytes.data(), headerBytes.size());
// Write two padding bytes
ofs.write("\0\0", 2);
// Write the pixel data
ofs.write((const char*)packedPixels.data(), packedPixels.size());
ofs.close();
return true;
}
std::uint8_t *BMP::GetPixel(const Eule::Vector2i &position) {
CHECK_IF_INITIALIZED
const std::size_t pixelIndex =
2022-03-05 19:37:02 +01:00
(position.y * size.x + position.x) * GetNumColorChannels();
if (pixelIndex >= pixelBuffer.size())
throw std::runtime_error("Pixel index out of range!");
return pixelBuffer.data() + pixelIndex;
}
const std::uint8_t *BMP::GetPixel(const Eule::Vector2i &position) const {
CHECK_IF_INITIALIZED
const std::size_t pixelIndex =
2022-03-05 19:37:02 +01:00
(position.y * size.x + position.x) * GetNumColorChannels();
if (pixelIndex >= pixelBuffer.size())
throw std::runtime_error("Pixel index out of range!");
return pixelBuffer.data() + pixelIndex;
}
void BMP::SetPixel(const Eule::Vector2i &position,
const std::uint8_t r,
const std::uint8_t g,
const std::uint8_t b,
const std::uint8_t a)
{
CHECK_IF_INITIALIZED
std::uint8_t* pixel = GetPixel(position);
switch (colormode) {
case Colormode::RGBA:
pixel[0] = r;
pixel[1] = g;
pixel[2] = b;
pixel[3] = a;
break;
case Colormode::RGB:
pixel[0] = r;
pixel[1] = g;
pixel[2] = b;
break;
}
return;
2022-03-05 16:15:00 +01:00
}
2022-03-05 19:37:02 +01:00
std::uint8_t *BMP::data() {
CHECK_IF_INITIALIZED
2022-03-05 19:37:02 +01:00
return pixelBuffer.data();
}
const std::uint8_t *BMP::data() const {
CHECK_IF_INITIALIZED
2022-03-05 19:37:02 +01:00
return pixelBuffer.data();
}
const Eule::Vector2i &BMP::GetDimensions() const {
CHECK_IF_INITIALIZED
2022-03-05 19:37:02 +01:00
return size;
}
const Colormode &BMP::GetColormode() const {
CHECK_IF_INITIALIZED
2022-03-05 19:37:02 +01:00
return colormode;
}
std::size_t BMP::GetNumColorChannels() const {
CHECK_IF_INITIALIZED
2022-03-05 19:37:02 +01:00
switch (colormode) {
case Colormode::RGB:
return 3;
case Colormode::RGBA:
return 4;
}
// Unreachable
return -1;
}
std::size_t BMP::GetPixelbufferSize() const {
CHECK_IF_INITIALIZED
2022-03-05 19:37:02 +01:00
return pixelBuffer.size();
}
bool BMP::IsInitialized() const {
return isInitialized;
}
2022-03-05 16:15:00 +01:00
}
#undef CHECK_IF_INITIALIZED