Added Read method

This commit is contained in:
Leonetienne 2022-03-05 22:00:57 +01:00
parent 33932906c4
commit 808738ac2e
9 changed files with 277 additions and 17 deletions

6
.gitignore vendored
View File

@ -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

View File

@ -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/)

View File

@ -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;
}

View File

@ -1,8 +1,7 @@
#include "BMP.h"
#include <iostream>
#include <fstream>
#include <stdexcept>
#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) {

View File

@ -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;
};
}

199
Src/BmpReader.cpp Normal file
View File

@ -0,0 +1,199 @@
#include "BmpReader.h"
#include "Colormodes.h"
#include "BMP.h"
#include <fstream>
#include <cstdint>
#ifdef _BMPLIB_DEBUG_OUTPUT_
#include <iostream>
#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<std::uint8_t> 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;
}
}

36
Src/BmpReader.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef BMPPP_BMPREADER_H
#define BMPPP_BMPREADER_H
#include <string>
#include <fstream>
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 <typename T>
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

View File

@ -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++) {

View File

@ -12,5 +12,6 @@ add_library(BmpPP
BMP.cpp
BmpWriter.cpp
BmpReader.cpp
BmpHeader.cpp
)