Refactor
This commit is contained in:
parent
f993241e9f
commit
33932906c4
117
Src/BMP.cpp
117
Src/BMP.cpp
@ -2,7 +2,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include "BmpHeader.h"
|
#include "BmpWriter.h"
|
||||||
|
|
||||||
#define CHECK_IF_INITIALIZED if (!isInitialized) throw std::runtime_error("Image not initialized!");
|
#define CHECK_IF_INITIALIZED if (!isInitialized) throw std::runtime_error("Image not initialized!");
|
||||||
|
|
||||||
@ -53,118 +53,19 @@ namespace Leonetienne::BmpPP {
|
|||||||
bool BMP::Write(const std::string &filename) const {
|
bool BMP::Write(const std::string &filename) const {
|
||||||
CHECK_IF_INITIALIZED
|
CHECK_IF_INITIALIZED
|
||||||
|
|
||||||
// Create the bmp header
|
return BmpWriter::Write(*this, filename);
|
||||||
BmpHeader bmpHeader;
|
}
|
||||||
|
|
||||||
// Populate file header
|
bool BMP::Read(const std::string &filename) {
|
||||||
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...
|
std::ifstream ifs(filename, std::ifstream::binary);
|
||||||
|
if (!ifs.good())
|
||||||
// Populate dib header
|
|
||||||
bmpHeader.dibHeader.imageWidth = size.x;
|
|
||||||
bmpHeader.dibHeader.imageHeight = size.y;
|
|
||||||
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?
|
|
||||||
const std::size_t numChannels = GetNumColorChannels();
|
|
||||||
|
|
||||||
// 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;
|
return false;
|
||||||
|
|
||||||
|
// When reading the image, we don't care about most of the header.
|
||||||
|
// All we want is the images resolutions, bits-per-pixel, and pixelbuffer address...
|
||||||
|
// This is because we do not support ALL of bmp, but just the most common formats...
|
||||||
|
|
||||||
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
namespace Leonetienne::BmpPP {
|
namespace Leonetienne::BmpPP {
|
||||||
|
|
||||||
|
class BmpWriter;
|
||||||
|
|
||||||
class BMP {
|
class BMP {
|
||||||
public:
|
public:
|
||||||
BMP();
|
BMP();
|
||||||
@ -53,11 +55,17 @@ namespace Leonetienne::BmpPP {
|
|||||||
//! Returns false, if unable to open the file
|
//! Returns false, if unable to open the file
|
||||||
bool Write(const std::string& filename) const;
|
bool Write(const std::string& filename) const;
|
||||||
|
|
||||||
|
//! Will read a bmp image from a file.
|
||||||
|
//! Returns false, if unable to open, or parse, file
|
||||||
|
bool Read(const std::string& filename);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Eule::Vector2i size;
|
Eule::Vector2i size;
|
||||||
Colormode colormode;
|
Colormode colormode;
|
||||||
std::vector<std::uint8_t> pixelBuffer;
|
std::vector<std::uint8_t> pixelBuffer;
|
||||||
bool isInitialized = false;
|
bool isInitialized = false;
|
||||||
|
|
||||||
|
friend class BmpWriter;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
124
Src/BmpWriter.cpp
Normal file
124
Src/BmpWriter.cpp
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
#include "BmpWriter.h"
|
||||||
|
#include "BmpHeader.h"
|
||||||
|
#include "Bmp.h"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace Leonetienne::BmpPP {
|
||||||
|
|
||||||
|
bool BmpWriter::Write(const BMP &image, const std::string& filename) {
|
||||||
|
// 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 = image.size.x;
|
||||||
|
bmpHeader.dibHeader.imageHeight = image.size.y;
|
||||||
|
bmpHeader.dibHeader.numBitsPerPixel = image.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(image.pixelBuffer.size());
|
||||||
|
|
||||||
|
// How many channels do we have?
|
||||||
|
const std::size_t numChannels = image.GetNumColorChannels();
|
||||||
|
|
||||||
|
// Calculate how many padding bytes to add per row
|
||||||
|
std::size_t paddingBytesPerRow = (4 - ((image.size.x * numChannels) % 4)) % 4;
|
||||||
|
|
||||||
|
// Iterate over all pixel rows
|
||||||
|
for (std::size_t y = 0; y < image.size.y; y++) {
|
||||||
|
const std::size_t rowIndex = y * image.size.x * numChannels;
|
||||||
|
|
||||||
|
for (std::size_t x = 0; x < image.size.x; x++) {
|
||||||
|
const std::size_t pixelIndex = rowIndex + x * numChannels;
|
||||||
|
|
||||||
|
// Write actual pixel values to row
|
||||||
|
switch (image.colormode) {
|
||||||
|
case Colormode::RGBA:
|
||||||
|
// Write B byte
|
||||||
|
packedPixels.insert(
|
||||||
|
packedPixels.cend(),
|
||||||
|
image.pixelBuffer[pixelIndex + 2]
|
||||||
|
);
|
||||||
|
// Write G byte
|
||||||
|
packedPixels.insert(
|
||||||
|
packedPixels.cend(),
|
||||||
|
image.pixelBuffer[pixelIndex + 1]
|
||||||
|
);
|
||||||
|
// Write R byte
|
||||||
|
packedPixels.insert(
|
||||||
|
packedPixels.cend(),
|
||||||
|
image.pixelBuffer[pixelIndex + 0]
|
||||||
|
);
|
||||||
|
// Write A byte
|
||||||
|
packedPixels.insert(
|
||||||
|
packedPixels.cend(),
|
||||||
|
image.pixelBuffer[pixelIndex + 3]
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Colormode::RGB:
|
||||||
|
// Write B byte
|
||||||
|
packedPixels.insert(
|
||||||
|
packedPixels.cend(),
|
||||||
|
image.pixelBuffer[pixelIndex + 2]
|
||||||
|
);
|
||||||
|
// Write G byte
|
||||||
|
packedPixels.insert(
|
||||||
|
packedPixels.cend(),
|
||||||
|
image.pixelBuffer[pixelIndex + 1]
|
||||||
|
);
|
||||||
|
// Write R byte
|
||||||
|
packedPixels.insert(
|
||||||
|
packedPixels.cend(),
|
||||||
|
image.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;
|
||||||
|
}
|
||||||
|
}
|
17
Src/BmpWriter.h
Normal file
17
Src/BmpWriter.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#ifndef TEST_BMPWRITER_H
|
||||||
|
#define TEST_BMPWRITER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Leonetienne::BmpPP {
|
||||||
|
|
||||||
|
class BMP;
|
||||||
|
|
||||||
|
class BmpWriter {
|
||||||
|
public:
|
||||||
|
static bool Write(const BMP& image, const std::string& filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //TEST_BMPWRITER_H
|
@ -11,5 +11,6 @@ add_library(BmpPP
|
|||||||
${Eule}
|
${Eule}
|
||||||
|
|
||||||
BMP.cpp
|
BMP.cpp
|
||||||
|
BmpWriter.cpp
|
||||||
BmpHeader.cpp
|
BmpHeader.cpp
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user