Added Read method
This commit is contained in:
parent
33932906c4
commit
808738ac2e
6
.gitignore
vendored
6
.gitignore
vendored
@ -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
|
||||
|
||||
|
@ -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/)
|
||||
|
@ -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"))
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
23
Src/BMP.cpp
23
Src/BMP.cpp
@ -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) {
|
||||
|
@ -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
199
Src/BmpReader.cpp
Normal 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
36
Src/BmpReader.h
Normal 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
|
@ -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++) {
|
||||
|
@ -12,5 +12,6 @@ add_library(BmpPP
|
||||
|
||||
BMP.cpp
|
||||
BmpWriter.cpp
|
||||
BmpReader.cpp
|
||||
BmpHeader.cpp
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user