diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7c5a205..969a540 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -24,3 +24,7 @@ jobs: run: cmake -B ${{github.workspace}}/build -S . - name: build run: cmake --build ${{github.workspace}}/build + - name: test + run: | + cd ${{github.workspace}}/build + ctest -C Debug diff --git a/CMakeLists.txt b/CMakeLists.txt index f6e29fc..e5747a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,11 +57,11 @@ add_compile_options("$<$:/utf-8>") add_executable(${PROJECT_NAME} ${SRC}) -message(STATUS "Linking " pixelariumimagelib) target_link_libraries(${PROJECT_NAME} PRIVATE pixelariumimagelib PRIVATE pixelariumrenderlib - PRIVATE pixelariumutilslib) + PRIVATE pixelariumutilslib + PRIVATE pixelariumresourcelib) target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/src @@ -91,3 +91,13 @@ if(APPLE) PUBLIC glfw PUBLIC "-framework OpenGL") endif() + + +option(PIXELARIUM_BUILD_UNITTESTS "Generate Unittests" ON) + + +if(PIXELARIUM_BUILD_UNITTESTS) + include(CTest) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 201197c..e131d02 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(utilities) add_subdirectory(imaging) -add_subdirectory(rendering) \ No newline at end of file +add_subdirectory(rendering) +add_subdirectory(resources) diff --git a/lib/imaging/CMakeLists.txt b/lib/imaging/CMakeLists.txt index 8afdacc..078fa86 100644 --- a/lib/imaging/CMakeLists.txt +++ b/lib/imaging/CMakeLists.txt @@ -6,7 +6,7 @@ message(STATUS "Found opencv: " ${OpenCV_INCLUDE_DIRS}) message(STATUS "OpenCV_LIBs from: " ${OpenCV_LIBS}) set(IMAGELIBSRC - Image.cpp) + PixelariumImage.cpp) set(IMAGELIBLIBNAME pixelariumimagelib) @@ -20,4 +20,4 @@ target_link_libraries(${IMAGELIBLIBNAME} target_include_directories(${IMAGELIBLIBNAME} PUBLIC ${OpenCV_INCLUDE_DIRS} - PRIVATE ${LIBCZI_INCLUDE_DIR}) \ No newline at end of file + PRIVATE ${LIBCZI_INCLUDE_DIR}) diff --git a/lib/imaging/Image.hpp b/lib/imaging/Image.hpp deleted file mode 100644 index 7f43f6d..0000000 --- a/lib/imaging/Image.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace pixelarium::imaging -{ -class Image -{ - public: - - explicit Image(const std::string& uri); - - // get back the defaults - Image() = default; - // we cannot copy an Image since this conflicts with the _img field - Image(const Image& other) = delete; - Image(Image&& other) noexcept = default; - // requires a copy ctor which we don't have - Image& operator=(const Image& other) = delete; - Image& operator=(Image&& other) noexcept = default; - ~Image() = default; - - const cv::Mat& GetImage() const { return *this->_img.get(); } - - private: - std::unique_ptr _img; -}; - -} // namespace pixelarium::imaging \ No newline at end of file diff --git a/lib/imaging/Image.cpp b/lib/imaging/PixelariumImage.cpp similarity index 58% rename from lib/imaging/Image.cpp rename to lib/imaging/PixelariumImage.cpp index 22f187b..332a19e 100644 --- a/lib/imaging/Image.cpp +++ b/lib/imaging/PixelariumImage.cpp @@ -1,18 +1,17 @@ -#include "Image.hpp" +#include "PixelariumImage.hpp" #include #include #include #include #include -#include -pixelarium::imaging::Image::Image(const std::string& uri) +pixelarium::imaging::PixelariumImage::PixelariumImage(const std::string& uri) { if (!std::filesystem::exists(uri)) { throw std::runtime_error(std::format("File not {} found", uri)); } - this->_img = std::make_unique(cv::imread(uri)); -} \ No newline at end of file + this->img_ = std::make_unique(cv::imread(uri)); +} diff --git a/lib/imaging/PixelariumImage.hpp b/lib/imaging/PixelariumImage.hpp new file mode 100644 index 0000000..c5b98ce --- /dev/null +++ b/lib/imaging/PixelariumImage.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +namespace pixelarium::imaging +{ +class PixelariumImage +{ + public: + + explicit PixelariumImage(const std::string& uri); + + // get back the defaults + PixelariumImage() = default; + // we cannot copy an Image since this conflicts with the _img field + PixelariumImage(const PixelariumImage& other) = delete; + PixelariumImage(PixelariumImage&& other) noexcept + : img_(std::move(other.img_)) {} + // requires a copy ctor which we don't have + PixelariumImage& operator=(const PixelariumImage& other) = delete; + PixelariumImage& operator=(PixelariumImage&& other) noexcept + { + if (this != &other) + { + img_ = std::move(other.img_); + } + + return *this; + } + + ~PixelariumImage() = default; + + const cv::Mat& GetImage() const { return *this->img_.get(); } + + protected: + std::unique_ptr img_; +}; + +} // namespace pixelarium::imaging diff --git a/lib/rendering/CvMatRender.cpp b/lib/rendering/CvMatRender.cpp index 3574fda..be191dd 100644 --- a/lib/rendering/CvMatRender.cpp +++ b/lib/rendering/CvMatRender.cpp @@ -1,90 +1,118 @@ #include "CvMatRender.hpp" -#include #include #include -#include -#include -#include "imaging/Image.hpp" #include +#include + +#include "imaging/PixelariumImage.hpp" using namespace pixelarium::imaging; -pixelarium::render::CvMatRender::CvMatRender(const std::shared_ptr& img) - : _base(img), _texture(0) +/// @brief Constructor for the CvMatRender class. +/// @param img A shared pointer to the PixelariumImage to be rendered. +pixelarium::render::CvMatRender::CvMatRender(const std::shared_ptr& img) : base_(img), texture_(0) { - // this->_img = this->_base->GetImage().clone(); - // // storing a copy of the to-be-rendered image with a "well-behaved" - // cv::cvtColor(this->_img, this->_img, cv::COLOR_BGR2RGBA); - this->_img = this->_base->GetImage().clone(); + // storing a copy of the to-be-rendered image + // because it will be resized and filtered eventually which we absolutely + // must not do on the original image + this->ResetRenderImage(); + cv::cvtColor(this->img_, this->img_, cv::COLOR_BGR2RGBA); } -/*static*/ void pixelarium::render::matToTexture(const cv::Mat& image, - GLuint* texture) +/// @brief Destructor for the CvMatRender class. +/// Deallocates the OpenGL texture if it exists. +pixelarium::render::CvMatRender::~CvMatRender() { - // only generate the texture when it's not already present - if (*texture == 0) + if (texture_) { - glGenTextures(1, texture); + glDeleteTextures(1, &texture_); + texture_ = 0; + } +} + +/// @brief Resets the render image with a new PixelariumImage. +/// @param img A shared pointer to the new PixelariumImage. +void pixelarium::render::CvMatRender::ResetRenderImage(const std::shared_ptr& img) +{ + this->base_ = img; + this->ResetRenderImage(); + cv::cvtColor(this->img_, this->img_, cv::COLOR_BGR2RGBA); +} + +/// @brief Uploads the current image data to an OpenGL texture. +/// @return The ID of the uploaded OpenGL texture. +/// @throws std::runtime_error if the image data is empty or if there is an OpenGL error. +GLuint pixelarium::render::CvMatRender::uploadTexture() +{ + if (img_.empty()) + { + throw std::runtime_error("Image data is empty."); } - glBindTexture(GL_TEXTURE_2D, *texture); + if (!this->texture_) + { + glGenTextures(1, &this->texture_); + if (this->texture_ == 0) + { + throw std::runtime_error("Failed to generate OpenGL texture."); + } + } + + glBindTexture(GL_TEXTURE_2D, this->texture_); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - // auto image = img.GetImage(); - switch (image.type()) + const int width = img_.cols; + const int height = img_.rows; + + GLenum format = (img_.type() == CV_32FC3 || img_.type() == CV_32FC1) ? GL_RGB : GL_RGBA; + GLenum type = (img_.type() == CV_16U || img_.type() == CV_16UC3) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; + GLenum internalFormat = GL_RGBA; + if (img_.type() == CV_32FC3 || img_.type() == CV_32FC1) { - case CV_16U: - case CV_16UC3: - case 26: - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.cols, image.rows, 0, - GL_RGBA, GL_UNSIGNED_SHORT, image.data); - break; - case 5: - case 29: - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.cols, image.rows, 0, - GL_RGBA, GL_FLOAT, image.data); - break; - default: - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.cols, image.rows, 0, - GL_RGBA, GL_UNSIGNED_BYTE, image.data); - break; + internalFormat = GL_RGB; } -} -GLuint* pixelarium::render::CvMatRender::Render() -{ - // storing a copy of the to-be-rendered image with a "well-behaved" - cv::cvtColor(this->_img, this->_img, cv::COLOR_BGR2RGBA); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, img_.data); - matToTexture(this->_img, &this->_texture); - return &this->_texture; -} - -GLuint* pixelarium::render::CvMatRender::Render(float factor) -{ - cv::resize(this->_base->GetImage(), this->_img, cv::Size(0,0), factor, factor, cv::INTER_LINEAR_EXACT); - - return this->Render(); -} - -GLuint* pixelarium::render::CvMatRender::Render(size_t width, size_t height) -{ - // this is nasty as it knows about what Render is doing - const auto sz{this->_base->GetImage().size()}; - - const auto get_factor = [](auto opt1, auto opt2) -> float + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { - return opt1 < opt2 ? opt1 : opt2; - }; + throw std::runtime_error("OpenGL error during texture upload: " + std::to_string(error)); + } + + return this->texture_; +} + +/// @brief Renders the image by uploading it as a texture. +/// @return The ID of the OpenGL texture. +GLuint pixelarium::render::CvMatRender::Render() { return this->uploadTexture(); } + +/// @brief Renders the image with a specified scaling factor. +/// @param factor The scaling factor for resizing the image. +/// @return The ID of the OpenGL texture. +GLuint pixelarium::render::CvMatRender::Render(float factor) +{ + cv::resize(this->base_->GetImage(), this->img_, cv::Size(0, 0), factor, factor, cv::INTER_LINEAR_EXACT); + + return this->uploadTexture(); +} + +/// @brief Renders the image to fit within the specified width and height. +/// @param width The maximum width of the rendered image. +/// @param height The maximum height of the rendered image. +/// @return The ID of the OpenGL texture. +GLuint pixelarium::render::CvMatRender::Render(size_t width, size_t height) +{ + const auto sz{this->base_->GetImage().size()}; + + const auto get_factor = [](auto opt1, auto opt2) -> float { return opt1 < opt2 ? opt1 : opt2; }; auto factor = get_factor(width / static_cast(sz.width), height / static_cast(sz.height)); - // cv::resize(this->_base->GetImage(), this->_img, cv::Size(0,0), factor, factor, cv::INTER_LINEAR_EXACT); return this->Render(factor); } - diff --git a/lib/rendering/CvMatRender.hpp b/lib/rendering/CvMatRender.hpp index 5d4d374..0f485da 100644 --- a/lib/rendering/CvMatRender.hpp +++ b/lib/rendering/CvMatRender.hpp @@ -13,25 +13,41 @@ #endif #include // Will drag system OpenGL headers #endif -#include "imaging/Image.hpp" +#include "imaging/PixelariumImage.hpp" // clang-format on namespace pixelarium::render { -static void matToTexture(const cv::Mat& image, GLuint* texture); class CvMatRender { public: + // we want the default constructor for the time being + // (although it does not make much sense and should + // get removed in the future) + // as the using AppGLFW constructs it empty as a member + // when coming to life. CvMatRender() = default; - explicit CvMatRender(const std::shared_ptr& img); - GLuint* Render(); - GLuint* Render(float factor); - GLuint* Render(size_t width, size_t height); + CvMatRender(CvMatRender&) = delete; + CvMatRender(const CvMatRender&) = delete; + CvMatRender(CvMatRender&&) = delete; + CvMatRender& operator=(CvMatRender&) = default; + CvMatRender& operator=(CvMatRender&&) = default; + ~CvMatRender(); + explicit CvMatRender(const std::shared_ptr& img); + + public: + GLuint Render(); + GLuint Render(float factor); + GLuint Render(size_t width, size_t height); + void ResetRenderImage() { this->img_ = this->base_->GetImage().clone(); } + void ResetRenderImage(const std::shared_ptr& img); private: - cv::Mat _img; - std::shared_ptr _base; - GLuint _texture; + cv::Mat img_; + std::shared_ptr base_; + GLuint texture_; + + GLuint uploadTexture(); }; -} // namespace pixelarium::render \ No newline at end of file +} // namespace pixelarium::render diff --git a/lib/resources/CMakeLists.txt b/lib/resources/CMakeLists.txt new file mode 100644 index 0000000..37c1e0e --- /dev/null +++ b/lib/resources/CMakeLists.txt @@ -0,0 +1,13 @@ +set(RESOURCELIBNAME pixelariumresourcelib) + +set(RESOURCELIBSRC + resource.cpp) + +add_library(${RESOURCELIBNAME} STATIC + ${RESOURCELIBSRC}) + +target_link_libraries(${RESOURCELIBNAME} + PRIVATE pixelariumimagelib) + +target_include_directories(${RESOURCELIBNAME} + PRIVATE ${CMAKE_SOURCE_DIR}/lib) diff --git a/lib/resources/resource.cpp b/lib/resources/resource.cpp new file mode 100644 index 0000000..dd4f39b --- /dev/null +++ b/lib/resources/resource.cpp @@ -0,0 +1,78 @@ +#include "resource.hpp" + +#include +#include + +using pixelarium::imaging::PixelariumImage; +using namespace std; + +namespace +{ +/// @brief Atomic counter for generating unique IDs. +static std::atomic id_; + +/// @brief Generates a unique ID. +/// @return A unique ID. +size_t GenerateId() { return id_.fetch_add(1, memory_order_relaxed); } +} // namespace + +/// @brief Retrieves a resource from the pool. +/// @param id The ID of the resource to retrieve. +/// @return A pointer to the resource if found, otherwise an empty optional. +std::optional pixelarium::resources::ImageResourcePool::GetResource(size_t id) const +{ + auto search{this->resources_.find(id)}; + if (search == this->resources_.end()) return std::nullopt; + + return search->second.get(); +} + +/// @brief Sets a resource in the pool. +/// @param res A unique pointer to the resource to set. +/// @return The ID of the new resource. +size_t pixelarium::resources::ImageResourcePool::SetResource(unique_ptr res) +{ + auto key{::GenerateId()}; + this->resources_.insert({key, std::move(res)}); + + return key; +} + +/// @brief Updates a resource in the pool. +/// @param id The ID of the resource to update. +/// @param res A unique pointer to the new resource. +/// @return True if the resource was updated, false otherwise. +bool pixelarium::resources::ImageResourcePool::UpdateResource(size_t id, std::unique_ptr res) +{ + auto search{this->resources_.find(id)}; + if (search == this->resources_.end()) return false; + + search->second = std::move(res); + + return true; +} + +/// @brief Deletes a resource from the pool. +/// @param id The ID of the resource to delete. +/// @return True if the resource was deleted, false otherwise. +bool pixelarium::resources::ImageResourcePool::DeleteResource(size_t id) +{ + auto search{this->resources_.find(id)}; + if (search == this->resources_.end()) return false; + + this->resources_.erase(search); + + return true; +} + +/// @brief Enumerates all resources in the pool. +/// @param func A function to call for each resource. The function should accept the resource ID and a const reference +/// to a PixelariumImage. +void pixelarium::resources::ImageResourcePool::EnumerateResources( + const std::function& func) +{ + for (const auto& e : this->resources_) + { + func(e.first, *e.second); + } +} diff --git a/lib/resources/resource.hpp b/lib/resources/resource.hpp new file mode 100644 index 0000000..45380d1 --- /dev/null +++ b/lib/resources/resource.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include + +#include "imaging/PixelariumImage.hpp" + +namespace pixelarium::resources +{ +struct IResource +{ + virtual ~IResource() = 0; +}; + +template +concept ResT = requires(R& r) { static_cast(r); }; + +// template +template +class IResourcePool +{ + public: + virtual ~IResourcePool() = default; + virtual std::optional GetResource(size_t id) const = 0; + virtual size_t SetResource(std::unique_ptr res) = 0; + virtual bool UpdateResource(size_t id, std::unique_ptr res) = 0; + virtual bool DeleteResource(size_t id) = 0; + virtual void EnumerateResources(const std::function& func) = 0; +}; + +// Now with the =GetResource= method, I do not want to transfer ownership to the caller of that method. The ownership +// should still +// reside with the =ResourcePool=! +// In fact, the intention is, that there is no way back once the =ResourcePool= took ownership of an object. +// Callers can get references, but no ownership. A caller might delete a resource though. +class ImageResourcePool : public IResourcePool +{ + public: + ImageResourcePool() = default; + ImageResourcePool(ImageResourcePool&) = delete; + ImageResourcePool(const ImageResourcePool&) = delete; + ImageResourcePool(ImageResourcePool&&) = delete; + ImageResourcePool& operator=(ImageResourcePool&) = delete; + ImageResourcePool& operator=(ImageResourcePool&&) = delete; + + std::optional GetResource(size_t id) const override; + size_t SetResource(std::unique_ptr res) override; + bool UpdateResource(size_t id, std::unique_ptr res) override; + bool DeleteResource(size_t id) override; + + void EnumerateResources(const std::function& func) override; + + private: + std::unordered_map> resources_; +}; +} // namespace pixelarium::resources diff --git a/lib/utilities/ILog.hpp b/lib/utilities/ILog.hpp index 6e8539a..be551d9 100644 --- a/lib/utilities/ILog.hpp +++ b/lib/utilities/ILog.hpp @@ -3,6 +3,14 @@ namespace pixelarium::utils::log { +enum class LogLevel +{ + Trace = 1 << 0, + Debug = 1 << 1, + Info = 1 << 2, + Warn = 1 << 3, + Error = 1 << 4, +}; class ILog { public: @@ -10,8 +18,9 @@ class ILog virtual void Debug(const std::string& msg) = 0; virtual void Warn(const std::string& msg) = 0; virtual void Error(const std::string& msg) = 0; + virtual void ChangeLevel(LogLevel lvl) = 0; virtual ~ILog() {} }; -} // namespace pixelarium::utils::log \ No newline at end of file +} // namespace pixelarium::utils::log diff --git a/lib/utilities/SpdLogger.cpp b/lib/utilities/SpdLogger.cpp index 2db0010..b60b606 100644 --- a/lib/utilities/SpdLogger.cpp +++ b/lib/utilities/SpdLogger.cpp @@ -5,31 +5,58 @@ #include #include #include +#include "ILog.hpp" using namespace pixelarium::utils::log; SpdLogger::SpdLogger(const std::string& file_sink, const std::string& name) - : _logger(spdlog::basic_logger_mt(name, file_sink)), _file(file_sink), _name(name) + : logger_(spdlog::basic_logger_mt(name, file_sink)), file_(file_sink), name_(name) { - spdlog::set_default_logger(this->_logger); + spdlog::set_default_logger(this->logger_); spdlog::flush_on(spdlog::level::info); - _logger->info("Logger initiated"); + logger_->info("Logger initiated"); } void SpdLogger::Info(const std::string& msg) { - this->_logger->info(msg); + this->logger_->info(msg); } void SpdLogger::Debug(const std::string& msg) { - this->_logger->debug(msg); + this->logger_->debug(msg); } void SpdLogger::Warn(const std::string& msg) { - this->_logger->warn(msg); + this->logger_->warn(msg); } -void SpdLogger::Error(const std::string& msg) +void SpdLogger::Error(const std::string& msg) { this->logger_->error(msg); } + +void SpdLogger::ChangeLevel(LogLevel lvl) { - this->_logger->error(msg); -} \ No newline at end of file + switch (lvl) + { + case LogLevel::Trace: + this->logger_->set_level(spdlog::level::trace); + spdlog::flush_on(spdlog::level::trace); + break; + case LogLevel::Info: + this->logger_->set_level(spdlog::level::info); + spdlog::flush_on(spdlog::level::info); + break; + case LogLevel::Warn: + this->logger_->set_level(spdlog::level::warn); + spdlog::flush_on(spdlog::level::warn); + break; + case LogLevel::Error: + this->logger_->set_level(spdlog::level::err); + spdlog::flush_on(spdlog::level::err); + break; + case LogLevel::Debug: + default: + this->logger_->set_level(spdlog::level::debug); + spdlog::flush_on(spdlog::level::debug); + } + + this->logger_->debug("Changed log level;"); +} diff --git a/lib/utilities/SpdLogger.hpp b/lib/utilities/SpdLogger.hpp index cb57206..3a8907d 100644 --- a/lib/utilities/SpdLogger.hpp +++ b/lib/utilities/SpdLogger.hpp @@ -17,10 +17,11 @@ class SpdLogger : public ILog void Debug(const std::string& msg) override; void Warn(const std::string& msg) override; void Error(const std::string& msg) override; + void ChangeLevel(LogLevel lvl) override; private: - std::shared_ptr _logger; - std::string _file; - std::string _name; + std::shared_ptr logger_; + std::string file_; + std::string name_; }; -} // namespace pixelarium::utils::log \ No newline at end of file +} // namespace pixelarium::utils::log diff --git a/modules/googletest.cmake b/modules/googletest.cmake new file mode 100644 index 0000000..0b023cf --- /dev/null +++ b/modules/googletest.cmake @@ -0,0 +1,13 @@ +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip +) +# From this documentation: https://google.github.io/googletest/quickstart-cmake.html +# For Windows: Prevent overriding the parent project's compiler/linker settings + +if(WIN32) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +endif() + +FetchContent_MakeAvailable(googletest) \ No newline at end of file diff --git a/src/AppGLFW.cpp b/src/AppGLFW.cpp index 876980d..49f0aa5 100644 --- a/src/AppGLFW.cpp +++ b/src/AppGLFW.cpp @@ -1,8 +1,9 @@ #include "AppGLFW.hpp" +#include #include -#include "imaging/Image.hpp" +#include "imaging/PixelariumImage.hpp" #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" @@ -22,7 +23,8 @@ using namespace pixelarium::imaging; return false; } -/*static*/ ImVec2 pixelarium::ui::ascpet_const_dimensions(const pixelarium::imaging::Image& img, const ImVec2& curr_dim) +/*static*/ ImVec2 pixelarium::ui::aspect_const_dimensions(const pixelarium::imaging::PixelariumImage& img, + const ImVec2& curr_dim) { const auto w_fact = (static_cast(curr_dim.x) / img.GetImage().cols); const auto h_fact = (static_cast(curr_dim.y) / img.GetImage().rows); @@ -133,27 +135,33 @@ int pixelarium::ui::AppGLFW::Run() this->MenuBar(); - if (this->_imagep) + if (this->imagep_) { // auto render = render::CvMatRender(this->_img); - ImGui::Begin("An image", &this->_imagep, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar); - this->_curr_dim = ImGui::GetContentRegionAvail(); + ImGui::Begin("An image", &this->imagep_, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar); + this->curr_dim_ = ImGui::GetContentRegionAvail(); auto new_dim = ImGui::GetContentRegionAvail(); - auto texture = dim_changed_p(this->_curr_dim, new_dim) - ? this->_render.Render(static_cast(this->_curr_dim.x), - static_cast(this->_curr_dim.y)) - : this->_render.Render(); + auto texture = dim_changed_p(this->curr_dim_, new_dim) + ? this->render_.Render(static_cast(this->curr_dim_.x), + static_cast(this->curr_dim_.y)) + : this->render_.Render(); - this->_curr_dim = new_dim; + this->curr_dim_ = new_dim; // random aspect ratio // ImGui::Image(reinterpret_cast( // reinterpret_cast(*texture)), // this->_curr_dim); - ImVec2 dim(this->_img->GetImage().cols, this->_img->GetImage().rows); + ImVec2 dim(this->img_->GetImage().cols, this->img_->GetImage().rows); // aspect ratio constant render - ImGui::Image(reinterpret_cast(reinterpret_cast(*texture)), - ascpet_const_dimensions(*this->_img, new_dim)); + ImGui::Image(reinterpret_cast(reinterpret_cast(texture)), + aspect_const_dimensions(*this->img_, new_dim)); + // ImGui::Image(reinterpret_cast(reinterpret_cast(texture)), + // ImVec2(img_->GetImage().cols, img_->GetImage().rows)); + + // We can do everything else from within the image buffer that ImGui offers + // ImGui::Separator(); + // ImGui::Text("This is a text within the image frame"); ImGui::End(); } @@ -162,8 +170,6 @@ int pixelarium::ui::AppGLFW::Run() ImGui::Render(); int display_w, display_h; glfwGetFramebufferSize(this->window, &display_w, &display_h); - glViewport(0, 0, display_w, display_h); - glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) @@ -217,15 +223,13 @@ void pixelarium::ui::AppGLFW::LoadImageProt() auto res{pfd::open_file("Load Inputs", pfd::path::home(), {"All Files", "*"}, pfd::opt::multiselect).result()}; for (auto& p : res) { - // lg::Logger::Debug("Adding image from " + std::string(p), - // __FUNCTION__); - if (this->_logger) + if (this->logger_) { - this->_logger->Warn(std::format("Creating image {}", p)); + this->logger_->Debug(std::format("{}: Creating image {}", __FUNCTION__, p)); } - // this->_img = Image(p); - this->_img = std::make_shared(p); - this->_render = pixelarium::render::CvMatRender(this->_img); - this->_imagep = true; + + this->img_ = std::make_shared(p); + this->render_ = pixelarium::render::CvMatRender(this->img_); + this->imagep_ = true; } -} \ No newline at end of file +} diff --git a/src/AppGLFW.hpp b/src/AppGLFW.hpp index c547370..7deca27 100644 --- a/src/AppGLFW.hpp +++ b/src/AppGLFW.hpp @@ -3,18 +3,20 @@ #include #include +#include #include -#include "Image.hpp" +#include "PixelariumImage.hpp" #include "imgui.h" #include "rendering/CvMatRender.hpp" +#include "resources/resource.hpp" #include "utilities/ILog.hpp" namespace pixelarium::ui { static bool dim_changed_p(const ImVec2& ref_rect, const ImVec2& new_rect); -static ImVec2 ascpet_const_dimensions(const pixelarium::imaging::Image& img, const ImVec2& curr_dim); +static ImVec2 aspect_const_dimensions(const pixelarium::imaging::PixelariumImage& img, const ImVec2& curr_dim); enum LogLevelSelection { @@ -28,7 +30,24 @@ class AppGLFW { public: AppGLFW() { this->InitMainWindow(); } - AppGLFW(std::unique_ptr& log) : _logger(log.get()) { this->InitMainWindow(); } + AppGLFW(std::unique_ptr& log) : AppGLFW() + { + logger_ = log.get(); + if (logger_) + { + logger_->Debug(std::format("{}: Initiating a new window", __FUNCTION__).c_str()); + + if (pool_) + { + logger_->Debug(std::format("{}: We have an image resource pool!", __FUNCTION__).c_str()); + } + } + } + AppGLFW(std::unique_ptr& log, std::unique_ptr& pool) + : AppGLFW(log) + { + pool_ = pool.get(); + }; int Run(); private: @@ -38,17 +57,18 @@ class AppGLFW private: // LogLevelSelection log_level_ = static_cast(0); - utils::log::ILog* _logger; + utils::log::ILog* logger_; + resources::ImageResourcePool* pool_; GLFWwindow* window = nullptr; - ImGuiWindowFlags window_flags = 0; - std::shared_ptr _img; - pixelarium::render::CvMatRender _render; - bool _imagep{false}; - ImVec2 _curr_dim; + ImGuiWindowFlags window_flags_ = 0; + std::shared_ptr img_; + pixelarium::render::CvMatRender render_; + bool imagep_{false}; + ImVec2 curr_dim_; }; static void glfw_error_callback(int error, const char* description) { fprintf(stderr, "GLFW Error %d: %s\n", error, description); } -} // namespace pixelarium::ui \ No newline at end of file +} // namespace pixelarium::ui diff --git a/src/main.cpp b/src/main.cpp index 55bdb3f..7ec5dd0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,25 +2,28 @@ #include #include "AppGLFW.hpp" +#include "resources/resource.hpp" +#include "uiresources.h" #include "utilities/ILog.hpp" #include "utilities/SpdLogger.hpp" -#include "uiresources.h" int main(int argc, char** argv) { - using namespace pixelarium::utils::log; + using namespace pixelarium; using namespace std; cout << "ok\n"; - unique_ptr logger; + unique_ptr logger; #ifdef _WIN32 - logger = make_unique(string(getenv("APPDATA")) + "/pixelarium/logfile.log", "default"); + logger = make_unique(string(getenv("APPDATA")) + "/pixelarium/logfile.log", "default"); #else - logger = make_unique(std::string(getenv("HOME")) + "/.cache/pixelarium/log.log", "default"); + logger = make_unique(std::string(getenv("HOME")) + "/.cache/pixelarium/log.log", "default"); #endif + logger->Info(std::format("{}: Starting Application {}", __FUNCTION__, PIXELARIUM_TITLE)); - auto app = pixelarium::ui::AppGLFW(logger); + logger->ChangeLevel(utils::log::LogLevel::Debug); + auto image_pool{std::make_unique()}; + + auto app = pixelarium::ui::AppGLFW(logger, image_pool); - logger->Info(std::format("Starting Application {}", PIXELARIUM_TITLE)); - logger->Error("Starting Application"); return app.Run(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..f7f9703 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +include(${PROJECT_SOURCE_DIR}/modules/googletest.cmake) + +add_executable(libpixelarium_UnitTests + lib/resources/test_resource.cpp) + +target_include_directories(libpixelarium_UnitTests + PUBLIC ${PROJECT_SOURCE_DIR}/lib + PUBLIC ${OpenCV_INCLUDE_DIRS} +) + + +target_link_libraries(libpixelarium_UnitTests + pixelariumresourcelib + pixelariumimagelib + GTest::gtest_main + GTest::gmock) + +include(GoogleTest) +gtest_discover_tests(libpixelarium_UnitTests) diff --git a/tests/lib/resources/test_resource.cpp b/tests/lib/resources/test_resource.cpp new file mode 100644 index 0000000..4beb72b --- /dev/null +++ b/tests/lib/resources/test_resource.cpp @@ -0,0 +1,92 @@ +#include + +#include + +#include "imaging/PixelariumImage.hpp" +#include "resources/resource.hpp" + +namespace +{ + +class DummyImage : public pixelarium::imaging::PixelariumImage +{ + // Implement minimal interface if needed for test +}; + +} // anonymous namespace + +using pixelarium::resources::ImageResourcePool; + +TEST(ImageResourcePoolTest, SetAndGetResource) +{ + ImageResourcePool pool; + auto img = std::make_unique(); + auto id = pool.SetResource(std::move(img)); + auto res = pool.GetResource(id); + EXPECT_TRUE(res.has_value()); + EXPECT_NE(res.value(), nullptr); +} + +TEST(ImageResourcePoolTest, SetWrappedRawPointerGet) +{ + ImageResourcePool pool; + auto img = new DummyImage(); + auto id = pool.SetResource(std::unique_ptr(img)); + auto res = pool.GetResource(id); + EXPECT_TRUE(res.has_value()); + EXPECT_NE(res.value(), nullptr); +} + +TEST(ImageResourcePoolTest, GetNonExistentResourceReturnsEmptyOptional) +{ + ImageResourcePool pool; + EXPECT_FALSE(pool.GetResource(12345)); +} + +TEST(ImageResourcePoolTest, UpdateResourceSuccess) +{ + ImageResourcePool pool; + auto id = pool.SetResource(std::make_unique()); + auto new_img = std::make_unique(); + EXPECT_TRUE(pool.UpdateResource(id, std::move(new_img))); + auto res = pool.GetResource(id); + EXPECT_TRUE(res.has_value()); + EXPECT_NE(res.value(), nullptr); +} + +TEST(ImageResourcePoolTest, UpdateResourceFail) +{ + ImageResourcePool pool; + auto new_img = std::make_unique(); + EXPECT_FALSE(pool.UpdateResource(999, std::move(new_img))); +} + +TEST(ImageResourcePoolTest, DeleteResourceSuccess) +{ + ImageResourcePool pool; + auto id = pool.SetResource(std::make_unique()); + EXPECT_TRUE(pool.DeleteResource(id)); + EXPECT_FALSE(pool.GetResource(id).has_value()); +} + +TEST(ImageResourcePoolTest, DeleteResourceFail) +{ + ImageResourcePool pool; + EXPECT_FALSE(pool.DeleteResource(8907)); +} + +TEST(ImageResourcePoolTest, EnumerateResources) +{ + ImageResourcePool pool; + auto id1 = pool.SetResource(std::make_unique()); + auto id2 = pool.SetResource(std::make_unique()); + std::vector found_ids{}; + + std::function func = + [&found_ids](size_t id, const pixelarium::imaging::PixelariumImage&) { found_ids.push_back(id); }; + pool.EnumerateResources(func); + + EXPECT_EQ(found_ids.size(), 2); + EXPECT_NE(std::find(found_ids.begin(), found_ids.end(), id1), found_ids.end()); + EXPECT_NE(std::find(found_ids.begin(), found_ids.end(), id2), found_ids.end()); +}