From b37814204fe3b9360779f325a8a004de24105c54 Mon Sep 17 00:00:00 2001 From: m-aXimilian <56168660+m-aXimilian@users.noreply.github.com> Date: Fri, 23 Jan 2026 23:00:35 +0000 Subject: [PATCH] Misc (#19) * get image returns optional instead of unique_ptr * introduce complexity * rename image load function * add example for a basic reader for binary image files * fix windows build? * add a status bar underneath the menu bar * use status bar in custom_0 example app * clang formats * packing w/ pragma push * add "Save As" functions to image views * resize over reserve * rm local override uri * add simple thread pool * rename to simple_thread_pool * get rid of useless renderlib * document version inc * extract hardcoded values to in-header * clang format patch * clone registered image in custom_0 * document binary-file header * minor fixes * clang format * rm unused render cmake --- CMakeLists.txt | 2 +- doc/versions.md | 27 +-- examples/custom_0/CMakeLists.txt | 1 - examples/custom_0/custom_0.cpp | 172 +++++++++++++++++- examples/custom_1/CMakeLists.txt | 1 - examples/custom_1/custom_1.cpp | 9 +- examples/simple/CMakeLists.txt | 1 - lib/CMakeLists.txt | 1 - lib/app/AppGLFW.cpp | 73 ++++++++ lib/app/AppGLFW.hpp | 20 +- lib/app/CMakeLists.txt | 22 ++- lib/app/DefaultApp.cpp | 4 +- lib/app/DefaultApp.hpp | 4 +- lib/app/PixelariumGallery.cpp | 4 +- lib/app/PixelariumGallery.hpp | 9 +- lib/app/app_resources_default.h.in | 3 + lib/{ => app}/rendering/CvMatRender.cpp | 14 +- lib/{ => app}/rendering/CvMatRender.hpp | 4 +- lib/app/rendering/IPixelariumImageView.cpp | 26 +++ .../rendering/IPixelariumImageView.hpp | 16 +- lib/{ => app}/rendering/ImageViewFactory.cpp | 8 +- lib/{ => app}/rendering/ImageViewFactory.hpp | 10 +- .../rendering/PixelariumImageViewCzi.cpp | 53 ++++-- .../rendering/PixelariumImageViewCzi.hpp | 15 +- .../rendering/PixelariumImageViewDefault.cpp | 35 +++- .../rendering/PixelariumImageViewDefault.hpp | 16 +- lib/{ => app}/rendering/RenderHelpers.cpp | 5 +- lib/{ => app}/rendering/RenderHelpers.hpp | 4 +- .../rendering/RenderImageManager.cpp | 10 +- .../rendering/RenderImageManager.hpp | 6 +- lib/imaging/CMakeLists.txt | 1 + lib/imaging/IPixelariumImage.cpp | 15 ++ lib/imaging/IPixelariumImage.hpp | 64 ++++++- lib/imaging/PixelariumImageFactory.cpp | 3 +- lib/imaging/PixelariumImageFactory.hpp | 2 +- lib/imaging/impl/PixelariumCzi.cpp | 18 +- lib/imaging/impl/PixelariumCzi.hpp | 11 +- lib/imaging/impl/PixelariumJpg.cpp | 6 +- lib/imaging/impl/PixelariumJpg.hpp | 9 +- lib/imaging/impl/PixelariumMem.cpp | 4 +- lib/imaging/impl/PixelariumMem.hpp | 8 +- lib/imaging/impl/PixelariumPng.cpp | 4 +- lib/imaging/impl/PixelariumPng.hpp | 8 +- lib/imaging/impl/PixelariumTiff.cpp | 4 +- lib/imaging/impl/PixelariumTiff.hpp | 8 +- lib/rendering/CMakeLists.txt | 26 --- lib/resources/resource.cpp | 13 +- lib/resources/resource.hpp | 18 +- lib/utilities/CMakeLists.txt | 4 +- lib/utilities/simple_thread_pool.cpp | 47 +++++ lib/utilities/simple_thread_pool.hpp | 56 ++++++ tests/lib/resources/test_resource.cpp | 15 +- 52 files changed, 712 insertions(+), 207 deletions(-) rename lib/{ => app}/rendering/CvMatRender.cpp (87%) rename lib/{ => app}/rendering/CvMatRender.hpp (94%) create mode 100644 lib/app/rendering/IPixelariumImageView.cpp rename lib/{ => app}/rendering/IPixelariumImageView.hpp (62%) rename lib/{ => app}/rendering/ImageViewFactory.cpp (87%) rename lib/{ => app}/rendering/ImageViewFactory.hpp (73%) rename lib/{ => app}/rendering/PixelariumImageViewCzi.cpp (75%) rename lib/{ => app}/rendering/PixelariumImageViewCzi.hpp (78%) rename lib/{ => app}/rendering/PixelariumImageViewDefault.cpp (66%) rename lib/{ => app}/rendering/PixelariumImageViewDefault.hpp (78%) rename lib/{ => app}/rendering/RenderHelpers.cpp (82%) rename lib/{ => app}/rendering/RenderHelpers.hpp (71%) rename lib/{ => app}/rendering/RenderImageManager.cpp (82%) rename lib/{ => app}/rendering/RenderImageManager.hpp (94%) create mode 100644 lib/imaging/IPixelariumImage.cpp delete mode 100644 lib/rendering/CMakeLists.txt create mode 100644 lib/utilities/simple_thread_pool.cpp create mode 100644 lib/utilities/simple_thread_pool.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d385464..414a775 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(pixelarium VERSION 0.0.10) +project(pixelarium VERSION 0.0.11) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_CXX_STANDARD 23) diff --git a/doc/versions.md b/doc/versions.md index dcebff2..03d5b8f 100644 --- a/doc/versions.md +++ b/doc/versions.md @@ -1,16 +1,17 @@ # Version History -| Version | Description | -|:-------:|:------------------------------------------------------------------------------------------------------------| -| 0.0.10| Adds Tiff-support, in-memory images, and advances usage example "custom_1" | -| 0.0.9 | Improve documentation, add example for `DefaultApp` override semantics | -| 0.0.8 | Init example projects | -| 0.0.7 | Refactors image gallery logic from `DefaultApp` into a separate module | -| 0.0.6 | Added documentation-only option `PIXELARIUM_BUILD_DOCS_ONLY`, libCZI upgrade to main branch CI improvements | -| 0.0.5 | Compile for C++23 and code style adaptions | -| 0.0.4 | Fix MSVC build, some cosmetics, explicit initial window size for images | -| 0.0.3 | Fetch subblocks based on the dimension selection sliders | -| 0.0.2 | Add Dimension selector sliders to CZI image view | -| 0.0.1 | Initiate CZI image view | -| 0.0.0 | Initial version | +| Version | Description | +|:--------:|:------------------------------------------------------------------------------------------------------------| +| 0.0.11 | Miscellaneous refactoring | +| 0.0.10 | Adds Tiff-support, in-memory images, and advances usage example "custom_1" | +| 0.0.9 | Improve documentation, add example for `DefaultApp` override semantics | +| 0.0.8 | Init example projects | +| 0.0.7 | Refactors image gallery logic from `DefaultApp` into a separate module | +| 0.0.6 | Added documentation-only option `PIXELARIUM_BUILD_DOCS_ONLY`, libCZI upgrade to main branch CI improvements | +| 0.0.5 | Compile for C++23 and code style adaptions | +| 0.0.4 | Fix MSVC build, some cosmetics, explicit initial window size for images | +| 0.0.3 | Fetch subblocks based on the dimension selection sliders | +| 0.0.2 | Add Dimension selector sliders to CZI image view | +| 0.0.1 | Initiate CZI image view | +| 0.0.0 | Initial version | diff --git a/examples/custom_0/CMakeLists.txt b/examples/custom_0/CMakeLists.txt index 2bd3ed3..8062b27 100644 --- a/examples/custom_0/CMakeLists.txt +++ b/examples/custom_0/CMakeLists.txt @@ -7,7 +7,6 @@ add_executable(${CUSTOM_0_NAME} ${SRC}) target_link_libraries(${CUSTOM_0_NAME} PRIVATE pixelariumimagelib - PRIVATE pixelariumrenderlib PRIVATE pixelariumutilslib PRIVATE pixelariumresourcelib PRIVATE pixelariumapplicationlib) diff --git a/examples/custom_0/custom_0.cpp b/examples/custom_0/custom_0.cpp index 16157ea..1d5b5b6 100644 --- a/examples/custom_0/custom_0.cpp +++ b/examples/custom_0/custom_0.cpp @@ -1,7 +1,16 @@ +#include + +#include +#include +#include +#include #include #include #include "DefaultApp.hpp" +#include "imgui.h" +#include "impl/PixelariumMem.hpp" +#include "portable-file-dialogs.h" #include "resources/resource.hpp" #include "utilities/ILog.hpp" #include "utilities/SpdLogger.hpp" @@ -20,9 +29,165 @@ unique_ptr logger{ make_unique(string(getenv("HOME")) + "/.cache/pixelarium/simple_app.log", "default")}; #endif +// instantiate an image pool for the application +resources::ImageResourcePool image_pool; + +constexpr auto ToCVPixelType(size_t depth, size_t chans) -> int +{ + int tp{}; + switch (depth) + { + case 8: + tp = CV_8U; + break; + case 16: + tp = CV_16U; + break; + default: + return -1; + } + + if (chans > 1) + { + return CV_MAKETYPE(tp, chans); + } + + return tp; +} + +struct StatusReport +{ + const std::function report_status; + const std::function reset_status; +}; + +class BinaryReader +{ + private: + filesystem::path bin_file{}; + vector buffer{}; + uintmax_t file_size; + + // struct __attribute__((packed)) ParsedImage // gcc and clang only +#pragma pack(push, 1) + struct ParsedImage + { + uint8_t depth; + uint8_t channels; + uint16_t width; + uint16_t height; + void* data; + }; +#pragma pack(pop) + + auto RegisterImage(const ParsedImage& img, string& name, const StatusReport& report) -> void + { + if (img.width == 0 || img.height == 0 || img.channels == 0 || img.depth == 0 || !img.data) + { + report.reset_status(); + report.report_status( + format("Parsing {} failed: Dimensions (w: {}, h: {}, d: {}, c: {}) cannot be parsed! The provided " + "bin-file is probably corrupted.", + name, img.width, img.height, img.depth, img.channels)); + return; + } + + auto tmp_mat = + cv::Mat(img.height, img.width, ToCVPixelType(img.depth, img.channels), const_cast(img.data)); + // not cloning is a dangling reference once the externally managed data pointer is freed + auto mat{tmp_mat.clone()}; + + image_pool.SetResource(make_unique(mat, name.c_str(), *logger)); + } + + auto ReadFile(const filesystem::path& file, const StatusReport& report) -> ParsedImage + { + uint8_t depth{}; + uint8_t channels{}; + uint16_t width{}; + uint16_t height{}; + uint64_t pixel_count{}; + if (!filesystem::exists(file)) return {}; + + auto sz = filesystem::file_size(file); + // header layout of binary file + // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | + // | ^ | ^ | ^ | ^ | pixel count in byte | + // | |______ |__ | + // pixel depth | | height in px + // | width in px + // channel count + constexpr auto header_size{14}; + + bool can_read = sz >= header_size; + + if (!can_read) return {}; + + if (!buffer.empty()) + { + buffer.clear(); + } + + // buffer = static_cast(malloc(sz)); + ifstream inp_stream(file, ios::binary); + if (inp_stream) + { + inp_stream.read(reinterpret_cast(&width), sizeof(width)); + inp_stream.read(reinterpret_cast(&height), sizeof(height)); + inp_stream.read(reinterpret_cast(&depth), sizeof(depth)); + inp_stream.read(reinterpret_cast(&channels), sizeof(channels)); + inp_stream.read(reinterpret_cast(&pixel_count), sizeof(pixel_count)); + logger->Info(format("{}(): Pixel count {}", __FUNCTION__, pixel_count)); + + if (pixel_count <= sz - header_size) + { + buffer.resize(pixel_count); + inp_stream.read(reinterpret_cast(buffer.data()), pixel_count); + } + } + + logger->Info(format("{}: Parsed image with width: {}, height: {}, depth: {}, channels: {}", __PRETTY_FUNCTION__, + width, height, depth, channels)); + report.report_status( + format("Parsed image with width: {}, height: {}, depth: {}, channels: {}", width, height, depth, channels)); + + return {.depth = depth, .channels = channels, .width = width, .height = height, .data = buffer.data()}; + } + + public: + auto Present(const StatusReport& report) -> void + { + using namespace ImGui; + SetNextWindowSize({256, 124}); + Begin("Load Binary File"); + if (Button("Load File")) + { + auto res{pfd::open_file("Load Inputs", pfd::path::home(), {"Bin Files", "*.bin"}).result()}; + if (!res.empty()) bin_file = filesystem::path(res.at(0)); + } + + if (filesystem::exists(bin_file)) + { + file_size = filesystem::file_size(bin_file); + Text("File: %s (%lu)", bin_file.filename().c_str(), file_size); + if (Button("Parse File")) + { + auto buff = ReadFile(bin_file, report); + auto name = bin_file.filename().string(); + RegisterImage(buff, name, report); + } + } + + End(); + } +}; + // create a custom app inheriting from the library's default app class MyApp : public application::DefaultApp { + private: + BinaryReader bin_read; + public: MyApp(const Log& log, Pool& pool) : application::DefaultApp(log, pool) {} @@ -37,9 +202,6 @@ int main() // some initial log message logger->Info(std::format("{}: Starting Application {}", __FUNCTION__, "Pixelarium")); - // instantiate an image pool for the application - resources::ImageResourcePool image_pool; - // create a custom application, inject its dependencies and start it auto app{MyApp(*logger, image_pool)}; @@ -50,4 +212,8 @@ void MyApp::Run() { this->gallery_.RenderGallery(); this->gallery_.RenderImages(); + const auto reporter = + StatusReport{.report_status = [this](const std::string& msg) { this->SetStatusTimed(msg, 5); }, + .reset_status = [this]() { this->ResetStatus(); }}; + bin_read.Present(reporter); } diff --git a/examples/custom_1/CMakeLists.txt b/examples/custom_1/CMakeLists.txt index 31ee14b..9f8643d 100644 --- a/examples/custom_1/CMakeLists.txt +++ b/examples/custom_1/CMakeLists.txt @@ -7,7 +7,6 @@ add_executable(${CUSTOM_1_NAME} ${SRC}) target_link_libraries(${CUSTOM_1_NAME} PRIVATE pixelariumimagelib - PRIVATE pixelariumrenderlib PRIVATE pixelariumutilslib PRIVATE pixelariumresourcelib PRIVATE pixelariumapplicationlib) diff --git a/examples/custom_1/custom_1.cpp b/examples/custom_1/custom_1.cpp index d144ad8..5912324 100644 --- a/examples/custom_1/custom_1.cpp +++ b/examples/custom_1/custom_1.cpp @@ -52,7 +52,7 @@ class Selector if (ImGui::BeginCombo("Select first image", preview_0_.c_str())) { pool_.Enumerate( - [&](resources::ResourceKey key, size_t idx, const imaging::IPixelariumImage& img) -> void + [&](resources::ResourceKey key, size_t idx, const imaging::IPixelariumImage& img) -> void { const bool is_selected = static_cast(idx) == selected_idx_0; if (ImGui::Selectable(img.Name().c_str(), is_selected)) @@ -74,7 +74,7 @@ class Selector if (ImGui::BeginCombo("Select second image", preview_1_.c_str())) { pool_.Enumerate( - [&](resources::ResourceKey key, size_t idx, const imaging::IPixelariumImage& img) -> void + [&](resources::ResourceKey key, size_t idx, const imaging::IPixelariumImage& img) -> void { const bool is_selected = static_cast(idx) == selected_idx_1; if (ImGui::Selectable(img.Name().c_str(), is_selected)) @@ -100,11 +100,12 @@ class Selector auto img1 = pool_.GetResource(selected_key_1); auto img_mat1 = img1.lock()->TryGetImage(); - if (img_mat0 == nullptr || img_mat1 == nullptr || img_mat0->empty() || img_mat1->empty()) return; + if (!img_mat0.has_value() || !img_mat1.has_value() || img_mat0.value().empty() || img_mat1.value().empty()) + return; if (img_mat0->size != img_mat1->size) return; - cv::multiply(*img_mat0, *img_mat1, *img_mat0); + cv::multiply(img_mat0.value(), img_mat1.value(), img_mat0.value()); std::string name{std::format("Multiply_{}", idx_)}; pool_.SetResource(std::make_unique(*img_mat0, name, *logger)); diff --git a/examples/simple/CMakeLists.txt b/examples/simple/CMakeLists.txt index 6b40178..12962c5 100644 --- a/examples/simple/CMakeLists.txt +++ b/examples/simple/CMakeLists.txt @@ -7,7 +7,6 @@ add_executable(${SIMPLE_NAME} ${SRC}) target_link_libraries(${SIMPLE_NAME} PRIVATE pixelariumimagelib - PRIVATE pixelariumrenderlib PRIVATE pixelariumutilslib PRIVATE pixelariumresourcelib PRIVATE pixelariumapplicationlib) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 071482d..f771199 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory(utilities) add_subdirectory(imaging) -add_subdirectory(rendering) add_subdirectory(resources) add_subdirectory(app) diff --git a/lib/app/AppGLFW.cpp b/lib/app/AppGLFW.cpp index ab85185..1a9b111 100644 --- a/lib/app/AppGLFW.cpp +++ b/lib/app/AppGLFW.cpp @@ -4,6 +4,56 @@ #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" +#include "imgui_internal.h" +#include "utilities/simple_thread_pool.hpp" + +// see https://github.com/ocornut/imgui/issues/3518 +bool PixelBeginStatusBar() +{ + using namespace ImGui; + ImGuiContext& g = *GImGui; + ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport(); + + SetCurrentViewport(NULL, viewport); + + g.NextWindowData.MenuBarOffsetMinVal = ImVec2( + g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); + ImGuiWindowFlags window_flags = + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; + float height = GetFrameHeight(); + bool is_open = BeginViewportSideBar("##MainStatusBar", viewport, ImGuiDir_Up, height, window_flags); + g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); + if (!is_open) + { + End(); + return false; + } + + g.CurrentWindow->Flags &= ~ImGuiWindowFlags_NoSavedSettings; + BeginMenuBar(); + return is_open; +} + +void PixelEndStatusBar() +{ + using namespace ImGui; + ImGuiContext& g = *GImGui; + if (!g.CurrentWindow->DC.MenuBarAppending) + { + IM_ASSERT_USER_ERROR(0, "Calling EndMainMenuBar() not from a menu-bar!"); + return; + } + + EndMenuBar(); + g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; + + if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest && g.ActiveId == 0) + FocusTopMostWindowUnderOne( + g.NavWindow, NULL, NULL, + ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild); + + End(); +} /// @brief GLFW error callback function. /// @param error The error code. @@ -193,6 +243,18 @@ void pixelarium::application::AppGLFW::MenuBar() ImGui::EndMainMenuBar(); } + + if (show_status_ && PixelBeginStatusBar()) + { + if (ImGui::Button("Ok", {20, 20})) + { + show_status_ = false; + status_message_.clear(); + } + + ImGui::Text("%s", status_message_.c_str()); + PixelEndStatusBar(); + } } /// @brief Allows the user to select the log level via a combo box. @@ -213,3 +275,14 @@ void pixelarium::application::AppGLFW::LogLevelSelect() ImGui::EndCombo(); } } + +void pixelarium::application::AppGLFW::SetStatusTimed(const std::string& status, size_t seconds) +{ + SetStatus(status); + utils::simple_thread_pool::run_asynch( + [this, seconds]() + { + std::this_thread::sleep_for(std::chrono::seconds(seconds)); + ResetStatus(); + }); +} diff --git a/lib/app/AppGLFW.hpp b/lib/app/AppGLFW.hpp index a3aee6c..bf8bb84 100644 --- a/lib/app/AppGLFW.hpp +++ b/lib/app/AppGLFW.hpp @@ -2,9 +2,8 @@ #include -#include +#include -#include "imgui.h" #include "utilities/ILog.hpp" namespace pixelarium::application @@ -19,6 +18,21 @@ class AppGLFW /// @brief Start the main render loop void Start() { this->RunLoop(); } + void SetStatusTimed(const std::string& status, size_t second); + + void SetStatus(const std::string& status) + { + logger_.Info(std::format("{}(): {}", __PRETTY_FUNCTION__, status)); + status_message_ = status; + show_status_ = true; + } + + void ResetStatus() + { + status_message_.clear(); + show_status_ = false; + } + protected: /// @brief Function implementing the first column of the menu bar (e.g. "Menu") virtual void MenuBarOptionsColumn1() {} @@ -47,5 +61,7 @@ class AppGLFW void LogLevelSelect(); int log_level_{0}; GLFWwindow* window = nullptr; + bool show_status_{false}; + std::string status_message_{}; }; } // namespace pixelarium::application diff --git a/lib/app/CMakeLists.txt b/lib/app/CMakeLists.txt index f670e38..8280012 100644 --- a/lib/app/CMakeLists.txt +++ b/lib/app/CMakeLists.txt @@ -16,15 +16,30 @@ set(APPLIBSRC ${imgui_DIR}/backends/imgui_impl_opengl3.cpp ${imgui_DIR}/backends/imgui_impl_glfw.cpp) +set(RENDERSRC + rendering/RenderHelpers.hpp + rendering/RenderHelpers.cpp + rendering/CvMatRender.hpp + rendering/CvMatRender.cpp + rendering/RenderImageManager.hpp + rendering/RenderImageManager.cpp + rendering/IPixelariumImageView.hpp + rendering/IPixelariumImageView.cpp + rendering/PixelariumImageViewDefault.hpp + rendering/PixelariumImageViewDefault.cpp + rendering/PixelariumImageViewCzi.hpp + rendering/PixelariumImageViewCzi.cpp + rendering/ImageViewFactory.hpp + rendering/ImageViewFactory.cpp) + set(APPLIBNAME pixelariumapplicationlib) add_library(${APPLIBNAME} - STATIC ${APPLIBSRC}) + STATIC ${APPLIBSRC} ${RENDERSRC}) target_link_libraries(${APPLIBNAME} PRIVATE pixelariumutilslib - PRIVATE pixelariumimagelib - PRIVATE pixelariumrenderlib) + PRIVATE pixelariumimagelib) # This needs to be public to let the consumer know about it. if(WIN32) @@ -47,6 +62,7 @@ target_include_directories(${APPLIBNAME} PRIVATE ${CMAKE_BINARY_DIR} PRIVATE ${PROJECT_SOURCE_DIR}/lib PRIVATE ${PROJECT_SOURCE_DIR}/lib/imaging + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/rendering PUBLIC ${pfd_DIR} PUBLIC ${imgui_DIR} PUBLIC ${imgui_DIR}/backends) diff --git a/lib/app/DefaultApp.cpp b/lib/app/DefaultApp.cpp index 04ffde5..f04648a 100644 --- a/lib/app/DefaultApp.cpp +++ b/lib/app/DefaultApp.cpp @@ -25,7 +25,7 @@ void DefaultApp::MenuBarOptionsColumn2() { if (ImGui::MenuItem(LOADIMAGE)) { - this->LoadImage(); + this->LoadImageDialogue(); } ImGui::EndMenu(); @@ -40,7 +40,7 @@ void DefaultApp::Run() this->gallery_.RenderImages(); } -void DefaultApp::LoadImage() +void DefaultApp::LoadImageDialogue() { auto res{pfd::open_file("Load Inputs", pfd::path::home(), {"All Files", "*"}, pfd::opt::multiselect).result()}; for (auto& p : res) diff --git a/lib/app/DefaultApp.hpp b/lib/app/DefaultApp.hpp index 1dde81a..0ba9d60 100644 --- a/lib/app/DefaultApp.hpp +++ b/lib/app/DefaultApp.hpp @@ -19,7 +19,7 @@ class DefaultApp : public AppGLFW DefaultApp(const utils::log::ILog& log, pixelarium::resources::ImageResourcePool& pool) : application::AppGLFW(log), pool_(pool), gallery_(log, pool) { - gallery_.SetLoadFunction([&]() -> void { this->LoadImage(); }); + gallery_.SetLoadFunction([&]() -> void { this->LoadImageDialogue(); }); } protected: @@ -32,7 +32,7 @@ class DefaultApp : public AppGLFW application::PixelariumImageGallery gallery_; protected: - void LoadImage(); + void LoadImageDialogue(); private: bool image_listp_{true}; diff --git a/lib/app/PixelariumGallery.cpp b/lib/app/PixelariumGallery.cpp index 835d69d..d2826e3 100644 --- a/lib/app/PixelariumGallery.cpp +++ b/lib/app/PixelariumGallery.cpp @@ -23,7 +23,7 @@ void PixelariumImageGallery::RenderGallery() if (ImGui::BeginListBox("Image List", ImVec2(200, 400))) { pool_.EnumerateResources( - [&](size_t id, size_t idx, const imaging::IPixelariumImage& img) -> void + [&](size_t id, size_t idx, const imaging::IPixelariumImage& img) -> void { const bool is_selected = selected_index == idx; if (ImGui::Selectable(std::format("{}", img.Name()).c_str(), is_selected)) @@ -74,7 +74,7 @@ void PixelariumImageGallery::RenderGallery() void PixelariumImageGallery::RenderImages() { this->render_manager_->Enumerate( - [&](resources::ResourceKey key, render::RenderImageStateWrapper& render_state) + [&](resources::ResourceKey key, application::RenderImageStateWrapper& render_state) { render_state.view->ShowImage(); diff --git a/lib/app/PixelariumGallery.hpp b/lib/app/PixelariumGallery.hpp index f1d3813..e6eaff4 100644 --- a/lib/app/PixelariumGallery.hpp +++ b/lib/app/PixelariumGallery.hpp @@ -3,13 +3,14 @@ #include "rendering/RenderImageManager.hpp" #include "resources/resource.hpp" #include "utilities/ILog.hpp" + namespace pixelarium::application { /// @brief Defines a concept for a gallery type /// @tparam P The resource pool type of the gallery concept -template -concept GalleryT = requires(P& p) { static_cast&>(p); }; +template +concept GalleryT = requires(P& p) { static_cast&>(p); }; /// @brief Interface for a Pixelarium gallery. /// @@ -33,7 +34,7 @@ class PixelariumImageGallery : IPixelariumGallery public: PixelariumImageGallery(const Log& log, resources::ImageResourcePool& pool) - : pool_{pool}, log_{log}, render_manager_(std::make_unique(pool, log)) + : pool_{pool}, log_{log}, render_manager_(std::make_unique(pool, log)) { } @@ -49,7 +50,7 @@ class PixelariumImageGallery : IPixelariumGallery std::function load_image_{}; Pool& pool_; const Log& log_; - std::unique_ptr render_manager_; + std::unique_ptr render_manager_; bool image_listp_{true}; bool auto_show_selectd_image_{true}; size_t selected_image_{0}; diff --git a/lib/app/app_resources_default.h.in b/lib/app/app_resources_default.h.in index fd4bc4d..84f27f0 100644 --- a/lib/app/app_resources_default.h.in +++ b/lib/app/app_resources_default.h.in @@ -17,6 +17,9 @@ #define LOADIMAGE "Load Image" #define REMOVEIMAGE "Remove Image" #define CLEARALL "Clear All" +#define SAVEAS "Save As..." // clang-format on inline constexpr std::array LOGLEVELS = {"Trace", "Debug", "Info", "Warning", "Error"}; + +inline constexpr auto kInitialWindowWidth {700.0f}; diff --git a/lib/rendering/CvMatRender.cpp b/lib/app/rendering/CvMatRender.cpp similarity index 87% rename from lib/rendering/CvMatRender.cpp rename to lib/app/rendering/CvMatRender.cpp index ef65228..efcb9ef 100644 --- a/lib/rendering/CvMatRender.cpp +++ b/lib/app/rendering/CvMatRender.cpp @@ -9,7 +9,7 @@ using namespace pixelarium::imaging; /// @brief Constructor for the CvMatRender class. /// @param img A shared pointer to the PixelariumImage to be rendered. -pixelarium::render::CvMatRender::CvMatRender(const cv::Mat& img) : base_(img), texture_(0) +pixelarium::application::CvMatRender::CvMatRender(const cv::Mat& img) : base_(img), texture_(0) { // storing a copy of the to-be-rendered image // because it will be resized and filtered eventually which we absolutely @@ -20,7 +20,7 @@ pixelarium::render::CvMatRender::CvMatRender(const cv::Mat& img) : base_(img), t /// @brief Destructor for the CvMatRender class. /// Deallocates the OpenGL texture if it exists. -pixelarium::render::CvMatRender::~CvMatRender() +pixelarium::application::CvMatRender::~CvMatRender() { if (texture_) { @@ -32,7 +32,7 @@ pixelarium::render::CvMatRender::~CvMatRender() /// @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() +GLuint pixelarium::application::CvMatRender::uploadTexture() { if (img_.empty()) { @@ -89,12 +89,12 @@ GLuint pixelarium::render::CvMatRender::uploadTexture() /// @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(); } +GLuint pixelarium::application::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) +GLuint pixelarium::application::CvMatRender::Render(float factor) { cv::resize(this->base_, this->img_, cv::Size(0, 0), factor, factor, cv::INTER_LINEAR_EXACT); @@ -105,7 +105,7 @@ GLuint pixelarium::render::CvMatRender::Render(float factor) /// @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) +GLuint pixelarium::application::CvMatRender::Render(size_t width, size_t height) { const auto sz{this->base_.size()}; @@ -116,7 +116,7 @@ GLuint pixelarium::render::CvMatRender::Render(size_t width, size_t height) return this->Render(factor); } -void pixelarium::render::CvMatRender::ResetRenderImage() +void pixelarium::application::CvMatRender::ResetRenderImage() { // we copy here this->img_ = this->base_.clone(); diff --git a/lib/rendering/CvMatRender.hpp b/lib/app/rendering/CvMatRender.hpp similarity index 94% rename from lib/rendering/CvMatRender.hpp rename to lib/app/rendering/CvMatRender.hpp index f776cab..9066db5 100644 --- a/lib/rendering/CvMatRender.hpp +++ b/lib/app/rendering/CvMatRender.hpp @@ -15,7 +15,7 @@ #include // clang-format on -namespace pixelarium::render +namespace pixelarium::application { /// @brief Renders cv::Mat bitmaps as OpenGL textures. class CvMatRender @@ -49,4 +49,4 @@ class CvMatRender GLuint uploadTexture(); }; -} // namespace pixelarium::render +} // namespace pixelarium::application diff --git a/lib/app/rendering/IPixelariumImageView.cpp b/lib/app/rendering/IPixelariumImageView.cpp new file mode 100644 index 0000000..4f93676 --- /dev/null +++ b/lib/app/rendering/IPixelariumImageView.cpp @@ -0,0 +1,26 @@ +#include "IPixelariumImageView.hpp" + +#include + +#include "app_resources_default.h" +#include "portable-file-dialogs.h" + +auto pixelarium::application::IPixelariumImageView::ImageViewMenuBar() -> void +{ + if (ImGui::BeginMenuBar()) + { + if (ImGui::MenuItem(SAVEAS)) + { + auto dest = pfd::save_file("Save File", ".", {"Image Files", "*.png *.jpg *.jpeg *.tiff"}, + pfd::opt::force_overwrite) + .result(); + if (!dest.empty()) + { + // this->img_->SaveImage(dest); + cv::imwrite(dest, cached_image_); + } + } + + ImGui::EndMenuBar(); + } +} diff --git a/lib/rendering/IPixelariumImageView.hpp b/lib/app/rendering/IPixelariumImageView.hpp similarity index 62% rename from lib/rendering/IPixelariumImageView.hpp rename to lib/app/rendering/IPixelariumImageView.hpp index c76d041..e92ca14 100644 --- a/lib/rendering/IPixelariumImageView.hpp +++ b/lib/app/rendering/IPixelariumImageView.hpp @@ -5,7 +5,7 @@ #include "imaging/IPixelariumImage.hpp" #include "imgui.h" -namespace pixelarium::render +namespace pixelarium::application { /// @brief An interface defining the contract on views to dedicated implementations of IPixelariumImage class IPixelariumImageView @@ -18,15 +18,23 @@ class IPixelariumImageView public: virtual const bool* GetStatus() const noexcept { return &this->open_p; } virtual void ForceUpdate() noexcept { this->is_dirty_ = true; } + + // this must be called immediately before a "ImGui::Begin" context + // as it will affect the next window and result in undeterministic effects + // when called "out of sync" virtual void SetInitialSize(float width = 700.0f, float height = 700.0f) { ImGui::SetNextWindowSize({width, height}); } protected: - std::shared_ptr img_{}; - std::unique_ptr cached_image_{}; + virtual void ImageViewMenuBar(); + + protected: + std::shared_ptr img_{}; + cv::Mat cached_image_{}; bool open_p{true}; bool is_dirty_{true}; + bool first_render_{true}; }; -} // namespace pixelarium::render +} // namespace pixelarium::application diff --git a/lib/rendering/ImageViewFactory.cpp b/lib/app/rendering/ImageViewFactory.cpp similarity index 87% rename from lib/rendering/ImageViewFactory.cpp rename to lib/app/rendering/ImageViewFactory.cpp index 34b4cf0..cec9a3a 100644 --- a/lib/rendering/ImageViewFactory.cpp +++ b/lib/app/rendering/ImageViewFactory.cpp @@ -3,17 +3,17 @@ #include #include +#include "IPixelariumImageView.hpp" +#include "PixelariumImageViewCzi.hpp" +#include "PixelariumImageViewDefault.hpp" #include "imaging/IPixelariumImage.hpp" #include "imaging/PixelariumImageFactory.hpp" -#include "rendering/IPixelariumImageView.hpp" -#include "rendering/PixelariumImageViewCzi.hpp" -#include "rendering/PixelariumImageViewDefault.hpp" /// @brief Creates a PixelariumImageView from a resource image. /// @param image_id The ID of the image resource to render. /// @return A unique pointer to the PixelariumImageView, or nullptr if the image resource is not found or is empty. The /// image data is copied. -std::unique_ptr pixelarium::render::ImageViewFactory::RenderImage( +std::unique_ptr pixelarium::application::ImageViewFactory::RenderImage( resources::ResourceKey image_id) { using ImageType = imaging::ImageFileType; diff --git a/lib/rendering/ImageViewFactory.hpp b/lib/app/rendering/ImageViewFactory.hpp similarity index 73% rename from lib/rendering/ImageViewFactory.hpp rename to lib/app/rendering/ImageViewFactory.hpp index a56c5ed..77b5cdf 100644 --- a/lib/rendering/ImageViewFactory.hpp +++ b/lib/app/rendering/ImageViewFactory.hpp @@ -1,15 +1,15 @@ #pragma once -#include "PixelariumImageViewDefault.hpp" -#include "rendering/IPixelariumImageView.hpp" +#include "IPixelariumImageView.hpp" #include "resources/resource.hpp" #include "utilities/ILog.hpp" -namespace pixelarium::render + +namespace pixelarium::application { /// @brief Factory for instantiating matching views to different implementations of IPixelariumImage. class ImageViewFactory { - using Image = imaging::IPixelariumImage; + using Image = imaging::IPixelariumImageCvMat; using Pool = resources::ImageResourcePool; using Log = utils::log::ILog; @@ -22,4 +22,4 @@ class ImageViewFactory Pool& image_pool_; const Log& log_; }; -} // namespace pixelarium::render +} // namespace pixelarium::application diff --git a/lib/rendering/PixelariumImageViewCzi.cpp b/lib/app/rendering/PixelariumImageViewCzi.cpp similarity index 75% rename from lib/rendering/PixelariumImageViewCzi.cpp rename to lib/app/rendering/PixelariumImageViewCzi.cpp index a761a11..aef35bb 100644 --- a/lib/rendering/PixelariumImageViewCzi.cpp +++ b/lib/app/rendering/PixelariumImageViewCzi.cpp @@ -3,13 +3,29 @@ #include #include +#include "CvMatRender.hpp" #include "RenderHelpers.hpp" #include "imaging/IPixelariumImage.hpp" #include "imaging/impl/PixelariumCzi.hpp" #include "imgui.h" -#include "rendering/CvMatRender.hpp" -pixelarium::render::PixelariumImageViewCzi::PixelariumImageViewCzi(std::shared_ptr img, const Log& log) +void pixelarium::application::PixelariumImageViewCzi::RefreshCachedImage() +{ + if (this->cached_image_.empty() || this->is_dirty_) + { + log_.Info(std::format("{}: refreshing image.", __PRETTY_FUNCTION__)); + imaging::CziParams params; + params.dimension_map = this->dimension_map_; + this->cached_image_ = this->img_->TryGetImage(params).value_or(cv::Mat{}); + // Resetting the image while the renderer is possibly accessing the + // image at the same time is not a good idea. Therefore, we simply create + // a new renderer here. + this->render_ = std::make_unique(this->cached_image_); + this->is_dirty_ = false; + } +} + +pixelarium::application::PixelariumImageViewCzi::PixelariumImageViewCzi(std::shared_ptr img, const Log& log) : log_(log), render_(std::make_unique(*img->TryGetImage())) { img_ = img; @@ -23,7 +39,7 @@ pixelarium::render::PixelariumImageViewCzi::PixelariumImageViewCzi(std::shared_p return true; }); - this->SetInitialSize(); + // this->SetInitialSize(); log_.Info(std::format("{}: dimension map size: {}", __PRETTY_FUNCTION__, dimension_map_.size())); } @@ -32,35 +48,36 @@ pixelarium::render::PixelariumImageViewCzi::PixelariumImageViewCzi(std::shared_p /// If the image is null, empty, or has an empty name, the function returns immediately. Otherwise, it creates an ImGui /// window with a horizontal scrollbar and menu bar. The image is rendered using the CvMatRender object, resizing it to /// fit the available window space. The raw and rendered dimensions are displayed below the image. -void pixelarium::render::PixelariumImageViewCzi::ShowImage() +void pixelarium::application::PixelariumImageViewCzi::ShowImage() { auto czi_img = std::static_pointer_cast(this->img_); if (!czi_img) return; - if (!this->cached_image_ || this->is_dirty_) - { - log_.Info(std::format("{}: refreshing image.", __PRETTY_FUNCTION__)); - imaging::CziParams params; - params.dimension_map = this->dimension_map_; - this->cached_image_ = this->img_->TryGetImage(params); - // Resetting the image while the renderer is possibly accessing the - // image at the same time is not a good idea. Therefore, we simply create - // a new renderer here. - this->render_ = std::make_unique(*this->cached_image_); - this->is_dirty_ = false; - } + RefreshCachedImage(); - if (czi_img->Empty() || this->img_->type_ == imaging::ImageFileType::kUnknown || !cached_image_ || + if (czi_img->Empty() || this->img_->type_ == imaging::ImageFileType::kUnknown || cached_image_.empty() || czi_img->Name().empty()) { // do nothing return; } + if (first_render_) + { + first_render_ = false; + constexpr auto initial_width{700.0f}; + const auto cached_width{cached_image_.cols}; + const auto cached_heigth{cached_image_.rows}; + const auto ratio{static_cast(cached_heigth) / cached_width}; + SetInitialSize(initial_width, (initial_width * ratio + 100)); + } + ImGui::Begin(this->img_->Name().c_str(), &this->open_p, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar); + ImageViewMenuBar(); + this->curr_dim_ = ImGui::GetContentRegionAvail(); auto new_dim = ImGui::GetContentRegionAvail(); auto texture = @@ -70,7 +87,7 @@ void pixelarium::render::PixelariumImageViewCzi::ShowImage() this->curr_dim_ = new_dim; - ImVec2 dim(cached_image_->cols, cached_image_->rows); + ImVec2 dim(cached_image_.cols, cached_image_.rows); ImGui::Image(reinterpret_cast(reinterpret_cast(texture)), aspect_const_dimensions(dim, new_dim)); diff --git a/lib/rendering/PixelariumImageViewCzi.hpp b/lib/app/rendering/PixelariumImageViewCzi.hpp similarity index 78% rename from lib/rendering/PixelariumImageViewCzi.hpp rename to lib/app/rendering/PixelariumImageViewCzi.hpp index 7e301c1..6f9b058 100644 --- a/lib/rendering/PixelariumImageViewCzi.hpp +++ b/lib/app/rendering/PixelariumImageViewCzi.hpp @@ -3,19 +3,19 @@ #include #include +#include "CvMatRender.hpp" +#include "IPixelariumImageView.hpp" +#include "imaging/IPixelariumImage.hpp" #include "imgui.h" #include "libCZI_DimCoordinate.h" -#include "rendering/CvMatRender.hpp" -#include "rendering/IPixelariumImageView.hpp" #include "utilities/ILog.hpp" -namespace pixelarium::render +namespace pixelarium::application { /// @brief A CZI-specific implementation of IPixelariumImageView. class PixelariumImageViewCzi : public IPixelariumImageView { - using Image = imaging::IPixelariumImage; - using Render = render::CvMatRender; + using Image = imaging::IPixelariumImageCvMat; using Log = utils::log::ILog; public: @@ -34,5 +34,8 @@ class PixelariumImageViewCzi : public IPixelariumImageView const Log& log_; std::unordered_map dimension_map_; std::unique_ptr render_; + + private: + void RefreshCachedImage(); }; -} // namespace pixelarium::render +} // namespace pixelarium::application diff --git a/lib/rendering/PixelariumImageViewDefault.cpp b/lib/app/rendering/PixelariumImageViewDefault.cpp similarity index 66% rename from lib/rendering/PixelariumImageViewDefault.cpp rename to lib/app/rendering/PixelariumImageViewDefault.cpp index 4af5d5f..903d5fc 100644 --- a/lib/rendering/PixelariumImageViewDefault.cpp +++ b/lib/app/rendering/PixelariumImageViewDefault.cpp @@ -3,31 +3,50 @@ #include #include "RenderHelpers.hpp" +#include "app_resources_default.h" #include "imaging/IPixelariumImage.hpp" #include "imgui.h" + +void pixelarium::application::PixelariumImageViewDefault::RefreshCachedImage() +{ + if (this->cached_image_.empty() || this->is_dirty_) + { + this->cached_image_ = this->img_->TryGetImage().value_or(cv::Mat{}); + this->is_dirty_ = false; + } +} + /// @brief Displays the image in an ImGui window. /// /// If the image is null, empty, or has an empty name, the function returns immediately. Otherwise, it creates an ImGui /// window with a horizontal scrollbar and menu bar. The image is rendered using the CvMatRender object, resizing it to /// fit the available window space. The raw and rendered dimensions are displayed below the image. -void pixelarium::render::PixelariumImageViewDefault::ShowImage() +void pixelarium::application::PixelariumImageViewDefault::ShowImage() { - if (!this->cached_image_ || this->is_dirty_) - { - this->cached_image_ = this->img_->TryGetImage(); - this->is_dirty_ = false; - } + RefreshCachedImage(); - if (this->img_->Empty() || this->img_->type_ == imaging::ImageFileType::kUnknown || !cached_image_ || + if (this->img_->Empty() || this->img_->type_ == imaging::ImageFileType::kUnknown || this->cached_image_.empty() || this->img_->Name().empty()) { // do nothing return; } + if (first_render_) + { + first_render_ = false; + + const auto cached_width{cached_image_.cols}; + const auto cached_heigth{cached_image_.rows}; + const auto ratio{static_cast(cached_heigth) / cached_width}; + SetInitialSize(kInitialWindowWidth, (kInitialWindowWidth * ratio + 100)); + } + ImGui::Begin(this->img_->Name().c_str(), &this->open_p, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar); + ImageViewMenuBar(); + this->curr_dim_ = ImGui::GetContentRegionAvail(); auto new_dim = ImGui::GetContentRegionAvail(); auto texture = @@ -37,7 +56,7 @@ void pixelarium::render::PixelariumImageViewDefault::ShowImage() this->curr_dim_ = new_dim; - ImVec2 dim(cached_image_->cols, cached_image_->rows); + ImVec2 dim(cached_image_.cols, cached_image_.rows); ImGui::Image(reinterpret_cast(reinterpret_cast(texture)), aspect_const_dimensions(dim, new_dim)); diff --git a/lib/rendering/PixelariumImageViewDefault.hpp b/lib/app/rendering/PixelariumImageViewDefault.hpp similarity index 78% rename from lib/rendering/PixelariumImageViewDefault.hpp rename to lib/app/rendering/PixelariumImageViewDefault.hpp index 2464413..0f784d8 100644 --- a/lib/rendering/PixelariumImageViewDefault.hpp +++ b/lib/app/rendering/PixelariumImageViewDefault.hpp @@ -2,25 +2,24 @@ #include +#include "CvMatRender.hpp" +#include "IPixelariumImageView.hpp" #include "imaging/IPixelariumImage.hpp" #include "imgui.h" -#include "rendering/CvMatRender.hpp" -#include "rendering/IPixelariumImageView.hpp" -namespace pixelarium::render +namespace pixelarium::application { /// @brief A default implementation of IPixelariumImageView. /// This is sufficient for single dimension images like png or jpg. class PixelariumImageViewDefault : public IPixelariumImageView { - using Image = imaging::IPixelariumImage; - using Render = render::CvMatRender; + using Image = imaging::IPixelariumImageCvMat; public: explicit PixelariumImageViewDefault(std::shared_ptr img) : render_(*img->TryGetImage()) { img_ = img; - this->SetInitialSize(); + // this->SetInitialSize(); } PixelariumImageViewDefault() = delete; PixelariumImageViewDefault(PixelariumImageViewDefault&) = delete; @@ -34,5 +33,8 @@ class PixelariumImageViewDefault : public IPixelariumImageView private: ImVec2 curr_dim_{}; CvMatRender render_; + + private: + void RefreshCachedImage(); }; -} // namespace pixelarium::render +} // namespace pixelarium::application diff --git a/lib/rendering/RenderHelpers.cpp b/lib/app/rendering/RenderHelpers.cpp similarity index 82% rename from lib/rendering/RenderHelpers.cpp rename to lib/app/rendering/RenderHelpers.cpp index a1ad36b..9364558 100644 --- a/lib/rendering/RenderHelpers.cpp +++ b/lib/app/rendering/RenderHelpers.cpp @@ -1,12 +1,13 @@ #include "RenderHelpers.hpp" #include + /// @brief Checks if the dimensions of two ImVec2 vectors have changed significantly. /// @param ref_rect The reference ImVec2 vector. /// @param new_rect The new ImVec2 vector to compare against. /// @return True if the absolute difference between the y-coordinates is greater than 5 or the x-coordinates are /// different; otherwise, false. -bool pixelarium::render::dim_changed_p(const ImVec2& ref_rect, const ImVec2& new_rect) +bool pixelarium::application::dim_changed_p(const ImVec2& ref_rect, const ImVec2& new_rect) { if (std::abs(ref_rect.y - new_rect.y) > 5 || std::abs(ref_rect.x - new_rect.x)) { @@ -20,7 +21,7 @@ bool pixelarium::render::dim_changed_p(const ImVec2& ref_rect, const ImVec2& new /// @param img The input image. /// @param curr_dim The current dimensions. /// @return The calculated dimensions maintaining aspect ratio. -ImVec2 pixelarium::render::aspect_const_dimensions(const ImVec2& raw_dim, const ImVec2& curr_dim) +ImVec2 pixelarium::application::aspect_const_dimensions(const ImVec2& raw_dim, const ImVec2& curr_dim) { const auto w_fact = (curr_dim.x / raw_dim.x); const auto h_fact = (curr_dim.y / raw_dim.y); diff --git a/lib/rendering/RenderHelpers.hpp b/lib/app/rendering/RenderHelpers.hpp similarity index 71% rename from lib/rendering/RenderHelpers.hpp rename to lib/app/rendering/RenderHelpers.hpp index 0633025..5f2ad4d 100644 --- a/lib/rendering/RenderHelpers.hpp +++ b/lib/app/rendering/RenderHelpers.hpp @@ -2,9 +2,9 @@ #include "imgui.h" -namespace pixelarium::render +namespace pixelarium::application { bool dim_changed_p(const ImVec2& ref_rect, const ImVec2& new_rect); ImVec2 aspect_const_dimensions(const ImVec2& raw_dim, const ImVec2& curr_dim); -}; // namespace pixelarium::render +}; // namespace pixelarium::application diff --git a/lib/rendering/RenderImageManager.cpp b/lib/app/rendering/RenderImageManager.cpp similarity index 82% rename from lib/rendering/RenderImageManager.cpp rename to lib/app/rendering/RenderImageManager.cpp index 5f867fc..03d76a7 100644 --- a/lib/rendering/RenderImageManager.cpp +++ b/lib/app/rendering/RenderImageManager.cpp @@ -7,7 +7,7 @@ using namespace std; /// @brief Updates the collection of rendered images by removing images marked for deletion. /// This function iterates through the \c keys_to_delete_ list and removes the corresponding images from the collection. /// It does not acquire the mutex to avoid deadlocks with the `Remove` function. -void pixelarium::render::RenderImageManager::UpdateCollection() +void pixelarium::application::RenderImageManager::UpdateCollection() { for (const auto& key : keys_to_delete_) { @@ -19,7 +19,7 @@ void pixelarium::render::RenderImageManager::UpdateCollection() /// @brief Marks a resource for deletion. /// @param key The ID of the resource to mark for deletion. -void pixelarium::render::RenderImageManager::MarkForDeletion(resources::ResourceKey key) +void pixelarium::application::RenderImageManager::MarkForDeletion(resources::ResourceKey key) { this->log_.Debug(std::format("{} marking key: \"{}\" for deletion.", __PRETTY_FUNCTION__, key)); lock_guard guard(this->mut_); @@ -29,7 +29,7 @@ void pixelarium::render::RenderImageManager::MarkForDeletion(resources::Resource /// @brief Removes a render image from the manager. /// @param key The key of the render image to remove. /// @return True if the render image was removed, false otherwise. -bool pixelarium::render::RenderImageManager::Remove(resources::ResourceKey key) noexcept +bool pixelarium::application::RenderImageManager::Remove(resources::ResourceKey key) noexcept { bool remove_state{false}; this->log_.Debug(std::format("{} removing key: \"{}\" from renderlist.", __PRETTY_FUNCTION__, key)); @@ -44,7 +44,7 @@ bool pixelarium::render::RenderImageManager::Remove(resources::ResourceKey key) /// @brief Adds a resource to the render image map. /// @param key The ID of the resource to add. /// @return void. No exception is thrown. -void pixelarium::render::RenderImageManager::Add(resources::ResourceKey key) noexcept +void pixelarium::application::RenderImageManager::Add(resources::ResourceKey key) noexcept { // we don't want to add what's already there // or empty render images @@ -77,4 +77,4 @@ void pixelarium::render::RenderImageManager::Add(resources::ResourceKey key) noe /// @brief Clears all render images from the manager. /// @note This function is noexcept. -void pixelarium::render::RenderImageManager::Clear() noexcept { this->render_image_map_.clear(); } +void pixelarium::application::RenderImageManager::Clear() noexcept { this->render_image_map_.clear(); } diff --git a/lib/rendering/RenderImageManager.hpp b/lib/app/rendering/RenderImageManager.hpp similarity index 94% rename from lib/rendering/RenderImageManager.hpp rename to lib/app/rendering/RenderImageManager.hpp index 296f5af..bd1f4e9 100644 --- a/lib/rendering/RenderImageManager.hpp +++ b/lib/app/rendering/RenderImageManager.hpp @@ -4,14 +4,14 @@ #include #include +#include "IPixelariumImageView.hpp" #include "ImageViewFactory.hpp" -#include "rendering/IPixelariumImageView.hpp" #include "resources/resource.hpp" #include "utilities/ILog.hpp" // This is intended as an additional abstraction // aggregating views that should be rendered (or not) -namespace pixelarium::render +namespace pixelarium::application { /// @brief Instead of directly using the view, we /// proxy it through a wrapper. This allows for arbitrary additional data @@ -71,4 +71,4 @@ class RenderImageManager const utils::log::ILog& log_; }; -} // namespace pixelarium::render +} // namespace pixelarium::application diff --git a/lib/imaging/CMakeLists.txt b/lib/imaging/CMakeLists.txt index 3f25051..04d23a4 100644 --- a/lib/imaging/CMakeLists.txt +++ b/lib/imaging/CMakeLists.txt @@ -7,6 +7,7 @@ message(STATUS "OpenCV_LIBs from: " ${OpenCV_LIBS}) set(IMAGELIBSRC IPixelariumImage.hpp + IPixelariumImage.cpp PixelariumImageFactory.hpp PixelariumImageFactory.cpp impl/PixelariumJpg.hpp diff --git a/lib/imaging/IPixelariumImage.cpp b/lib/imaging/IPixelariumImage.cpp new file mode 100644 index 0000000..ada7e36 --- /dev/null +++ b/lib/imaging/IPixelariumImage.cpp @@ -0,0 +1,15 @@ +#include "IPixelariumImage.hpp" + +#include + +bool pixelarium::imaging::IPixelariumImageCvMat::SaveImage(const std::string& uri) +{ + auto current_image = this->TryGetImage(); + + if (!current_image.has_value()) + { + return false; + } + + return cv::imwrite(uri, current_image.value()); +} diff --git a/lib/imaging/IPixelariumImage.hpp b/lib/imaging/IPixelariumImage.hpp index 045582c..c2657b9 100644 --- a/lib/imaging/IPixelariumImage.hpp +++ b/lib/imaging/IPixelariumImage.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -38,6 +37,7 @@ struct IImageQuery /// @brief This aims to be a generic image abstraction /// meant for codec specific implementation. +template class IPixelariumImage { public: @@ -47,19 +47,19 @@ class IPixelariumImage /// @return A unique pointer to a Mat object containing the image data, /// or nullptr if the image is not found or cannot be retrieved. /// May throw exceptions for multidimensional images. - virtual std::unique_ptr TryGetImage() = 0; + virtual std::optional TryGetImage() = 0; /// @brief Attempts to retrieve the image. /// @param query The query object defining the images to retrieve. /// @return A unique pointer to a Mat object containing the image data, /// or nullptr if the image is not found or cannot be retrieved. - virtual std::unique_ptr TryGetImage(const IImageQuery& query) = 0; + virtual std::optional TryGetImage(const IImageQuery& query) = 0; /// @brief Attempts to retrieve a collection of images based on a query. /// @param query The query object defining the images to retrieve. /// @return A vector of unique pointers to cv::Mat objects. Each element is an image. /// Returns an empty vector if no images are found or if an error occurs. - virtual std::vector> TryGetImages(const IImageQuery& query) = 0; + virtual std::vector> TryGetImages(const IImageQuery& query) = 0; /// @brief Checks if the image is empty. /// @return true if the image is empty, false otherwise. @@ -92,4 +92,60 @@ class IPixelariumImage protected: std::filesystem::path uri_; }; + +class IPixelariumImageCvMat : public IPixelariumImage +{ + public: + virtual ~IPixelariumImageCvMat() = default; + + /// @brief Attempts to retrieve the image. + /// @return A unique pointer to a Mat object containing the image data, + /// or nullptr if the image is not found or cannot be retrieved. + /// May throw exceptions for multidimensional images. + virtual std::optional TryGetImage() = 0; + + /// @brief Attempts to retrieve the image. + /// @param query The query object defining the images to retrieve. + /// @return A unique pointer to a Mat object containing the image data, + /// or nullptr if the image is not found or cannot be retrieved. + virtual std::optional TryGetImage(const IImageQuery& query) = 0; + + /// @brief Attempts to retrieve a collection of images based on a query. + /// @param query The query object defining the images to retrieve. + /// @return A vector of unique pointers to cv::Mat objects. Each element is an image. + /// Returns an empty vector if no images are found or if an error occurs. + virtual std::vector> TryGetImages(const IImageQuery& query) = 0; + + /// @brief Checks if the image is empty. + /// @return true if the image is empty, false otherwise. + virtual bool Empty() const noexcept = 0; + + /// @brief Saves an image to the resource identifier given by uri. + /// @return true if saving was successful, false otherwise. + virtual bool SaveImage(const std::string& uri); + + // default implemented + public: + /// @brief Gets the resource identifier as a file path. + /// @return @c std::filesystem::path of the underlying resource. + virtual std::filesystem::path Uri() const noexcept { return this->uri_; } + + /// @brief Gets the resource name. + /// @note Implementations of IPixelariumImage that live in memory + /// should override this to get something meaningful as the name + /// cannot be fetched from the resource uri in that case. + /// @return The name of the underlying resource. + virtual std::string Name() const noexcept + { + if (!this->uri_.empty()) + { + return this->uri_.filename().string(); + } + + return {}; + } + + public: + const static ImageFileType type_{ImageFileType::kAbstract}; +}; } // namespace pixelarium::imaging diff --git a/lib/imaging/PixelariumImageFactory.cpp b/lib/imaging/PixelariumImageFactory.cpp index b2c162a..a001ddb 100644 --- a/lib/imaging/PixelariumImageFactory.cpp +++ b/lib/imaging/PixelariumImageFactory.cpp @@ -3,13 +3,14 @@ #include #include +#include "imaging/IPixelariumImage.hpp" #include "imaging/impl/PixelariumMem.hpp" #include "impl/PixelariumCzi.hpp" #include "impl/PixelariumJpg.hpp" #include "impl/PixelariumPng.hpp" #include "impl/PixelariumTiff.hpp" -/*static*/ std::unique_ptr +/*static*/ std::unique_ptr pixelarium::imaging::PixelariumImageFactory::CreateImage(const std::string& uri, const Log& log) { const auto res{std::filesystem::path(uri)}; diff --git a/lib/imaging/PixelariumImageFactory.hpp b/lib/imaging/PixelariumImageFactory.hpp index 4114329..b098afe 100644 --- a/lib/imaging/PixelariumImageFactory.hpp +++ b/lib/imaging/PixelariumImageFactory.hpp @@ -37,6 +37,6 @@ class PixelariumImageFactory using Log = utils::log::ILog; public: - static std::unique_ptr CreateImage(const std::string& uri, const Log& log); + static std::unique_ptr CreateImage(const std::string& uri, const Log& log); }; } // namespace pixelarium::imaging diff --git a/lib/imaging/impl/PixelariumCzi.cpp b/lib/imaging/impl/PixelariumCzi.cpp index 659ed8f..dcf87e1 100644 --- a/lib/imaging/impl/PixelariumCzi.cpp +++ b/lib/imaging/impl/PixelariumCzi.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -48,8 +50,8 @@ constexpr int try_get_index_match(const pixelarium::imaging::CziParams& params, return index; } -std::unique_ptr CZISubBlockToCvMat(std::shared_ptr bitmap, libCZI::PixelType pixeltype, - const pixelarium::utils::log::ILog& log) +std::optional CZISubBlockToCvMat(std::shared_ptr bitmap, libCZI::PixelType pixeltype, + const pixelarium::utils::log::ILog& log) { size_t pixel_size{0}; int target_type; @@ -94,21 +96,21 @@ std::unique_ptr CZISubBlockToCvMat(std::shared_ptr break; } - if (pixel_size < 0) return nullptr; + if (pixel_size < 0) return std::nullopt; log.Info(std::format("{}: source pixel type {}, target cv pixel type {}, pixel size {}", __PRETTY_FUNCTION__, pixel_pair.first, pixel_pair.second, pixel_size)); size_t height{bitmap->GetHeight()}; size_t width{bitmap->GetWidth()}; - auto fill_mat = std::make_unique(height, width, target_type); + auto fill_mat = cv::Mat(height, width, target_type); auto bitmap_info = bitmap->Lock(); for (size_t h{0}; h < height; ++h) { unsigned char* source_row = ((unsigned char*)bitmap_info.ptrDataRoi) + bitmap_info.stride * h; - unsigned char* target_row = fill_mat->ptr(h); + unsigned char* target_row = fill_mat.ptr(h); for (size_t w{0}; w < width; ++w) { @@ -139,7 +141,7 @@ std::unique_ptr CZISubBlockToCvMat(std::shared_ptr return fill_mat; } -std::unique_ptr pixelarium::imaging::PixelariumCzi::SubblockToCvMat(int index) +std::optional pixelarium::imaging::PixelariumCzi::SubblockToCvMat(int index) { log_.Info(std::format("{}: constructing bitmap with index {}", __PRETTY_FUNCTION__, index)); auto block = this->czi_reader_->ReadSubBlock(index); @@ -170,9 +172,9 @@ pixelarium::imaging::PixelariumCzi::PixelariumCzi(const std::string& uri, const }); } -std::unique_ptr pixelarium::imaging::PixelariumCzi::TryGetImage() { return SubblockToCvMat(0); } +std::optional pixelarium::imaging::PixelariumCzi::TryGetImage() { return SubblockToCvMat(0); } -std::unique_ptr pixelarium::imaging::PixelariumCzi::TryGetImage(const IImageQuery& query) +std::optional pixelarium::imaging::PixelariumCzi::TryGetImage(const IImageQuery& query) { const auto czi_query = static_cast(query); int index = try_get_index_match(czi_query, *this->czi_reader_); diff --git a/lib/imaging/impl/PixelariumCzi.hpp b/lib/imaging/impl/PixelariumCzi.hpp index 75dd899..36ad5fb 100644 --- a/lib/imaging/impl/PixelariumCzi.hpp +++ b/lib/imaging/impl/PixelariumCzi.hpp @@ -18,7 +18,7 @@ struct CziParams : public IImageQuery }; /// @brief Implements support for .czi-images in the realm of IPixelariumImage -class PixelariumCzi : public IPixelariumImage +class PixelariumCzi : public IPixelariumImageCvMat { using Log = pixelarium::utils::log::ILog; @@ -31,16 +31,17 @@ class PixelariumCzi : public IPixelariumImage // IPixelariumImage member implementations public: - std::unique_ptr TryGetImage() override; + std::optional TryGetImage() override; - std::unique_ptr TryGetImage(const IImageQuery&) override; + std::optional TryGetImage(const IImageQuery&) override; - std::vector> TryGetImages(const IImageQuery&) override + std::vector> TryGetImages(const IImageQuery&) override { // ToDo: proper error throw std::runtime_error("Not implemented."); } + public: bool Empty() const noexcept override { return this->is_empty_; } const libCZI::SubBlockStatistics& GetStatistics() const { return this->image_statistics_; } @@ -49,7 +50,7 @@ class PixelariumCzi : public IPixelariumImage const static ImageFileType type_{ImageFileType::kCzi}; private: - std::unique_ptr SubblockToCvMat(int index); + std::optional SubblockToCvMat(int index); private: // this should be set by each image getter diff --git a/lib/imaging/impl/PixelariumJpg.cpp b/lib/imaging/impl/PixelariumJpg.cpp index be5c920..787fa9f 100644 --- a/lib/imaging/impl/PixelariumJpg.cpp +++ b/lib/imaging/impl/PixelariumJpg.cpp @@ -1,7 +1,5 @@ #include "PixelariumJpg.hpp" -#include -#include #include #include @@ -16,11 +14,11 @@ pixelarium::imaging::PixelariumJpg::PixelariumJpg(const std::string& uri) this->uri_ = std::filesystem::path(uri); } -std::unique_ptr pixelarium::imaging::PixelariumJpg::TryGetImage() +std::optional pixelarium::imaging::PixelariumJpg::TryGetImage() { try { - auto img = std::make_unique(cv::imread(this->uri_.string())); + auto img = cv::Mat(cv::imread(this->uri_.string())); this->is_empty_ = false; diff --git a/lib/imaging/impl/PixelariumJpg.hpp b/lib/imaging/impl/PixelariumJpg.hpp index 100b4cf..37ef378 100644 --- a/lib/imaging/impl/PixelariumJpg.hpp +++ b/lib/imaging/impl/PixelariumJpg.hpp @@ -8,27 +8,28 @@ namespace pixelarium::imaging { /// @brief Implements support for .jpg-images in the realm of IPixelariumImage -class PixelariumJpg : public IPixelariumImage +class PixelariumJpg : public IPixelariumImageCvMat { public: explicit PixelariumJpg(const std::string& url); // IPixelariumImage member implementations public: - std::unique_ptr TryGetImage() override; + std::optional TryGetImage() override; - std::unique_ptr TryGetImage(const IImageQuery&) override + std::optional TryGetImage(const IImageQuery&) override { // ToDo: proper error throw std::runtime_error("Not possible with jpg."); } - std::vector> TryGetImages(const IImageQuery&) override + std::vector> TryGetImages(const IImageQuery&) override { // ToDo: proper error throw std::runtime_error("Not possible with jpg."); } + public: bool Empty() const noexcept override { return this->is_empty_; } public: diff --git a/lib/imaging/impl/PixelariumMem.cpp b/lib/imaging/impl/PixelariumMem.cpp index e9ac226..0377fb8 100644 --- a/lib/imaging/impl/PixelariumMem.cpp +++ b/lib/imaging/impl/PixelariumMem.cpp @@ -12,7 +12,7 @@ pixelarium::imaging::PixelariumMem::PixelariumMem(const cv::Mat& img, const std: this->uri_ = std::filesystem::path(); } -std::unique_ptr pixelarium::imaging::PixelariumMem::TryGetImage() +std::optional pixelarium::imaging::PixelariumMem::TryGetImage() { // ToDo: this craving for a revision of the whole concept: // the interface requires a unique_ptr here. This concept was designed to "create an in-memory image on demand" sort @@ -22,5 +22,5 @@ std::unique_ptr pixelarium::imaging::PixelariumMem::TryGetImage() // So, returning a unique_ptr from it in the following semantic essentially calls the // copy constructor of cv::Mat. This is potentially not "super bad", but at least it requires attention at some // point. - return std::make_unique(this->img_); + return this->img_; } diff --git a/lib/imaging/impl/PixelariumMem.hpp b/lib/imaging/impl/PixelariumMem.hpp index 9a7619f..41137d9 100644 --- a/lib/imaging/impl/PixelariumMem.hpp +++ b/lib/imaging/impl/PixelariumMem.hpp @@ -9,7 +9,7 @@ namespace pixelarium::imaging { /// @brief Implements support for in-memory images in the realm of IPixelariumImage -class PixelariumMem : public IPixelariumImage +class PixelariumMem : public IPixelariumImageCvMat { using Log = pixelarium::utils::log::ILog; @@ -18,11 +18,11 @@ class PixelariumMem : public IPixelariumImage // IPixelariumImage member implementations public: - std::unique_ptr TryGetImage() override; + std::optional TryGetImage() override; - std::unique_ptr TryGetImage(const IImageQuery&) override { throw std::runtime_error("Not implemented."); } + std::optional TryGetImage(const IImageQuery&) override { throw std::runtime_error("Not implemented."); } - std::vector> TryGetImages(const IImageQuery&) override + std::vector> TryGetImages(const IImageQuery&) override { throw std::runtime_error("Not implemented."); } diff --git a/lib/imaging/impl/PixelariumPng.cpp b/lib/imaging/impl/PixelariumPng.cpp index 30fcdf5..cfdb7b1 100644 --- a/lib/imaging/impl/PixelariumPng.cpp +++ b/lib/imaging/impl/PixelariumPng.cpp @@ -16,11 +16,11 @@ pixelarium::imaging::PixelariumPng::PixelariumPng(const std::string& uri) this->uri_ = std::filesystem::path(uri); } -std::unique_ptr pixelarium::imaging::PixelariumPng::TryGetImage() +std::optional pixelarium::imaging::PixelariumPng::TryGetImage() { try { - auto img = std::make_unique(cv::imread(this->uri_.string())); + auto img = cv::Mat(cv::imread(this->uri_.string())); this->is_empty_ = false; diff --git a/lib/imaging/impl/PixelariumPng.hpp b/lib/imaging/impl/PixelariumPng.hpp index 29be769..a3cc897 100644 --- a/lib/imaging/impl/PixelariumPng.hpp +++ b/lib/imaging/impl/PixelariumPng.hpp @@ -8,22 +8,22 @@ namespace pixelarium::imaging { /// @brief Implements support for .png-images in the realm of IPixelariumImage -class PixelariumPng : public IPixelariumImage +class PixelariumPng : public IPixelariumImageCvMat { public: explicit PixelariumPng(const std::string& url); // IPixelariumImage member implementations public: - std::unique_ptr TryGetImage() override; + std::optional TryGetImage() override; - std::unique_ptr TryGetImage(const IImageQuery&) override + std::optional TryGetImage(const IImageQuery&) override { // ToDo: proper error throw std::runtime_error("Not possible with png."); } - std::vector> TryGetImages(const IImageQuery&) override + std::vector> TryGetImages(const IImageQuery&) override { // ToDo: proper error throw std::runtime_error("Not possible with png."); diff --git a/lib/imaging/impl/PixelariumTiff.cpp b/lib/imaging/impl/PixelariumTiff.cpp index 67a5a28..418d74d 100644 --- a/lib/imaging/impl/PixelariumTiff.cpp +++ b/lib/imaging/impl/PixelariumTiff.cpp @@ -16,11 +16,11 @@ pixelarium::imaging::PixelariumTiff::PixelariumTiff(const std::string& uri, cons this->uri_ = std::filesystem::path(uri); } -std::unique_ptr pixelarium::imaging::PixelariumTiff::TryGetImage() +std::optional pixelarium::imaging::PixelariumTiff::TryGetImage() { try { - auto img = std::make_unique(cv::imread(this->uri_.string())); + auto img = cv::Mat(cv::imread(this->uri_.string())); this->is_empty_ = false; diff --git a/lib/imaging/impl/PixelariumTiff.hpp b/lib/imaging/impl/PixelariumTiff.hpp index 95880a1..6d653e0 100644 --- a/lib/imaging/impl/PixelariumTiff.hpp +++ b/lib/imaging/impl/PixelariumTiff.hpp @@ -9,7 +9,7 @@ namespace pixelarium::imaging { /// @brief Implements support for .tiff-images in the realm of IPixelariumImage -class PixelariumTiff : public IPixelariumImage +class PixelariumTiff : public IPixelariumImageCvMat { using Log = pixelarium::utils::log::ILog; @@ -18,15 +18,15 @@ class PixelariumTiff : public IPixelariumImage // IPixelariumImage member implementations public: - std::unique_ptr TryGetImage() override; + std::optional TryGetImage() override; - std::unique_ptr TryGetImage(const IImageQuery&) override + std::optional TryGetImage(const IImageQuery&) override { // ToDo: proper error throw std::runtime_error("Not possible with tiff."); } - std::vector> TryGetImages(const IImageQuery&) override + std::vector> TryGetImages(const IImageQuery&) override { // ToDo: proper error throw std::runtime_error("Not possible with tiff."); diff --git a/lib/rendering/CMakeLists.txt b/lib/rendering/CMakeLists.txt deleted file mode 100644 index eb9a6b6..0000000 --- a/lib/rendering/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -set(RENDERLIBNAME pixelariumrenderlib) - -set(RENDERLIBSRC - RenderHelpers.hpp - RenderHelpers.cpp - CvMatRender.hpp - CvMatRender.cpp - RenderImageManager.hpp - RenderImageManager.cpp - IPixelariumImageView.hpp - PixelariumImageViewDefault.hpp - PixelariumImageViewDefault.cpp - PixelariumImageViewCzi.hpp - PixelariumImageViewCzi.cpp - ImageViewFactory.hpp - ImageViewFactory.cpp) - -add_library(${RENDERLIBNAME} STATIC - ${RENDERLIBSRC}) - -target_link_libraries(${RENDERLIBNAME} - PRIVATE pixelariumimagelib) - -target_include_directories(${RENDERLIBNAME} - PRIVATE ${CMAKE_SOURCE_DIR}/lib - PRIVATE ${imgui_DIR}) diff --git a/lib/resources/resource.cpp b/lib/resources/resource.cpp index 37fa6b7..d9c6bc6 100644 --- a/lib/resources/resource.cpp +++ b/lib/resources/resource.cpp @@ -5,7 +5,9 @@ #include #include -using pixelarium::imaging::IPixelariumImage; +#include "imaging/IPixelariumImage.hpp" + +using Image = pixelarium::imaging::IPixelariumImageCvMat; using namespace std; namespace @@ -21,7 +23,7 @@ size_t GenerateId() { return id_.fetch_add(1, memory_order_relaxed); } /// @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::weak_ptr pixelarium::resources::ImageResourcePool::GetResource(ResourceKey id) const +std::weak_ptr pixelarium::resources::ImageResourcePool::GetResource(ResourceKey id) const { auto search{this->resources_.find(id)}; if (search == this->resources_.end()) return {}; @@ -32,7 +34,7 @@ std::weak_ptr pixelarium::resources::ImageResourcePool::GetRes /// @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) +size_t pixelarium::resources::ImageResourcePool::SetResource(unique_ptr res) { if (res == nullptr) { @@ -52,8 +54,7 @@ size_t pixelarium::resources::ImageResourcePool::SetResource(unique_ptr res) +bool pixelarium::resources::ImageResourcePool::ModifyResource(ResourceKey id, std::unique_ptr res) { auto search{this->resources_.find(id)}; if (search == this->resources_.end()) return false; @@ -80,7 +81,7 @@ bool pixelarium::resources::ImageResourcePool::DeleteResource(ResourceKey id) /// @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) + const std::function&)>& func) { size_t idx{0}; for (const auto& e : this->resources_) diff --git a/lib/resources/resource.hpp b/lib/resources/resource.hpp index a4c0d22..f72f4b2 100644 --- a/lib/resources/resource.hpp +++ b/lib/resources/resource.hpp @@ -38,7 +38,7 @@ concept ResT = requires(R& r) { static_cast(r); }; /// @brief Defines an interface for a resource pool /// @tparam ResT defines the resource type that is accepted by the pool -template +template class IResourcePool { public: @@ -48,7 +48,7 @@ class IResourcePool virtual bool ModifyResource(ResourceKey id, std::unique_ptr res) = 0; virtual bool DeleteResource(ResourceKey id) = 0; virtual void EnumerateResources( - const std::function& func) = 0; + const std::function&)>& func) = 0; virtual size_t GetTotalSize() const = 0; virtual void Clear() = 0; }; @@ -58,7 +58,7 @@ class IResourcePool // 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 +class ImageResourcePool : public IResourcePool { public: ImageResourcePool() = default; @@ -68,17 +68,17 @@ class ImageResourcePool : public IResourcePool ImageResourcePool& operator=(ImageResourcePool&) = delete; ImageResourcePool& operator=(ImageResourcePool&&) = delete; - std::weak_ptr GetResource(ResourceKey id) const override; - ResourceKey SetResource(std::unique_ptr res) override; - bool ModifyResource(ResourceKey id, std::unique_ptr res) override; + std::weak_ptr GetResource(ResourceKey id) const override; + ResourceKey SetResource(std::unique_ptr res) override; + bool ModifyResource(ResourceKey id, std::unique_ptr res) override; bool DeleteResource(ResourceKey id) override; void Clear() override { this->resources_.clear(); } void EnumerateResources( - const std::function& func) override; + const std::function&)>& func) override; template - requires std::invocable + requires std::invocable void Enumerate(Callable&& func) const { size_t idx{0}; @@ -91,7 +91,7 @@ class ImageResourcePool : public IResourcePool size_t GetTotalSize() const override { return resources_.size(); } private: - std::unordered_map> resources_; + std::unordered_map> resources_; std::mutex mut_; }; } // namespace pixelarium::resources diff --git a/lib/utilities/CMakeLists.txt b/lib/utilities/CMakeLists.txt index 9228bcd..12fc276 100644 --- a/lib/utilities/CMakeLists.txt +++ b/lib/utilities/CMakeLists.txt @@ -3,7 +3,9 @@ set(UTILSLIBNAME pixelariumutilslib) set(UTILSLIBSRC ILog.hpp SpdLogger.hpp - SpdLogger.cpp) + SpdLogger.cpp + simple_thread_pool.hpp + simple_thread_pool.cpp) add_library(${UTILSLIBNAME} STATIC ${UTILSLIBSRC}) diff --git a/lib/utilities/simple_thread_pool.cpp b/lib/utilities/simple_thread_pool.cpp new file mode 100644 index 0000000..05504a7 --- /dev/null +++ b/lib/utilities/simple_thread_pool.cpp @@ -0,0 +1,47 @@ +#include "simple_thread_pool.hpp" + +#include +#include + +using namespace pixelarium::utils; + +simple_thread_pool::simple_thread_pool(size_t num_threads) +{ + for (size_t i{0}; i < num_threads; ++i) + { + workers_.emplace_back( + [this]() + { + while (true) + { + std::function job; + { + std::unique_lock lck(thread_mutex_); + + cv_.wait(lck, [this]() -> bool { return shutdown_ || !task_queue_.empty(); }); + + if (shutdown_ && task_queue_.empty()) return; + + job = std::move(task_queue_.front()); + task_queue_.pop(); + } + + job(); + } + }); + } +} + +simple_thread_pool::~simple_thread_pool() +{ + { + std::unique_lock lck(thread_mutex_); + shutdown_ = true; + } + + cv_.notify_all(); + for (auto& th : workers_) + { + th.join(); + } +} diff --git a/lib/utilities/simple_thread_pool.hpp b/lib/utilities/simple_thread_pool.hpp new file mode 100644 index 0000000..8b15a03 --- /dev/null +++ b/lib/utilities/simple_thread_pool.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace pixelarium::utils +{ +class simple_thread_pool +{ + public: + explicit simple_thread_pool(size_t); + simple_thread_pool(simple_thread_pool&) = delete; + simple_thread_pool(const simple_thread_pool&) = delete; + simple_thread_pool(simple_thread_pool&&) = delete; + simple_thread_pool& operator=(simple_thread_pool&) = delete; + simple_thread_pool& operator=(simple_thread_pool&&) = delete; + ~simple_thread_pool(); + + template + requires std::invocable + static auto run_asynch(Callable&& fun) -> void + { + simple_thread_pool::Global().enqueue(std::forward(fun)); + } + + public: + template + requires std::invocable + auto enqueue(Callable&& fun) -> void + { + { + std::unique_lock lck(thread_mutex_); + task_queue_.emplace(std::forward(fun)); + } + + cv_.notify_one(); + } + + private: + static auto Global() -> simple_thread_pool& + { + const auto kThreadCount{std::thread::hardware_concurrency() * 2}; + static simple_thread_pool global_instance(kThreadCount == 0 ? 5 : kThreadCount); + return global_instance; + } + std::vector workers_; + std::condition_variable cv_; + std::mutex thread_mutex_; + std::queue> task_queue_; + bool shutdown_{false}; +}; +} // namespace pixelarium::utils diff --git a/tests/lib/resources/test_resource.cpp b/tests/lib/resources/test_resource.cpp index 2f2972b..7295672 100644 --- a/tests/lib/resources/test_resource.cpp +++ b/tests/lib/resources/test_resource.cpp @@ -2,20 +2,21 @@ #include +#include "imaging/IPixelariumImage.hpp" #include "resources/resource.hpp" namespace { // A Mock implementation for tests requiring _any_ instance of a IPixelariumImage -class DummyImage : public pixelarium::imaging::IPixelariumImage +class DummyImage : public pixelarium::imaging::IPixelariumImageCvMat { public: - std::unique_ptr TryGetImage() override { return {}; } + std::optional TryGetImage() override { return {}; } - std::unique_ptr TryGetImage(const pixelarium::imaging::IImageQuery&) override { return {}; } + std::optional TryGetImage(const pixelarium::imaging::IImageQuery&) override { return {}; } - std::vector> TryGetImages(const pixelarium::imaging::IImageQuery&) override { return {}; } + std::vector> TryGetImages(const pixelarium::imaging::IImageQuery&) override { return {}; } std::string Name() const noexcept override { return {}; } @@ -43,7 +44,7 @@ TEST(ImageResourcePoolTest, SetWrappedRawPointerGet) { ImageResourcePool pool; auto img = new DummyImage(); - auto id = pool.SetResource(std::unique_ptr(img)); + auto id = pool.SetResource(std::unique_ptr(img)); auto res = pool.GetResource(id); auto res_img = res.lock(); EXPECT_NE(res_img, nullptr); @@ -95,7 +96,7 @@ TEST(ImageResourcePoolTest, EnumerateResources) auto id2 = pool.SetResource(std::make_unique()); std::vector found_ids{}; - pool.EnumerateResources([&found_ids](size_t id, size_t, const pixelarium::imaging::IPixelariumImage&) + pool.EnumerateResources([&found_ids](size_t id, size_t, const pixelarium::imaging::IPixelariumImage&) { found_ids.push_back(id); }); EXPECT_EQ(found_ids.size(), 2); @@ -110,7 +111,7 @@ TEST(ImageResourcePoolTest, TemplatedEnumerate) auto id2 = pool.SetResource(std::make_unique()); std::vector found_ids{}; - pool.Enumerate([&found_ids](size_t id, size_t, const pixelarium::imaging::IPixelariumImage&) + pool.Enumerate([&found_ids](size_t id, size_t, const pixelarium::imaging::IPixelariumImage&) { found_ids.push_back(id); }); EXPECT_EQ(found_ids.size(), 2);