From 4d6cba0afd9844ae913f6188016dd89eec4c86cd Mon Sep 17 00:00:00 2001 From: Vladyslav Baranovskyi Date: Tue, 24 Dec 2024 21:47:24 +0200 Subject: [PATCH] PFM image loader + tests --- openVulkanoCpp/Image/ImageLoaderPfm.cpp | 96 +++++++++++++++++++++++++ openVulkanoCpp/Image/ImageLoaderPfm.hpp | 31 ++++++++ tests/Image/ImageLoaderPfmTest.cpp | 84 ++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 openVulkanoCpp/Image/ImageLoaderPfm.cpp create mode 100644 openVulkanoCpp/Image/ImageLoaderPfm.hpp create mode 100644 tests/Image/ImageLoaderPfmTest.cpp diff --git a/openVulkanoCpp/Image/ImageLoaderPfm.cpp b/openVulkanoCpp/Image/ImageLoaderPfm.cpp new file mode 100644 index 0000000..8ac8feb --- /dev/null +++ b/openVulkanoCpp/Image/ImageLoaderPfm.cpp @@ -0,0 +1,96 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include "ImageLoaderPfm.hpp" + +#include "Scene/DataFormat.hpp" + +#include +#include +#include +#include +#include + +namespace OpenVulkano::Image +{ + std::unique_ptr ImageLoaderPfm::loadFromFile(const std::string& filePath) + { + std::ifstream file(filePath, std::ios::binary); + if (!file.is_open()) + { + throw std::runtime_error("Failed to open PFM file: " + filePath); + } + + std::vector buffer((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + return loadFromMemory(buffer); + } + + 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); + + 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); + + if (!header.isLittleEndian) + { + for (float& value : data) + { + uint8_t* ptr = reinterpret_cast(&value); + std::reverse(ptr, ptr + sizeof(float)); + } + } + + 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); + return image; + } + + Math::Vector2i ImageLoaderPfm::GetImageDimensions(const std::string& filename) + { + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) + { + throw std::runtime_error("Failed to open PFM file: " + filename); + } + + 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); + 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 new file mode 100644 index 0000000..1011bc7 --- /dev/null +++ b/openVulkanoCpp/Image/ImageLoaderPfm.hpp @@ -0,0 +1,31 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "Image/ImageLoader.hpp" + +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/tests/Image/ImageLoaderPfmTest.cpp b/tests/Image/ImageLoaderPfmTest.cpp new file mode 100644 index 0000000..64273f5 --- /dev/null +++ b/tests/Image/ImageLoaderPfmTest.cpp @@ -0,0 +1,84 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include + +#include "Image/ImageLoaderPfm.hpp" +#include "Scene/DataFormat.hpp" + +using namespace OpenVulkano; +using namespace OpenVulkano::Image; + +TEST_CASE("ImageLoaderPfm loads PFM images", "[ImageLoaderPfm]") +{ + ImageLoaderPfm loader; + + SECTION("Grayscale Pf image") + { + std::ostringstream pfStream; + pfStream << "Pf\n" + << "4 2\n" + << "-1.0\n"; + + for (int i = 0; i < 8; ++i) + { + float pixelValue = 1.0f; + pfStream.write(reinterpret_cast(&pixelValue), sizeof(float)); + } + + std::string pfData = pfStream.str(); + std::vector pfBuffer(pfData.begin(), pfData.end()); + + auto image = loader.loadFromMemory(pfBuffer); + + REQUIRE(image != nullptr); + REQUIRE(image->resolution.x == 4); + REQUIRE(image->resolution.y == 2); + REQUIRE(image->dataFormat == DataFormat::Format::R32_SFLOAT); + + const float expectedValue = 1.0f; + for (size_t i = 0; i < image->data.Size(); i += sizeof(float)) + { + float value; + std::memcpy(&value, &image->data[i], sizeof(float)); + REQUIRE(value == expectedValue); + } + } + + SECTION("Color PF image") + { + std::ostringstream pfStream; + pfStream << "PF\n" + << "3 2\n" + << "-1.0\n"; + + for (int i = 0; i < 6 * 3; ++i) + { + float pixelValue = 1.0f; + pfStream.write(reinterpret_cast(&pixelValue), sizeof(float)); + } + + std::string pfData = pfStream.str(); + std::vector pfBuffer(pfData.begin(), pfData.end()); + + auto image = loader.loadFromMemory(pfBuffer); + + REQUIRE(image != nullptr); + REQUIRE(image->resolution.x == 3); + REQUIRE(image->resolution.y == 2); + REQUIRE(image->dataFormat == DataFormat::Format::R32G32B32_SFLOAT); + + const float expectedValue = 1.0f; + for (size_t i = 0; i < image->data.Size(); i += sizeof(float)) + { + float value; + std::memcpy(&value, &image->data[i], sizeof(float)); + REQUIRE(value == expectedValue); + } + } +} \ No newline at end of file