From 808738ac2e288aec1db84cac29375172496df7f4 Mon Sep 17 00:00:00 2001 From: Leonetienne Date: Sat, 5 Mar 2022 22:00:57 +0100 Subject: [PATCH] Added Read method --- .gitignore | 6 ++ Exec/CMakeLists.txt | 1 + Exec/main.cpp | 16 +++- Src/BMP.cpp | 23 +++-- Src/BMP.h | 8 ++ Src/BmpReader.cpp | 199 ++++++++++++++++++++++++++++++++++++++++++++ Src/BmpReader.h | 36 ++++++++ Src/BmpWriter.cpp | 4 +- Src/CMakeLists.txt | 1 + 9 files changed, 277 insertions(+), 17 deletions(-) create mode 100644 Src/BmpReader.cpp create mode 100644 Src/BmpReader.h diff --git a/.gitignore b/.gitignore index df3544f..29aa1a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +# All images +*.bmp +*.jpg +*.jpeg +*.png + # Vim swap files *.swp diff --git a/Exec/CMakeLists.txt b/Exec/CMakeLists.txt index 2e8335f..356f283 100644 --- a/Exec/CMakeLists.txt +++ b/Exec/CMakeLists.txt @@ -4,6 +4,7 @@ project(BmpPP_exec) set(CMAKE_CXX_STANDARD 17) ADD_COMPILE_DEFINITIONS(_EULE_NO_INTRINSICS_) +ADD_COMPILE_DEFINITIONS(_BMPLIB_DEBUG_OUTPUT_) INCLUDE_DIRECTORIES(../Src/Eule/) INCLUDE_DIRECTORIES(../Src/) diff --git a/Exec/main.cpp b/Exec/main.cpp index 38b0e8a..6e0fde2 100644 --- a/Exec/main.cpp +++ b/Exec/main.cpp @@ -6,6 +6,7 @@ using namespace Leonetienne::BmpPP; int main() { + /* BMP bmp({800, 600}, Colormode::RGB); for (int x = 0; x < 800; x++) @@ -19,8 +20,19 @@ int main() { ); } - if (!bmp.Write("test.bmp")) - std::cerr << "What the hell" << std::endl; + */ + + BMP bmp; + + if (!bmp.Read("test.bmp")) + std::cerr << "Failed to read" << std::endl; + + std::cout << (bmp.GetColormode() == Colormode::RGB) << std::endl; + + if (!bmp.Write("testwrite.bmp")) + std::cerr << "What the hell" << std::endl; + + return 0; } diff --git a/Src/BMP.cpp b/Src/BMP.cpp index 894102e..a013695 100644 --- a/Src/BMP.cpp +++ b/Src/BMP.cpp @@ -1,8 +1,7 @@ #include "BMP.h" -#include -#include #include #include "BmpWriter.h" +#include "BmpReader.h" #define CHECK_IF_INITIALIZED if (!isInitialized) throw std::runtime_error("Image not initialized!"); @@ -13,6 +12,14 @@ namespace Leonetienne::BmpPP { return; } + + BMP::BMP(const std::string &filename) { + if(!Read(filename)) + throw std::runtime_error("Unable to read bmp image!"); + + return; + } + BMP::BMP(const Eule::Vector2i &size, const Colormode& colormode) : size { size }, @@ -57,17 +64,7 @@ namespace Leonetienne::BmpPP { } bool BMP::Read(const std::string &filename) { - - std::ifstream ifs(filename, std::ifstream::binary); - if (!ifs.good()) - 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... - - - return true; + return BmpReader::Read(*this, filename); } std::uint8_t *BMP::GetPixel(const Eule::Vector2i &position) { diff --git a/Src/BMP.h b/Src/BMP.h index 59ff50b..8528a8f 100644 --- a/Src/BMP.h +++ b/Src/BMP.h @@ -9,12 +9,19 @@ namespace Leonetienne::BmpPP { class BmpWriter; + class BmpReader; class BMP { public: + // Will create an uninitialized image BMP(); + + //! Will create an image with the entire pixel buffer set to 0 explicit BMP(const Eule::Vector2i& size, const Colormode& colormode = Colormode::RGBA); + //! Will create a image and read it from a bmp file + explicit BMP(const std::string& filename); + //! Will return a pointer to the first byte of a pixel at a given position std::uint8_t* GetPixel(const Eule::Vector2i& position); @@ -66,6 +73,7 @@ namespace Leonetienne::BmpPP { bool isInitialized = false; friend class BmpWriter; + friend class BmpReader; }; } diff --git a/Src/BmpReader.cpp b/Src/BmpReader.cpp new file mode 100644 index 0000000..0468dc2 --- /dev/null +++ b/Src/BmpReader.cpp @@ -0,0 +1,199 @@ +#include "BmpReader.h" +#include "Colormodes.h" +#include "BMP.h" +#include +#include + +#ifdef _BMPLIB_DEBUG_OUTPUT_ +#include +#endif + +namespace Leonetienne::BmpPP { + + bool BmpReader::Read(BMP &image, const std::string &filename) { + // Open file + std::ifstream ifs(filename, std::ifstream::binary); + if (!ifs.good()) + return false; + + // When reading the image, we don't care about most of the header. + // All we want is the images resolution, bits-per-pixel, and the pixelbuffer address... + // This is because we do not support ALL of bmp, but just the most common formats... + + // Fetch bmp signature + std::uint16_t signature; + ReadBytes(ifs, signature); + + // Does the signature match? + if (signature != 0x4D42) { + #ifdef _BMPLIB_DEBUG_OUTPUT_ + std::cerr << "Bmp signature doesn't match" << std::endl; + #endif + ifs.close(); + return false; + } + + // Skip eight useless bytes (filesize + reserved0 + reserved1) + ifs.ignore(8); + + // Read the address of the pixel buffer + std::uint32_t pixelBufferAddress; + ReadBytes(ifs, pixelBufferAddress); + + // Read the size of the dib header + std::uint32_t dibHeaderSize; + ReadBytes(ifs, dibHeaderSize); + + std::uint32_t width; + std::uint32_t height; + std::uint16_t bitsPerPixel; + + // Check if what kind of dib header we're dealing with + // These are the two we're supporting (to read, at least). + if (dibHeaderSize == 12) { + // BITMAPCOREHEADER it is + + // Read the width and the height (both 2-byte ints) + std::uint16_t width_2byte; + std::uint16_t height_2byte; + + ifs >> width_2byte; + ifs >> height_2byte; + ReadBytes(ifs, width_2byte); + ReadBytes(ifs, height_2byte); + + width = width_2byte; + height = height_2byte; + + // Read the bits per pixel + ReadBytes(ifs, bitsPerPixel); + } + else if (dibHeaderSize == 40) { + // BITMAPINFOHEADER it is + + // Read the width and height (both 4-byte signed ints) + std::int32_t width_4byte_signed; + std::int32_t height_4byte_signed; + + ReadBytes(ifs, width_4byte_signed); + ReadBytes(ifs, height_4byte_signed); + + width = width_4byte_signed; + height = height_4byte_signed; + + // Skip two useless bytes + ifs.ignore(2); + + // Read the bits per pixel value + ReadBytes(ifs, bitsPerPixel); + } + else if (dibHeaderSize == 124) { + // BITMAPV5HEADER it is + + // Read the width and height + ReadBytes(ifs, width); + ReadBytes(ifs, height); + + // Skip two useless bytes + ifs.ignore(2); + + // Read the bits per pixel value + ReadBytes(ifs, bitsPerPixel); + } + else { + #ifdef _BMPLIB_DEBUG_OUTPUT_ + std::cerr << "Unsupported dib header found. Dib header size: " << dibHeaderSize << " bytes." << std::endl; + #endif + ifs.close(); + return false; + } + + // Convert bits per pixel to color mode + Colormode colormode; + switch (bitsPerPixel) { + case 32: + colormode = Colormode::RGBA; + break; + + case 24: + colormode = Colormode::RGB; + break; + + default: + #ifdef _BMPLIB_DEBUG_OUTPUT_ + std::cerr << "Unsupported pixel format found (bits per pixel). Bits per pixel: " << bitsPerPixel << std::endl; + #endif + ifs.close(); + return false; + } + + // Now read the pixel array + // Pre-calculate the channel count + const std::size_t numChannels = bitsPerPixel / 8; + + // Calculate how much padding there should be between each row + const std::size_t paddingBytesPerRow = (4 - ((width * numChannels) % 4)) % 4; + + // Calculate how long bytes we should care about per row (so, excluding padding) + const std::size_t rowWidth = width * numChannels; + + // Create the pixel buffer + std::vector pixelArray; + pixelArray.resize(width * height * numChannels); + + // Set the reading-head to the address of the pixel array + ifs.seekg(pixelBufferAddress); + + // Iterate over all rows, and skip padding after each row + for (std::int64_t y = height-1; y >= 0; y--) { + const std::size_t rowIndex = y * width * numChannels; + + for (std::size_t x = 0; x < width; x++) { + const std::size_t pixelIndex = rowIndex + x * numChannels; + + switch (colormode) { + case Colormode::RGBA: { + std::uint8_t r, g, b, a; + ReadBytes(ifs, b); + ReadBytes(ifs, g); + ReadBytes(ifs, r); + ReadBytes(ifs, a); + + pixelArray[pixelIndex + 0] = r; + pixelArray[pixelIndex + 1] = g; + pixelArray[pixelIndex + 2] = b; + pixelArray[pixelIndex + 3] = a; + } + break; + + case Colormode::RGB: { + std::uint8_t r, g, b; + ReadBytes(ifs, b); + ReadBytes(ifs, g); + ReadBytes(ifs, r); + + pixelArray[pixelIndex + 0] = r; + pixelArray[pixelIndex + 1] = g; + pixelArray[pixelIndex + 2] = b; + } + break; + } + } + + if (paddingBytesPerRow > 0) + ifs.ignore(paddingBytesPerRow); + } + + // We are done with the file + ifs.close(); + + // Now construct our image + image.ReInitialize(Eule::Vector2i(width, height), colormode); + + // We're a friend-class, so we're going to MOVE (not copy) the pixels over manually. Much faster. + image.pixelBuffer.swap(pixelArray); + + // Done. + return true; + } +} diff --git a/Src/BmpReader.h b/Src/BmpReader.h new file mode 100644 index 0000000..fa3cb90 --- /dev/null +++ b/Src/BmpReader.h @@ -0,0 +1,36 @@ +#ifndef BMPPP_BMPREADER_H +#define BMPPP_BMPREADER_H + +#include +#include + +namespace Leonetienne::BmpPP { + + class BMP; + + class BmpReader { + public: + static bool Read(BMP& image, const std::string& filename); + + private: + // Will read sizeof(T) bytes of is into buffer + template + static std::ifstream& ReadBytes(std::ifstream& is, T& buffer) { + const std::size_t sizeofT = sizeof(T); + buffer = 0x0; + std::uint8_t buf; + + for (std::size_t i = 0; i < sizeofT; i++) + { + is.read((char*)&buf, 1); + T bbuf = buf << (i * 8); + buffer |= bbuf; + } + + return is; + } + }; + +} + +#endif //BMPP_BMPREADER_H diff --git a/Src/BmpWriter.cpp b/Src/BmpWriter.cpp index e2d5fff..ab513a8 100644 --- a/Src/BmpWriter.cpp +++ b/Src/BmpWriter.cpp @@ -33,10 +33,10 @@ namespace Leonetienne::BmpPP { 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; + const 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++) { + for (std::int64_t y = image.size.y-1; y >= 0; y--) { const std::size_t rowIndex = y * image.size.x * numChannels; for (std::size_t x = 0; x < image.size.x; x++) { diff --git a/Src/CMakeLists.txt b/Src/CMakeLists.txt index 6960093..b3d93d3 100644 --- a/Src/CMakeLists.txt +++ b/Src/CMakeLists.txt @@ -12,5 +12,6 @@ add_library(BmpPP BMP.cpp BmpWriter.cpp + BmpReader.cpp BmpHeader.cpp )