diff --git a/openVulkanoCpp/Image/ImageLoaderPfm.cpp b/openVulkanoCpp/Image/ImageLoaderPfm.cpp index 8ac8feb..8c4931b 100644 --- a/openVulkanoCpp/Image/ImageLoaderPfm.cpp +++ b/openVulkanoCpp/Image/ImageLoaderPfm.cpp @@ -14,6 +14,33 @@ #include #include +namespace +{ + struct PfmImageHeader + { + int width; + int height; + bool isColor; + bool isLittleEndian; + }; + + PfmImageHeader ParseHeader(std::istringstream& stream) + { + PfmImageHeader header; + std::string magic; + stream >> magic; + header.isColor = (magic == "PF"); + + float endianess; + stream >> header.width >> header.height >> endianess; + stream.ignore(); + + header.isLittleEndian = endianess < 0; + + return header; + } +} + namespace OpenVulkano::Image { std::unique_ptr ImageLoaderPfm::loadFromFile(const std::string& filePath) @@ -31,12 +58,21 @@ namespace OpenVulkano::Image std::unique_ptr ImageLoaderPfm::loadFromMemory(const std::vector& buffer) { std::istringstream stream(std::string(buffer.begin(), buffer.end()), std::ios::binary); - PfmImageHeader header = parseHeader(stream); + PfmImageHeader header = ParseHeader(stream); size_t numChannels = header.isColor ? 3 : 1; size_t dataSize = header.width * header.height * numChannels * sizeof(float); - std::vector data(header.width * header.height * numChannels); - stream.read(reinterpret_cast(data.data()), dataSize); + Array data(header.width * header.height * numChannels); + size_t rowSize = header.width * numChannels * sizeof(float); + + for (int y = header.height - 1; y >= 0; --y) + { + stream.read(reinterpret_cast(&data[y * header.width * numChannels]), rowSize); + if (!stream) + { + throw std::runtime_error("Error reading PFM image data"); + } + } if (!header.isLittleEndian) { @@ -47,20 +83,10 @@ namespace OpenVulkano::Image } } - for (int y = 0; y < header.height / 2; ++y) - { - for (int x = 0; x < header.width * numChannels; ++x) - { - std::swap(data[y * header.width * numChannels + x], - data[(header.height - 1 - y) * header.width * numChannels + x]); - } - } - auto image = std::make_unique(); image->resolution = { static_cast(header.width), static_cast(header.height), 1 }; image->dataFormat = header.isColor ? DataFormat::Format::R32G32B32_SFLOAT : DataFormat::Format::R32_SFLOAT; - image->data = Array(dataSize); - memcpy(image->data.Data(), data.data(), dataSize); + image->data = std::move(*reinterpret_cast*>(&data)); return image; } @@ -74,23 +100,7 @@ namespace OpenVulkano::Image std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); std::istringstream stream(std::string(buffer.begin(), buffer.end()), std::ios::binary); - PfmImageHeader header = parseHeader(stream); + PfmImageHeader header = ParseHeader(stream); return { header.width, header.height }; } - - ImageLoaderPfm::PfmImageHeader ImageLoaderPfm::parseHeader(std::istringstream& stream) - { - ImageLoaderPfm::PfmImageHeader header; - std::string magic; - stream >> magic; - header.isColor = (magic == "PF"); - - float endianess; - stream >> header.width >> header.height >> endianess; - stream.ignore(); - - header.isLittleEndian = endianess < 0; - - return header; - } } \ No newline at end of file diff --git a/openVulkanoCpp/Image/ImageLoaderPfm.hpp b/openVulkanoCpp/Image/ImageLoaderPfm.hpp index 1011bc7..e823888 100644 --- a/openVulkanoCpp/Image/ImageLoaderPfm.hpp +++ b/openVulkanoCpp/Image/ImageLoaderPfm.hpp @@ -12,20 +12,9 @@ namespace OpenVulkano::Image { class ImageLoaderPfm : public IImageLoader { - struct PfmImageHeader - { - int width; - int height; - bool isColor; - bool isLittleEndian; - }; - public: std::unique_ptr loadFromFile(const std::string& filePath) override; std::unique_ptr loadFromMemory(const std::vector& buffer) override; Math::Vector2i GetImageDimensions(const std::string& filename) override; - - private: - PfmImageHeader parseHeader(std::istringstream& stream); }; } \ No newline at end of file diff --git a/openVulkanoCpp/Image/ImageLoaderPnm.cpp b/openVulkanoCpp/Image/ImageLoaderPnm.cpp index a21fcc3..be63e47 100644 --- a/openVulkanoCpp/Image/ImageLoaderPnm.cpp +++ b/openVulkanoCpp/Image/ImageLoaderPnm.cpp @@ -9,6 +9,128 @@ #include #include +namespace +{ + struct PnmHeader + { + char magic = 0; // 1 for P1, 2 for P2 etc + int width = 0; + int height = 0; + int maxVal = 255; // Only used for formats P2, P3, P5, P6 + }; + + void MaybeSkipComments(std::istream& stream) + { + std::string line; + while (std::getline(stream, line)) + { + line.erase(0, line.find_first_not_of(" \t")); + + if (line.empty() || line[0] != '#') + { + stream.seekg(-static_cast(line.length()) - 1, std::ios::cur); + break; + } + } + } + + PnmHeader ParseHeader(std::istream& stream) + { + PnmHeader header; + + MaybeSkipComments(stream); + char pMagic; + stream >> pMagic; + if (pMagic != 'P') + { + throw std::runtime_error("Unsupported magic"); + } + + stream >> header.magic; + header.magic -= '0'; + if (header.magic < 1 || header.magic > 6) + { + throw std::runtime_error("Unsupported magic number: P" + (header.magic + '0')); + } + + MaybeSkipComments(stream); + stream >> header.width >> header.height; + if (header.magic != 1 && header.magic != 4) + { + MaybeSkipComments(stream); + stream >> header.maxVal; + } + + stream.ignore(std::numeric_limits::max(), '\n'); + return header; + } + + OpenVulkano::Array ReadBinaryData(std::istream& stream, const PnmHeader& header) + { + size_t dataSize = header.width * header.height * ((header.magic == 6) ? 3 : 1); + OpenVulkano::Array data(dataSize); + + if (header.magic == 4) + { + size_t rowBytes = (header.width + 7) / 8; + OpenVulkano::Array rowBuffer(rowBytes); + + for (int y = header.height - 1; y >= 0; --y) + { + stream.read(reinterpret_cast(rowBuffer.Data()), rowBytes); + size_t rowStart = y * header.width; + for (int x = 0; x < header.width; ++x) + { + size_t byteIndex = x / 8; + size_t bitIndex = 7 - (x % 8); + data[rowStart + x] = (rowBuffer[byteIndex] >> bitIndex) & 1 ? 255 : 0; + } + } + } + else + { + size_t bytesPerRow = header.width * ((header.magic == 6) ? 3 : 1); + OpenVulkano::Array rowBuffer(bytesPerRow); + + for (int y = header.height - 1; y >= 0; --y) + { + stream.read(reinterpret_cast(rowBuffer.Data()), bytesPerRow); + size_t rowStart = y * bytesPerRow; + std::memcpy(data.Data() + rowStart, rowBuffer.Data(), bytesPerRow); + } + } + + return data; + } + + OpenVulkano::Array ReadTextData(std::istream& stream, const PnmHeader& header) + { + const size_t numChannels = (header.magic == 3) ? 3 : 1; + OpenVulkano::Array data(header.width * header.height * numChannels); + const int PRECOMPUTED_RATIO = 255 / header.maxVal; + + for (int y = header.height - 1; y >= 0; --y) + { + for (size_t x = 0; x < header.width * numChannels; ++x) + { + int value; + stream >> value; + + if (header.magic == 1) + { + data[y * header.width * numChannels + x] = static_cast(value * 255); + } + else + { + data[y * header.width * numChannels + x] = static_cast(value * PRECOMPUTED_RATIO); + } + } + } + + return data; + } +} + namespace OpenVulkano::Image { std::unique_ptr ImageLoaderPnm::loadFromFile(const std::string& filePath) @@ -26,20 +148,20 @@ namespace OpenVulkano::Image std::unique_ptr ImageLoaderPnm::loadFromMemory(const std::vector& buffer) { std::istringstream stream(std::string(buffer.begin(), buffer.end())); - PnmHeader header = parseHeader(stream); + PnmHeader header = ParseHeader(stream); std::unique_ptr result = std::make_unique(); result->resolution.x = header.width; result->resolution.y = header.height; result->resolution.z = 1; - if (header.magic == 1 || header.magic == 2 || header.magic == 3) + if (header.magic <= 3) { - result->data = readTextData(stream, header); + result->data = ReadTextData(stream, header); } - else if (header.magic == 4 || header.magic == 5 || header.magic == 6) + else if (header.magic <= 6) { - result->data = readBinaryData(stream, header); + result->data = ReadBinaryData(stream, header); } else { @@ -66,85 +188,7 @@ namespace OpenVulkano::Image throw std::runtime_error("Failed to open file: " + filename); } - PnmHeader header = parseHeader(file); + PnmHeader header = ParseHeader(file); return Math::Vector2i(header.width, header.height); } - - PnmHeader ImageLoaderPnm::parseHeader(std::istream& stream) - { - PnmHeader header; - - char pMagic; - stream >> pMagic; - if (pMagic != 'P') - { - throw std::runtime_error("Unsupported magic"); - } - - stream >> header.magic; - header.magic -= '0'; - if (header.magic != 1 && header.magic != 2 && header.magic != 3 && header.magic != 4 && header.magic != 5 - && header.magic != 6) - { - throw std::runtime_error("Unsupported magic number: P" + (header.magic + '0')); - } - - stream >> header.width >> header.height; - if (header.magic != 1 && header.magic != 4) - { - stream >> header.maxVal; - } - - stream.ignore(std::numeric_limits::max(), '\n'); - return header; - } - - Array ImageLoaderPnm::readBinaryData(std::istream& stream, const PnmHeader& header) - { - size_t dataSize = header.width * header.height * ((header.magic == 6) ? 3 : 1); - Array data(dataSize); - - if (header.magic == 4) - { - size_t rowBytes = (header.width + 7) / 8; - Array rowBuffer(rowBytes); - size_t index = 0; - - for (int y = 0; y < header.height; ++y) - { - stream.read(reinterpret_cast(rowBuffer.Data()), rowBytes); - for (int x = 0; x < header.width; ++x) - { - size_t byteIndex = x / 8; - size_t bitIndex = 7 - (x % 8); - data[index++] = (rowBuffer[byteIndex] >> bitIndex) & 1 ? 255 : 0; - } - } - } - else - { - stream.read(reinterpret_cast(data.Data()), dataSize); - } - - return data; - } - - Array ImageLoaderPnm::readTextData(std::istream& stream, const PnmHeader& header) - { - Array data(header.width * header.height * ((header.magic == 3) ? 3 : 1)); - for (size_t i = 0; i < data.Size(); ++i) - { - int value; - stream >> value; - if (header.magic == 1 || header.magic == 4) - { - data[i] = static_cast(value * 255); - } - else - { - data[i] = static_cast((value * 255) / header.maxVal); - } - } - return data; - } }; \ No newline at end of file diff --git a/openVulkanoCpp/Image/ImageLoaderPnm.hpp b/openVulkanoCpp/Image/ImageLoaderPnm.hpp index d633506..51e092a 100644 --- a/openVulkanoCpp/Image/ImageLoaderPnm.hpp +++ b/openVulkanoCpp/Image/ImageLoaderPnm.hpp @@ -9,24 +9,11 @@ namespace OpenVulkano::Image { - struct PnmHeader - { - char magic = 0; // 1 for P1, 2 for P2 etc - int width = 0; - int height = 0; - int maxVal = 255; // Only used for formats P2, P3, P5, P6 - }; - class ImageLoaderPnm : public IImageLoader { public: std::unique_ptr loadFromFile(const std::string& filePath) override; std::unique_ptr loadFromMemory(const std::vector& buffer) override; Math::Vector2i GetImageDimensions(const std::string& filename) override; - - private: - PnmHeader parseHeader(std::istream& stream); - Array readBinaryData(std::istream& stream, const PnmHeader& header); - Array readTextData(std::istream& stream, const PnmHeader& header); }; } \ No newline at end of file diff --git a/tests/Image/ImageLoaderPnmTest.cpp b/tests/Image/ImageLoaderPnmTest.cpp index c4f008e..4737e69 100644 --- a/tests/Image/ImageLoaderPnmTest.cpp +++ b/tests/Image/ImageLoaderPnmTest.cpp @@ -24,7 +24,7 @@ TEST_CASE("ImageLoaderPnm - Load P1 (ASCII Black & White)") REQUIRE(image->resolution.x == 4); REQUIRE(image->resolution.y == 2); REQUIRE(image->dataFormat == DataFormat::Format::R8_UINT); - REQUIRE(image->data == Array { 255, 0, 255, 0, 0, 255, 0, 255 }); + REQUIRE(image->data == Array { 0, 255, 0, 255, 255, 0, 255, 0 }); } TEST_CASE("ImageLoaderPnm - Load P2 (ASCII Grayscale)") @@ -38,21 +38,21 @@ TEST_CASE("ImageLoaderPnm - Load P2 (ASCII Grayscale)") REQUIRE(image->resolution.x == 4); REQUIRE(image->resolution.y == 2); REQUIRE(image->dataFormat == DataFormat::Format::R8_UINT); - REQUIRE(image->data == Array { 0, 64, 128, 255, 255, 128, 64, 0 }); + REQUIRE(image->data == Array { 255, 128, 64, 0, 0, 64, 128, 255 }); } TEST_CASE("ImageLoaderPnm - Load P3 (ASCII RGB)") { - std::vector p3Buffer = { 'P', '3', '\n', '2', ' ', '2', '\n', '2', '5', '5', '\n', '2', '5', '5', ' ', '0', - ' ', '0', ' ', '0', ' ', '2', '5', '5', ' ', '0', '\n', '0', ' ', '0', ' ', '2', - '5', '5', ' ', '2', '5', '5', ' ', '2', '5', '5', ' ', '2', '5', '5', '\n' }; + std::vector p3Buffer = { 'P', '3', '\n', '2', ' ', '2', '\n', '2', '5', '5', '\n', + '2', '5', '5', ' ', '0', ' ', '0', ' ', '0', ' ', '2', '5', '5', ' ', '0', '\n', + '0', ' ', '0', ' ', '2', '5', '5', ' ', '2', '5', '5', ' ', '2', '5', '5', ' ', '2', '5', '5', '\n' }; ImageLoaderPnm loader; auto image = loader.loadFromMemory(p3Buffer); REQUIRE(image->resolution.x == 2); REQUIRE(image->resolution.y == 2); REQUIRE(image->dataFormat == DataFormat::Format::R8G8B8_UINT); - REQUIRE(image->data == Array { 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255 }); + REQUIRE(image->data == Array { 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0 }); } TEST_CASE("ImageLoaderPnm - Load P4 (Binary Black & White)") @@ -66,8 +66,8 @@ TEST_CASE("ImageLoaderPnm - Load P4 (Binary Black & White)") REQUIRE(image->dataFormat == DataFormat::Format::R8_UINT); REQUIRE(image->data == Array { - 255, 0, 255, 0, 255, 0, 255, 0, // Row 1 - 0, 255, 0, 255, 0, 255, 0, 255 // Row 2 + 0, 255, 0, 255, 0, 255, 0, 255, // Row 1 + 255, 0, 255, 0, 255, 0, 255, 0 // Row 2 }); } @@ -81,7 +81,7 @@ TEST_CASE("ImageLoaderPnm - Load P5 (Binary Grayscale)") REQUIRE(image->resolution.x == 4); REQUIRE(image->resolution.y == 2); REQUIRE(image->dataFormat == DataFormat::Format::R8_UINT); - REQUIRE(image->data == Array { 0, 64, 128, 255, 255, 128, 64, 0 }); + REQUIRE(image->data == Array { 255, 128, 64, 0, 0, 64, 128, 255 }); } TEST_CASE("ImageLoaderPnm - Load P6 (Binary RGB)") @@ -94,7 +94,7 @@ TEST_CASE("ImageLoaderPnm - Load P6 (Binary RGB)") REQUIRE(image->resolution.x == 2); REQUIRE(image->resolution.y == 2); REQUIRE(image->dataFormat == DataFormat::Format::R8G8B8_UINT); - REQUIRE(image->data == Array { 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 255 }); + REQUIRE(image->data == Array { 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 255, 0 }); } TEST_CASE("ImageLoaderPnm - Get Dimensions")