2022-03-05 16:15:00 +01:00
|
|
|
#include "BMP.h"
|
|
|
|
#include <iostream>
|
2022-03-05 19:30:41 +01:00
|
|
|
#include <fstream>
|
|
|
|
#include <stdexcept>
|
|
|
|
#include "BmpHeader.h"
|
2022-03-05 16:15:00 +01:00
|
|
|
|
2022-03-05 20:22:36 +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 {
|
|
|
|
|
2022-03-05 20:22:36 +01:00
|
|
|
BMP::BMP() {
|
|
|
|
// Do nothing
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-05 16:15:00 +01:00
|
|
|
BMP::BMP(const Eule::Vector2i &size, const Colormode& colormode)
|
|
|
|
:
|
2022-03-05 19:30:41 +01:00
|
|
|
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) {
|
2022-03-05 20:22:36 +01:00
|
|
|
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) {
|
2022-03-05 20:22:36 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-03-05 19:30:41 +01:00
|
|
|
bool BMP::Write(const std::string &filename) const {
|
2022-03-05 20:22:36 +01:00
|
|
|
CHECK_IF_INITIALIZED
|
2022-03-05 19:30:41 +01:00
|
|
|
|
|
|
|
// 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;
|
2022-03-05 19:30:41 +01:00
|
|
|
// 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
|
|
|
|
2022-03-05 19:30:41 +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
|
|
|
}
|
2022-03-05 19:30:41 +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) {
|
2022-03-05 20:22:36 +01:00
|
|
|
CHECK_IF_INITIALIZED
|
|
|
|
|
2022-03-05 19:30:41 +01:00
|
|
|
const std::size_t pixelIndex =
|
2022-03-05 19:37:02 +01:00
|
|
|
(position.y * size.x + position.x) * GetNumColorChannels();
|
2022-03-05 19:30:41 +01:00
|
|
|
|
|
|
|
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 {
|
2022-03-05 20:22:36 +01:00
|
|
|
CHECK_IF_INITIALIZED
|
|
|
|
|
2022-03-05 19:30:41 +01:00
|
|
|
const std::size_t pixelIndex =
|
2022-03-05 19:37:02 +01:00
|
|
|
(position.y * size.x + position.x) * GetNumColorChannels();
|
2022-03-05 19:30:41 +01:00
|
|
|
|
|
|
|
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,
|
2022-03-05 20:22:36 +01:00
|
|
|
const std::uint8_t a)
|
|
|
|
{
|
|
|
|
CHECK_IF_INITIALIZED
|
2022-03-05 19:30:41 +01:00
|
|
|
|
|
|
|
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() {
|
2022-03-05 20:22:36 +01:00
|
|
|
CHECK_IF_INITIALIZED
|
|
|
|
|
2022-03-05 19:37:02 +01:00
|
|
|
return pixelBuffer.data();
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::uint8_t *BMP::data() const {
|
2022-03-05 20:22:36 +01:00
|
|
|
CHECK_IF_INITIALIZED
|
|
|
|
|
2022-03-05 19:37:02 +01:00
|
|
|
return pixelBuffer.data();
|
|
|
|
}
|
|
|
|
|
|
|
|
const Eule::Vector2i &BMP::GetDimensions() const {
|
2022-03-05 20:22:36 +01:00
|
|
|
CHECK_IF_INITIALIZED
|
|
|
|
|
2022-03-05 19:37:02 +01:00
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
const Colormode &BMP::GetColormode() const {
|
2022-03-05 20:22:36 +01:00
|
|
|
CHECK_IF_INITIALIZED
|
|
|
|
|
2022-03-05 19:37:02 +01:00
|
|
|
return colormode;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::size_t BMP::GetNumColorChannels() const {
|
2022-03-05 20:22:36 +01:00
|
|
|
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 {
|
2022-03-05 20:22:36 +01:00
|
|
|
CHECK_IF_INITIALIZED
|
|
|
|
|
2022-03-05 19:37:02 +01:00
|
|
|
return pixelBuffer.size();
|
|
|
|
}
|
|
|
|
|
2022-03-05 20:22:36 +01:00
|
|
|
bool BMP::IsInitialized() const {
|
|
|
|
return isInitialized;
|
|
|
|
}
|
|
|
|
|
2022-03-05 16:15:00 +01:00
|
|
|
}
|
2022-03-05 20:22:36 +01:00
|
|
|
|
|
|
|
#undef CHECK_IF_INITIALIZED
|