Basic implementation, and write function
This commit is contained in:
parent
0cd22f3bd2
commit
14294fcebf
@ -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;
|
||||||
}
|
}
|
||||||
|
172
Src/BMP.cpp
172
Src/BMP.cpp
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
13
Src/BMP.h
13
Src/BMP.h
@ -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
30
Src/BmpHeader.cpp
Normal 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
98
Src/BmpHeader.h
Normal 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
|
@ -11,4 +11,5 @@ add_library(BmpPP
|
|||||||
${Eule}
|
${Eule}
|
||||||
|
|
||||||
BMP.cpp
|
BMP.cpp
|
||||||
|
BmpHeader.cpp
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user