Basic implementation, and write function

This commit is contained in:
Leonetienne 2022-03-05 19:30:41 +01:00
parent 0cd22f3bd2
commit 14294fcebf
6 changed files with 333 additions and 4 deletions

View File

@ -1,9 +1,26 @@
#include <BMP.h> #include <BMP.h>
#include <iostream>
#include <BmpHeader.h>
using namespace Leonetienne::BmpPP; using namespace Leonetienne::BmpPP;
int main() { int main() {
BMP bmp({800, 600});
BMP bmp({800, 600}, Colormode::RGB);
for (int x = 0; x < 800; x++)
for (int y = 0; y < 600; y++) {
bmp.SetPixel({x, y},
(std::uint8_t)( ((double)x / 800.0) * 255.0),
(std::uint8_t)((1.0 - ((double)x / 800.0)) * 255.0),
(std::uint8_t)((1.0 - ((double)y / 800.0)) * 255.0),
255
);
}
if (!bmp.Write("test.bmp"))
std::cerr << "What the hell" << std::endl;
return 0; return 0;
} }

View File

@ -1,11 +1,15 @@
#include "BMP.h" #include "BMP.h"
#include <iostream> #include <iostream>
#include <fstream>
#include <stdexcept>
#include "BmpHeader.h"
namespace Leonetienne::BmpPP { namespace Leonetienne::BmpPP {
BMP::BMP(const Eule::Vector2i &size, const Colormode& colormode) BMP::BMP(const Eule::Vector2i &size, const Colormode& colormode)
: :
size { size } size { size },
colormode { colormode }
{ {
pixelBuffer.clear(); pixelBuffer.clear();
@ -19,11 +23,177 @@ namespace Leonetienne::BmpPP {
switch (colormode) { switch (colormode) {
case Colormode::RGB: case Colormode::RGB:
return 3; return 3;
case Colormode::RGBA: case Colormode::RGBA:
return 4; return 4;
// Unreachable
} }
// Unreachable
return -1;
}
bool BMP::Write(const std::string &filename) const {
// 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;
bmpHeader.dibHeader.numBitsPerPixel = ColormodeToPixelSize(colormode) * 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?
const std::size_t numChannels = ColormodeToPixelSize(colormode);
// 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);
}
}
// 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) {
const std::size_t pixelIndex =
(position.y * size.x + position.x) * ColormodeToPixelSize(colormode);
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 {
const std::size_t pixelIndex =
(position.y * size.x + position.x) * ColormodeToPixelSize(colormode);
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) {
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;
} }
} }

View File

@ -12,11 +12,24 @@ namespace Leonetienne::BmpPP {
public: public:
explicit BMP(const Eule::Vector2i& size, const Colormode& colormode = Colormode::RGBA); explicit BMP(const Eule::Vector2i& size, const Colormode& colormode = Colormode::RGBA);
//! Will write the bmp image to a file.
bool Write(const std::string& filename) const;
//! Will return a pointer to the first byte of a pixel at a given position
std::uint8_t* GetPixel(const Eule::Vector2i& position);
//! Will return a pointer to the first byte of a pixel at a given position
const std::uint8_t* GetPixel(const Eule::Vector2i& position) const;
//! Will set the color of a pixel at a given position
void 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 = 0xFF);
private: private:
//! Will return the corresponding pixel size (in bytes) of a colormode. Like, 3 for RGB and 4 for RGBA. //! Will return the corresponding pixel size (in bytes) of a colormode. Like, 3 for RGB and 4 for RGBA.
static int ColormodeToPixelSize(const Colormode& colormode); static int ColormodeToPixelSize(const Colormode& colormode);
Eule::Vector2i size; Eule::Vector2i size;
Colormode colormode;
std::vector<std::uint8_t> pixelBuffer; std::vector<std::uint8_t> pixelBuffer;
}; };

30
Src/BmpHeader.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "BmpHeader.h"
namespace Leonetienne::BmpPP {
std::vector<std::uint8_t> BmpHeader::ToBytes() const {
std::vector<std::uint8_t> data;
InsertBytes(data, fileHeader.signature);
InsertBytes(data, fileHeader.filesize);
InsertBytes(data, fileHeader.reserved0);
InsertBytes(data, fileHeader.reserved1);
InsertBytes(data, fileHeader.addressPixelBuffer);
InsertBytes(data, dibHeader.dibHeaderSize);
InsertBytes(data, dibHeader.imageWidth);
InsertBytes(data, dibHeader.imageHeight);
InsertBytes(data, dibHeader.numColorPlanes);
InsertBytes(data, dibHeader.numBitsPerPixel);
InsertBytes(data, dibHeader.compressionUsed);
InsertBytes(data, dibHeader.pixelArraySize);
InsertBytes(data, dibHeader.printResolutionHorizontal);
InsertBytes(data, dibHeader.printResolutionVertical);
InsertBytes(data, dibHeader.numColorsInPalette);
InsertBytes(data, dibHeader.numImportantColors);
return data;
}
}

98
Src/BmpHeader.h Normal file
View File

@ -0,0 +1,98 @@
#ifndef BMPPP_EXEC_BMPHEADER_H
#define BMPPP_EXEC_BMPHEADER_H
#include <cstdint>
#include <vector>
namespace Leonetienne::BmpPP {
// This struct represents the actual image file.
class BmpHeader {
public:
//! This header contains information about the file itself.
struct FileHeader {
//! Constant value. These two bytes identify a bmp file.
const std::uint16_t signature = 0x4D42;
//! The size of the entire file, in bytes.
std::uint32_t filesize;
//! Reserved...
std::uint16_t reserved0;
//! Reserved...
std::uint16_t reserved1;
//! The address where the pixel buffer / pixel array begins.
std::uint32_t addressPixelBuffer;
// Since C++ tends to pad structures, we have to explicitly define how many bytes
// this struct is long. For example, sizeof(FileHeader) (without the size field)
// returns 16, even though it really should only be 14.
// This has to be set at the END of the struct, as to not be written to the file itself.
static constexpr std::uint16_t NBYTES = 14;
} fileHeader;
//! This header contains information about the image itself.
//! We are using the BITMAPINFOHEADER format.
struct DibHeader {
//! The size of the dibHeader (since there are multiple to choose from).
//! This one 40 bytes, fix.
const std::uint32_t dibHeaderSize = 40;
//! The width of the image, in pixels.
std::int32_t imageWidth;
//! The height of the image, in pixels.
std::int32_t imageHeight;
//! The amount of color planes used. Always 1.
const std::uint16_t numColorPlanes = 1;
//! The amount of bits (not bytes!) per pixel.
std::uint16_t numBitsPerPixel;
//! We don't use any compression.
const std::uint32_t compressionUsed = 0;
//! The size of the pixel array (including row padding)
std::uint32_t pixelArraySize;
//! The amount of pixels per meter on the horizontal axis. Kinda useless for our purposes, but required to be in the header.
const std::int32_t printResolutionHorizontal = 2835;
//! The amount of pixels per meter on the vertical axis. Kinda useless for our purposes, but required to be in the header.
const std::int32_t printResolutionVertical = 2835;
//! We don't have any colors in our color palette
const std::uint32_t numColorsInPalette = 0;
//! All colors are important
const std::uint32_t numImportantColors = 0;
// This has to be set at the END of the struct, as to not be written to the file itself.
static constexpr std::uint16_t NBYTES = 40;
} dibHeader;
//! Will return bytes representing the header
//! Just dumping the memory won't work, because the compiler may insert padding in between members
std::vector<std::uint8_t> ToBytes() const;
private:
template <typename T>
void InsertBytes(std::vector<std::uint8_t>& vec, const T& value) const {
vec.insert(
vec.cend(),
static_cast<const std::uint8_t*>(static_cast<const void*>(&value)),
static_cast<const std::uint8_t*>(static_cast<const void*>(&value)) + sizeof(value)
);
return;
};
};
}
#endif //BMPPP_EXEC_BMPHEADER_H

View File

@ -11,4 +11,5 @@ add_library(BmpPP
${Eule} ${Eule}
BMP.cpp BMP.cpp
BmpHeader.cpp
) )