From 1ea83d9d11304f9c5397f64baa852ab2b2fa20c8 Mon Sep 17 00:00:00 2001 From: m-aXimilian <56168660+m-aXimilian@users.noreply.github.com> Date: Fri, 26 Sep 2025 21:09:51 +0200 Subject: [PATCH] Czi rendering (#10) * add dimension selector sliders to czi view * version doc & version bump * cosmetic * fix build? * multi-dimension selection enabled in czi view * remove the parameterized reset function of the renderer it is not and is conceptually questionable doc updates & some minor improvements auto-update CZI-view when sliders are moved set initial window size fix windows build and msvc build For reasons I don not comprehend, on Windows, we must include =opencv2/core/mat.hpp= as the very last header in the =CvMatRender.hpp=. If this is at any other position building on Windows, compilation will break w/ all kinds of cryptic errors regarding OpenCV. When building w/ msvc =__PRETTY_FUNCTION__= is not defined. =ILog.hpp= now defines it. This should be revised to only be set when the compiler is msvc. For the time being, it is considered sufficient though. --- CMakeLists.txt | 2 +- doc/Doxyfile.in | 7 +- doc/versions.md | 10 ++ lib/app/AppGLFW.hpp | 14 +++ lib/app/DefaultApp.cpp | 2 +- lib/app/DefaultApp.hpp | 3 + lib/imaging/CMakeLists.txt | 2 + lib/imaging/IPixelariumImage.hpp | 5 +- lib/imaging/PixelariumImageFactory.cpp | 5 +- lib/imaging/PixelariumImageFactory.hpp | 6 +- lib/imaging/impl/PixelariumCzi.cpp | 124 +++++++++++++++---- lib/imaging/impl/PixelariumCzi.hpp | 23 ++-- lib/imaging/impl/PixelariumJpg.hpp | 1 + lib/imaging/impl/PixelariumPng.hpp | 1 + lib/rendering/CvMatRender.cpp | 42 +------ lib/rendering/CvMatRender.hpp | 15 ++- lib/rendering/IPixelariumImageView.hpp | 8 +- lib/rendering/ImageViewFactory.cpp | 14 ++- lib/rendering/ImageViewFactory.hpp | 3 +- lib/rendering/PixelariumImageViewCzi.cpp | 45 ++++++- lib/rendering/PixelariumImageViewCzi.hpp | 17 ++- lib/rendering/PixelariumImageViewDefault.hpp | 7 +- lib/rendering/RenderImageManager.cpp | 7 +- lib/rendering/RenderImageManager.hpp | 8 ++ lib/resources/resource.hpp | 9 +- lib/utilities/ILog.hpp | 6 + lib/utilities/SpdLogger.hpp | 2 + 27 files changed, 282 insertions(+), 106 deletions(-) create mode 100644 doc/versions.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 20ca15d..47989df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(pixelarium VERSION 0.0.1) +project(pixelarium VERSION 0.0.4) set(CMAKE_VERBOSE_MAKEFILE ON) set(CXX_STANDARD 20) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index d38157c..cdbae05 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -14,4 +14,9 @@ DOXYFILE_ENCODING = UTF-8 GENERATE_LATEX = NO FULL_PATH_NAMES = NO USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/doc/index.md -HTML_EXTRA_STYLESHEET = @AWESOME_CSS_DIR@/doxygen-awesome.css \ No newline at end of file + + +HTML_EXTRA_STYLESHEET = @AWESOME_CSS_DIR@/doxygen-awesome.css +GENERATE_TREEVIEW = YES +DISABLE_INDEX = NO +FULL_SIDEBAR = NO \ No newline at end of file diff --git a/doc/versions.md b/doc/versions.md new file mode 100644 index 0000000..573a16d --- /dev/null +++ b/doc/versions.md @@ -0,0 +1,10 @@ +# Version History + +| Version | Description | +|:-------:|:------------------------------------------------------------------------| +| 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/lib/app/AppGLFW.hpp b/lib/app/AppGLFW.hpp index 698c4fe..a3aee6c 100644 --- a/lib/app/AppGLFW.hpp +++ b/lib/app/AppGLFW.hpp @@ -9,19 +9,33 @@ namespace pixelarium::application { +/// @brief Base class providing scaffolding with GLFW and some default +/// implementations that can be extended and/or overridden by consumers class AppGLFW { public: explicit AppGLFW(const utils::log::ILog& log) : logger_(log) { this->InitMainWindow(); } + /// @brief Start the main render loop void Start() { this->RunLoop(); } protected: + /// @brief Function implementing the first column of the menu bar (e.g. "Menu") virtual void MenuBarOptionsColumn1() {} + + /// @brief Function implementing the second column of the menu bar (e.g. "File") virtual void MenuBarOptionsColumn2() {} + + /// @brief Function implementing the third column of the menu bar (e.g. "Options") virtual void MenuBarOptionsColumn3() {} + + /// @brief Function implementing the fourth column of the menu bar (e.g. "More") virtual void MenuBarOptionsColumn4() {} + + /// @brief Function implementing the first column of the menu bar (e.g. "Help") virtual void MenuBarOptionsColumn5() {} + + /// @brief Main function that gets called within the render loop. virtual void Run() {} const utils::log::ILog& logger_; diff --git a/lib/app/DefaultApp.cpp b/lib/app/DefaultApp.cpp index 48a970e..e24042d 100644 --- a/lib/app/DefaultApp.cpp +++ b/lib/app/DefaultApp.cpp @@ -128,7 +128,7 @@ void pixelarium::ui::DefaultApp::LoadImage() try { - pool_.SetResource(PixelariumImageFactory::CreateImage(p)); + pool_.SetResource(PixelariumImageFactory::CreateImage(p, logger_)); } catch (pixelarium::resources::empty_resource_exception& e) { diff --git a/lib/app/DefaultApp.hpp b/lib/app/DefaultApp.hpp index 8712254..816037f 100644 --- a/lib/app/DefaultApp.hpp +++ b/lib/app/DefaultApp.hpp @@ -11,6 +11,9 @@ namespace pixelarium::ui { +/// @brief Default implementation of AppGLFW. +/// This can either be used as is, as an example or as a base class +/// providing some defaults for a more custom implementation. class DefaultApp : public application::AppGLFW { public: diff --git a/lib/imaging/CMakeLists.txt b/lib/imaging/CMakeLists.txt index 0757d72..324bd50 100644 --- a/lib/imaging/CMakeLists.txt +++ b/lib/imaging/CMakeLists.txt @@ -24,9 +24,11 @@ add_library(${IMAGELIBLIBNAME} target_link_libraries(${IMAGELIBLIBNAME} PUBLIC ${OpenCV_LIBS} + PUBLIC pixelariumutilslib PRIVATE libCZIStatic) target_include_directories(${IMAGELIBLIBNAME} + PRIVATE ${CMAKE_SOURCE_DIR}/lib PUBLIC ${OpenCV_INCLUDE_DIRS} PUBLIC ${LIBCZI_INCLUDE_DIR}) diff --git a/lib/imaging/IPixelariumImage.hpp b/lib/imaging/IPixelariumImage.hpp index c6721cf..0390afd 100644 --- a/lib/imaging/IPixelariumImage.hpp +++ b/lib/imaging/IPixelariumImage.hpp @@ -46,15 +46,16 @@ class IPixelariumImage virtual std::unique_ptr 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&) = 0; + virtual std::unique_ptr 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&) = 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. diff --git a/lib/imaging/PixelariumImageFactory.cpp b/lib/imaging/PixelariumImageFactory.cpp index aecf01a..cb67e26 100644 --- a/lib/imaging/PixelariumImageFactory.cpp +++ b/lib/imaging/PixelariumImageFactory.cpp @@ -1,6 +1,5 @@ #include "PixelariumImageFactory.hpp" -#include #include #include @@ -10,7 +9,7 @@ /*static*/ std::unique_ptr -pixelarium::imaging::PixelariumImageFactory::CreateImage(const std::string& uri) +pixelarium::imaging::PixelariumImageFactory::CreateImage(const std::string& uri, const Log& log) { const auto res{std::filesystem::path(uri)}; const auto target_type{ExtensionToType(res.extension().string())}; @@ -30,7 +29,7 @@ pixelarium::imaging::PixelariumImageFactory::CreateImage(const std::string& uri) return std::make_unique(uri); break; case ImageFileType::CZI: - return std::make_unique(uri); + return std::make_unique(uri, log); break; } } diff --git a/lib/imaging/PixelariumImageFactory.hpp b/lib/imaging/PixelariumImageFactory.hpp index 391e7f1..d3da42b 100644 --- a/lib/imaging/PixelariumImageFactory.hpp +++ b/lib/imaging/PixelariumImageFactory.hpp @@ -3,6 +3,7 @@ #include #include "IPixelariumImage.hpp" +#include "utilities/ILog.hpp" namespace pixelarium::imaging { constexpr pixelarium::imaging::ImageFileType ExtensionToType(const std::string& extension) @@ -26,9 +27,12 @@ constexpr pixelarium::imaging::ImageFileType ExtensionToType(const std::string& return pixelarium::imaging::ImageFileType::UNKNOWN; } +/// @brief Factory for instantiating implementations of IPixelariumImage based on the given file type. class PixelariumImageFactory { + using Log = utils::log::ILog; + public: - static std::unique_ptr CreateImage(const std::string& uri); + 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 099ae22..1de2717 100644 --- a/lib/imaging/impl/PixelariumCzi.cpp +++ b/lib/imaging/impl/PixelariumCzi.cpp @@ -1,58 +1,95 @@ #include "PixelariumCzi.hpp" +#include #include +#include +#include #include +#include #include "libCZI.h" +#include "utilities/ILog.hpp" -pixelarium::imaging::PixelariumCzi::PixelariumCzi(const std::string& uri) +bool comp_blockinfo_params(const pixelarium::imaging::CziParams& params, const libCZI::SubBlockInfo& info) { - if (!std::filesystem::exists(uri)) - { - throw std::runtime_error("Render file not found."); - } + bool res{true}; - this->is_empty_ = false; - this->uri_ = std::filesystem::path(uri); + info.coordinate.EnumValidDimensions( + [&](libCZI::DimensionIndex dim, int start) -> bool + { + if (params.dimension_map.at(dim) == start) + { + return true; + } - auto stream = libCZI::CreateStreamFromFile(this->uri_.wstring().c_str()); - this->czi_reader_ = libCZI::CreateCZIReader(); - this->czi_reader_->Open(stream); - this->image_statistics_ = this->czi_reader_->GetStatistics(); + res &= false; + return true; + }); + + return res; } -std::unique_ptr CZISubBlockToCvMat(std::shared_ptr bitmap, libCZI::PixelType pixeltype) + +constexpr int try_get_index_match(const pixelarium::imaging::CziParams& params, libCZI::ICZIReader& reader) +{ + int index{-1}; + reader.EnumerateSubBlocks( + [&](int idx, const libCZI::SubBlockInfo& info) -> bool + { + if (comp_blockinfo_params(params, info)) + { + index = idx; + // returning false will stop the enumeration + return false; + } + + return true; + }); + + return index; +} + +std::unique_ptr CZISubBlockToCvMat(std::shared_ptr bitmap, libCZI::PixelType pixeltype, + const pixelarium::utils::log::ILog& log) { size_t pixel_size{0}; int target_type; + std::pair pixel_pair; switch (pixeltype) { case libCZI::PixelType::Gray8: + pixel_pair.first = "Gray8"; + pixel_pair.second = "CV_8U"; pixel_size = 1; target_type = CV_8U; break; case libCZI::PixelType::Gray16: + pixel_pair.first = "Gray16"; + pixel_pair.second = "CV_16U"; pixel_size = 2; target_type = CV_16U; break; case libCZI::PixelType::Bgr24: + pixel_pair.first = "Bgr24"; + pixel_pair.second = "CV_8UC3"; pixel_size = 3; target_type = CV_8UC3; break; case libCZI::PixelType::Bgra32: + pixel_pair.first = "Bgra32"; + pixel_pair.second = "CV_8CU4"; target_type = CV_8UC4; case libCZI::PixelType::Gray32: + pixel_pair.first = "Gray32"; + pixel_pair.second = "CV_32S"; target_type = CV_32S; case libCZI::PixelType::Gray32Float: + pixel_pair.first = "Gray32Float"; + pixel_pair.second = "CV_32F"; target_type = CV_32F; pixel_size = 4; break; - // case libCZI::PixelType::Gray64ComplexFloat: - // case libCZI::PixelType::Gray64Float: - // target_type = - // pixel_size = 8; - // break; default: pixel_size = -1; break; @@ -60,6 +97,9 @@ std::unique_ptr CZISubBlockToCvMat(std::shared_ptr if (pixel_size < 0) return nullptr; + 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); @@ -86,6 +126,9 @@ std::unique_ptr CZISubBlockToCvMat(std::shared_ptr target_row[3 * w + 1] = source_row[3 * w + 1]; target_row[3 * w + 2] = source_row[3 * w + 2]; break; + case 4: + reinterpret_cast(target_row)[w] = reinterpret_cast(source_row)[w]; + break; default: throw std::runtime_error("Unknown pixel type requested!"); break; @@ -97,11 +140,48 @@ std::unique_ptr CZISubBlockToCvMat(std::shared_ptr return fill_mat; } -std::unique_ptr pixelarium::imaging::PixelariumCzi::TryGetImage() +std::unique_ptr pixelarium::imaging::PixelariumCzi::SubblockToCvMat(int index) { - auto block = this->czi_reader_->ReadSubBlock(0); + log_.Info(std::format("{}: constructing bitmap with index {}", __PRETTY_FUNCTION__, index)); + auto block = this->czi_reader_->ReadSubBlock(index); auto bitmap = block->CreateBitmap(); - auto res = CZISubBlockToCvMat(bitmap, block->GetSubBlockInfo().pixelType); - - return res; + return CZISubBlockToCvMat(bitmap, block->GetSubBlockInfo().pixelType, log_); +} + +pixelarium::imaging::PixelariumCzi::PixelariumCzi(const std::string& uri, const Log& log) : log_(log) +{ + if (!std::filesystem::exists(uri)) + { + throw std::runtime_error("Render file not found."); + } + + this->is_empty_ = false; + this->uri_ = std::filesystem::path(uri); + + auto stream = libCZI::CreateStreamFromFile(this->uri_.wstring().c_str()); + this->czi_reader_ = libCZI::CreateCZIReader(); + this->czi_reader_->Open(stream); + this->image_statistics_ = this->czi_reader_->GetStatistics(); + + this->image_statistics_.dimBounds.EnumValidDimensions( + [&](libCZI::DimensionIndex dim, int start, int) -> bool + { + this->dimension_map_[dim] = start; + return true; + }); +} + +std::unique_ptr pixelarium::imaging::PixelariumCzi::TryGetImage() { return SubblockToCvMat(0); } + +std::unique_ptr 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_); + + if (index < 0) + { + return SubblockToCvMat(0); + } + + return SubblockToCvMat(index); } diff --git a/lib/imaging/impl/PixelariumCzi.hpp b/lib/imaging/impl/PixelariumCzi.hpp index 99e3b89..a1b7ec7 100644 --- a/lib/imaging/impl/PixelariumCzi.hpp +++ b/lib/imaging/impl/PixelariumCzi.hpp @@ -4,19 +4,25 @@ #include #include "../IPixelariumImage.hpp" +#include "utilities/ILog.hpp" #include "libCZI.h" namespace pixelarium::imaging { +/// @brief An implementation of IImageQuery to work on CZI images. +/// @note Check the documentation here https://zeiss.github.io/libczi/pages/mainpage.html#czi-in-a-nutshell struct CziParams : public IImageQuery { - + /// @brief A map providing the start coordinate for each dimension in the CZI + std::unordered_map dimension_map; }; +/// @brief Implements support for .czi-images in the realm of IPixelariumImage class PixelariumCzi : public IPixelariumImage { + using Log = pixelarium::utils::log::ILog; public: - explicit PixelariumCzi(const std::string& uri); + explicit PixelariumCzi(const std::string& uri, const Log& log); ~PixelariumCzi() { if (this->czi_reader_) @@ -27,11 +33,7 @@ class PixelariumCzi : public IPixelariumImage public: std::unique_ptr TryGetImage() override; - std::unique_ptr TryGetImage(const IImageQuery&) override - { - // ToDo: proper error - throw std::runtime_error("Not implemented."); - } + std::unique_ptr TryGetImage(const IImageQuery&) override; std::vector> TryGetImages(const IImageQuery&) override { @@ -46,6 +48,9 @@ class PixelariumCzi : public IPixelariumImage public: const static ImageFileType type_{ImageFileType::CZI}; + private: + std::unique_ptr SubblockToCvMat(int index); + private: // this should be set by each image getter // after a new cv::Mat could be instantiated @@ -54,5 +59,9 @@ class PixelariumCzi : public IPixelariumImage libCZI::SubBlockStatistics image_statistics_; std::shared_ptr czi_reader_; + + std::unordered_map dimension_map_; + + const Log& log_; }; } // namespace pixelarium::imaging diff --git a/lib/imaging/impl/PixelariumJpg.hpp b/lib/imaging/impl/PixelariumJpg.hpp index 74d097e..9e66007 100644 --- a/lib/imaging/impl/PixelariumJpg.hpp +++ b/lib/imaging/impl/PixelariumJpg.hpp @@ -7,6 +7,7 @@ namespace pixelarium::imaging { +/// @brief Implements support for .jpg-images in the realm of IPixelariumImage class PixelariumJpg : public IPixelariumImage { public: diff --git a/lib/imaging/impl/PixelariumPng.hpp b/lib/imaging/impl/PixelariumPng.hpp index 78fa238..dacf31f 100644 --- a/lib/imaging/impl/PixelariumPng.hpp +++ b/lib/imaging/impl/PixelariumPng.hpp @@ -7,6 +7,7 @@ namespace pixelarium::imaging { +/// @brief Implements support for .png-images in the realm of IPixelariumImage class PixelariumPng : public IPixelariumImage { public: diff --git a/lib/rendering/CvMatRender.cpp b/lib/rendering/CvMatRender.cpp index 3bddf67..402ca92 100644 --- a/lib/rendering/CvMatRender.cpp +++ b/lib/rendering/CvMatRender.cpp @@ -1,9 +1,7 @@ #include "CvMatRender.hpp" -#include #include #include -#include #include "imaging/IPixelariumImage.hpp" @@ -11,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(std::shared_ptr& img) : base_(img), texture_(0) +pixelarium::render::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 @@ -31,15 +29,6 @@ pixelarium::render::CvMatRender::~CvMatRender() } } -/// @brief Resets the render image with a new PixelariumImage. -/// @param img A shared pointer to the new PixelariumImage. -void pixelarium::render::CvMatRender::ResetRenderImage(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. @@ -103,11 +92,7 @@ GLuint pixelarium::render::CvMatRender::Render() { return this->uploadTexture(); /// @return The ID of the OpenGL texture. GLuint pixelarium::render::CvMatRender::Render(float factor) { - auto res_val {this->base_->TryGetImage()}; - if (res_val) - { - cv::resize(*res_val, this->img_, cv::Size(0, 0), factor, factor, cv::INTER_LINEAR_EXACT); - } + cv::resize(this->base_, this->img_, cv::Size(0, 0), factor, factor, cv::INTER_LINEAR_EXACT); return this->uploadTexture(); } @@ -118,14 +103,7 @@ GLuint pixelarium::render::CvMatRender::Render(float factor) /// @return The ID of the OpenGL texture. GLuint pixelarium::render::CvMatRender::Render(size_t width, size_t height) { - auto res_val {this->base_->TryGetImage()}; - - if (!res_val) - { - return this->Render(1.0f); - } - - const auto sz{res_val->size()}; + const auto sz{this->base_.size()}; const auto get_factor = [](auto opt1, auto opt2) -> float { return opt1 < opt2 ? opt1 : opt2; }; @@ -136,18 +114,6 @@ GLuint pixelarium::render::CvMatRender::Render(size_t width, size_t height) void pixelarium::render::CvMatRender::ResetRenderImage() { - if (this->base_ == nullptr) - { - return; - } - - auto root_res = this->base_->TryGetImage(); - - if (!root_res) - { - return; - } - // we copy here - this->img_ = root_res->clone(); + this->img_ = this->base_.clone(); } diff --git a/lib/rendering/CvMatRender.hpp b/lib/rendering/CvMatRender.hpp index 8b31cb4..f776cab 100644 --- a/lib/rendering/CvMatRender.hpp +++ b/lib/rendering/CvMatRender.hpp @@ -2,7 +2,6 @@ // windows.h must come before GL/GL.h here. // clang format would change this, effectively rendering the build broken. // clang-format off -#include #ifdef _WIN32 #include #include @@ -13,11 +12,12 @@ #endif #include // Will drag system OpenGL headers #endif -#include "imaging/IPixelariumImage.hpp" +#include // clang-format on namespace pixelarium::render { +/// @brief Renders cv::Mat bitmaps as OpenGL textures. class CvMatRender { public: @@ -26,25 +26,24 @@ class CvMatRender // get removed in the future) // as the using AppGLFW constructs it empty as a member // when coming to life. - CvMatRender() = default; + // CvMatRender() = default; CvMatRender(CvMatRender&) = delete; CvMatRender(const CvMatRender&) = delete; CvMatRender(CvMatRender&&) = delete; - CvMatRender& operator=(CvMatRender&) = default; - CvMatRender& operator=(CvMatRender&& other) = default; + CvMatRender& operator=(CvMatRender&) = delete; + CvMatRender& operator=(CvMatRender&& other) = delete; ~CvMatRender(); - explicit CvMatRender(std::shared_ptr& img); + explicit CvMatRender(const cv::Mat& img); public: GLuint Render(); GLuint Render(float factor); GLuint Render(size_t width, size_t height); void ResetRenderImage(); - void ResetRenderImage(std::shared_ptr& img); private: cv::Mat img_; - std::shared_ptr base_; + const cv::Mat& base_; GLuint texture_; GLuint uploadTexture(); diff --git a/lib/rendering/IPixelariumImageView.hpp b/lib/rendering/IPixelariumImageView.hpp index abc14e7..c76d041 100644 --- a/lib/rendering/IPixelariumImageView.hpp +++ b/lib/rendering/IPixelariumImageView.hpp @@ -3,10 +3,11 @@ #include #include "imaging/IPixelariumImage.hpp" -#include "rendering/CvMatRender.hpp" +#include "imgui.h" namespace pixelarium::render { +/// @brief An interface defining the contract on views to dedicated implementations of IPixelariumImage class IPixelariumImageView { public: @@ -17,11 +18,14 @@ class IPixelariumImageView public: virtual const bool* GetStatus() const noexcept { return &this->open_p; } virtual void ForceUpdate() noexcept { this->is_dirty_ = true; } + 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_{}; - render::CvMatRender render_; bool open_p{true}; bool is_dirty_{true}; }; diff --git a/lib/rendering/ImageViewFactory.cpp b/lib/rendering/ImageViewFactory.cpp index 2ac5e70..15bffcb 100644 --- a/lib/rendering/ImageViewFactory.cpp +++ b/lib/rendering/ImageViewFactory.cpp @@ -1,6 +1,8 @@ #include "ImageViewFactory.hpp" +#include #include + #include "imaging/PixelariumImageFactory.hpp" #include "rendering/IPixelariumImageView.hpp" #include "rendering/PixelariumImageViewCzi.hpp" @@ -8,9 +10,10 @@ /// @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. +/// @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( - size_t image_id) + resources::ResourceKey image_id) { auto res{this->image_pool_.GetResource(image_id)}; @@ -35,15 +38,14 @@ std::unique_ptr pixelarium::render::Im return {}; case imaging::ImageFileType::PNG: case imaging::ImageFileType::JPG: - log_.Info("Creating a Default View"); + log_.Info(std::format("{}: Creating a Default View", __PRETTY_FUNCTION__)); // beware: here we copy the actual image resource over to the new image return std::make_unique(img); case imaging::ImageFileType::CZI: - log_.Info("{}: Creating a CZI View"); + log_.Info(std::format("{}: Creating a CZI View", __PRETTY_FUNCTION__)); // beware: here we copy the actual image resource over to the new image - return std::make_unique(img); + return std::make_unique(img, log_); default: return {}; } } - diff --git a/lib/rendering/ImageViewFactory.hpp b/lib/rendering/ImageViewFactory.hpp index ef4c246..a56c5ed 100644 --- a/lib/rendering/ImageViewFactory.hpp +++ b/lib/rendering/ImageViewFactory.hpp @@ -6,6 +6,7 @@ #include "utilities/ILog.hpp" namespace pixelarium::render { +/// @brief Factory for instantiating matching views to different implementations of IPixelariumImage. class ImageViewFactory { using Image = imaging::IPixelariumImage; @@ -15,7 +16,7 @@ class ImageViewFactory public: explicit ImageViewFactory(Pool& pool, const Log& log) : image_pool_(pool), log_(log) {} - std::unique_ptr RenderImage(size_t id); + std::unique_ptr RenderImage(resources::ResourceKey id); private: Pool& image_pool_; diff --git a/lib/rendering/PixelariumImageViewCzi.cpp b/lib/rendering/PixelariumImageViewCzi.cpp index 815b213..3f59489 100644 --- a/lib/rendering/PixelariumImageViewCzi.cpp +++ b/lib/rendering/PixelariumImageViewCzi.cpp @@ -1,11 +1,31 @@ #include "PixelariumImageViewCzi.hpp" #include +#include #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) + : log_(log), render_(std::make_unique(*img->TryGetImage())) +{ + img_ = img; + auto czi_img = std::static_pointer_cast(this->img_); + + auto stats = czi_img->GetStatistics(); + stats.dimBounds.EnumValidDimensions( + [&](libCZI::DimensionIndex dim, int start, int) -> bool + { + this->dimension_map_[dim] = start; + return true; + }); + + this->SetInitialSize(); + log_.Info(std::format("{}: dimension map size: {}", __PRETTY_FUNCTION__, dimension_map_.size())); +} /// @brief Displays the image in an ImGui window. /// @@ -20,7 +40,14 @@ void pixelarium::render::PixelariumImageViewCzi::ShowImage() if (!this->cached_image_ || this->is_dirty_) { - this->cached_image_ = this->img_->TryGetImage(); + 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; } @@ -38,8 +65,8 @@ void pixelarium::render::PixelariumImageViewCzi::ShowImage() 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(); + ? this->render_->Render(static_cast(this->curr_dim_.x), static_cast(this->curr_dim_.y)) + : this->render_->Render(); this->curr_dim_ = new_dim; @@ -53,12 +80,22 @@ void pixelarium::render::PixelariumImageViewCzi::ShowImage() ImGui::Text("%s", std::format("Render Dimensions W : {}, H: {}", curr_dim_.x, curr_dim_.y).c_str()); ImGui::Text("Dimensions"); ImGui::Separator(); + if (ImGui::Button("Update")) + { + this->ForceUpdate(); + } auto stats = czi_img->GetStatistics(); stats.dimBounds.EnumValidDimensions( [&](libCZI::DimensionIndex dim, int start, int size) -> bool { - ImGui::Text("%c\t Start: %d\t End: %d", libCZI::Utils::DimensionToChar(dim), start, size); + auto dim_char = libCZI::Utils::DimensionToChar(dim); + if (ImGui::SliderInt(std::format("{}({}-{})", dim_char, start, size).c_str(), &dimension_map_[dim], start, + size - 1)) + { + this->ForceUpdate(); + } + return true; }); diff --git a/lib/rendering/PixelariumImageViewCzi.hpp b/lib/rendering/PixelariumImageViewCzi.hpp index 2f3feca..7e301c1 100644 --- a/lib/rendering/PixelariumImageViewCzi.hpp +++ b/lib/rendering/PixelariumImageViewCzi.hpp @@ -1,21 +1,25 @@ #pragma once +#include +#include + #include "imgui.h" +#include "libCZI_DimCoordinate.h" +#include "rendering/CvMatRender.hpp" #include "rendering/IPixelariumImageView.hpp" +#include "utilities/ILog.hpp" namespace pixelarium::render { +/// @brief A CZI-specific implementation of IPixelariumImageView. class PixelariumImageViewCzi : public IPixelariumImageView { using Image = imaging::IPixelariumImage; using Render = render::CvMatRender; + using Log = utils::log::ILog; public: - explicit PixelariumImageViewCzi(std::shared_ptr img) - { - img_ = img; - render_ = Render(img_); - } + explicit PixelariumImageViewCzi(std::shared_ptr img, const Log& log); PixelariumImageViewCzi() = delete; PixelariumImageViewCzi(PixelariumImageViewCzi&) = delete; PixelariumImageViewCzi(const PixelariumImageViewCzi&) = delete; @@ -27,5 +31,8 @@ class PixelariumImageViewCzi : public IPixelariumImageView private: ImVec2 curr_dim_{}; + const Log& log_; + std::unordered_map dimension_map_; + std::unique_ptr render_; }; } // namespace pixelarium::render diff --git a/lib/rendering/PixelariumImageViewDefault.hpp b/lib/rendering/PixelariumImageViewDefault.hpp index db66cc3..2464413 100644 --- a/lib/rendering/PixelariumImageViewDefault.hpp +++ b/lib/rendering/PixelariumImageViewDefault.hpp @@ -9,16 +9,18 @@ namespace pixelarium::render { +/// @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; public: - explicit PixelariumImageViewDefault(std::shared_ptr img) + explicit PixelariumImageViewDefault(std::shared_ptr img) : render_(*img->TryGetImage()) { img_ = img; - render_ = Render(img_); + this->SetInitialSize(); } PixelariumImageViewDefault() = delete; PixelariumImageViewDefault(PixelariumImageViewDefault&) = delete; @@ -31,5 +33,6 @@ class PixelariumImageViewDefault : public IPixelariumImageView private: ImVec2 curr_dim_{}; + CvMatRender render_; }; } // namespace pixelarium::render diff --git a/lib/rendering/RenderImageManager.cpp b/lib/rendering/RenderImageManager.cpp index c3218c3..5f867fc 100644 --- a/lib/rendering/RenderImageManager.cpp +++ b/lib/rendering/RenderImageManager.cpp @@ -48,8 +48,13 @@ void pixelarium::render::RenderImageManager::Add(resources::ResourceKey key) noe { // we don't want to add what's already there // or empty render images + if (this->render_image_map_.contains(key)) + { + return; + } + auto current_view = this->view_factory_->RenderImage(key); - if (this->render_image_map_.contains(key) || current_view == nullptr) + if (current_view == nullptr) { return; } diff --git a/lib/rendering/RenderImageManager.hpp b/lib/rendering/RenderImageManager.hpp index dc88ae8..c72f099 100644 --- a/lib/rendering/RenderImageManager.hpp +++ b/lib/rendering/RenderImageManager.hpp @@ -23,6 +23,14 @@ struct RenderImageStateWrapper const bool* show_state; }; + +/// @brief Manage instances of IPixelariumImageView. +/// +/// This class is used to keep track of what must be rendered. +/// It manages a set of IPixelariumImageView instances that can be traversed +/// via its Enumerate() function. +/// Views that shall not be rendered anymore should be marked for deletion +/// with MarkForDeletion() class RenderImageManager { using Pool = resources::ImageResourcePool; diff --git a/lib/resources/resource.hpp b/lib/resources/resource.hpp index 0140b63..a4c0d22 100644 --- a/lib/resources/resource.hpp +++ b/lib/resources/resource.hpp @@ -12,6 +12,7 @@ namespace pixelarium::resources { using ResourceKey = size_t; +/// @brief A dedicated exception to be thrown when a resource of an IResourcePool is empty. struct empty_resource_exception : public std::exception { empty_resource_exception() {}; @@ -22,15 +23,21 @@ struct empty_resource_exception : public std::exception std::string message_ = "Empty Resource"; }; - +/// @brief Abstract representation of a Resource. +/// This is meant to be implemented by arbitrary explicit resource types and thus +/// gives no contract other than the abstract type. struct IResource { virtual ~IResource() = default; }; +/// @brief Defines a concept for a resource type +/// @tparam R The resource template parameter template 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 class IResourcePool { diff --git a/lib/utilities/ILog.hpp b/lib/utilities/ILog.hpp index 4f2c1ea..8bd9217 100644 --- a/lib/utilities/ILog.hpp +++ b/lib/utilities/ILog.hpp @@ -1,6 +1,11 @@ #pragma once #include +#ifdef WIN32 +#define __PRETTY_FUNCTION__ __FUNCTION__ +#endif + + namespace pixelarium::utils::log { enum class LogLevel @@ -11,6 +16,7 @@ enum class LogLevel Warn = 1 << 3, Error = 1 << 4, }; +/// @brief Interface for logging implementations. class ILog { public: diff --git a/lib/utilities/SpdLogger.hpp b/lib/utilities/SpdLogger.hpp index c844ffc..2aa1196 100644 --- a/lib/utilities/SpdLogger.hpp +++ b/lib/utilities/SpdLogger.hpp @@ -8,6 +8,8 @@ #include "ILog.hpp" namespace pixelarium::utils::log { +/// @brief Implements ILog using the spdlog library +/// see https://github.com/gabime/spdlog class SpdLogger : public ILog { public: