diff --git a/CMakeLists.txt b/CMakeLists.txt index 5047fc3..d385464 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(pixelarium VERSION 0.0.9) +project(pixelarium VERSION 0.0.10) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_CXX_STANDARD 23) @@ -25,6 +25,8 @@ option(PIXELARIUM_BUILD_DOCS_ONLY "Build only Documentation (no compilatoin)" OF option(PIXELARIUM_BUILD_EXAMPLES "Build example projects" ON) #==================== +string(TOUPPER "${CMAKE_PROJECT_NAME}" PIXELARIUM_TITLE) + if(PIXELARIUM_BUILD_DOCS OR PIXELARIUM_BUILD_DOCS_ONLY) include(${PROJECT_SOURCE_DIR}/cmake/awesomeDoxygen.cmake) set(MAINPAGE_FILE "doc/index.md") @@ -71,8 +73,6 @@ if(UNIX) set(CMAKE_CXX_FLAGS "-Wall -Wextra -g") endif() -string(TOUPPER "${CMAKE_PROJECT_NAME}" PIXELARIUM_TITLE) - add_subdirectory(${pfd_DIR}) add_subdirectory(${spdlog_DIR}) add_subdirectory(${glfw3_module_DIR}) diff --git a/Readme.org b/Readme.org index 70033d1..c9e15eb 100644 --- a/Readme.org +++ b/Readme.org @@ -17,6 +17,17 @@ It tries to be as flexible as possible. This is still work in progress and will change significantly. +* Supported Types + +Currently, Pixelarium supports the following image file formats: +- jpeg +- png +- tiff +- czi + + +where possible, the [[https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html][OpenCV codecs]] are used to interpret the respective file type. The czi-format is supported via [[https://github.com/ZEISS/libczi][libCZI]]. + * Prerequisites Dependencies are either submodules in the =modules= subdirectory or artifacts of the cmake build process from the =cmake= directory. This repository should therefore be cloned recursively: @@ -88,3 +99,7 @@ This is the most straight-forward usage of Pixelarium. It simply instantiates a ** [[file:examples/custom_0/][custom_0]] This is meant to showcase that [[file:lib/app/DefaultApp.hpp][=DefaultApp=]] ([[file:lib/app/AppGLFW.hpp][=AppGLFW=]] as well) is meant to be customized via inheritance. + +** [[file:examples/custom_1/][custom_1]] + +Is a slightly more involved example showcasing how to inject a user defined control into the existing scaffolding of =DefaultApp= using a multiplication filter. diff --git a/doc/versions.md b/doc/versions.md index b9954ce..dcebff2 100644 --- a/doc/versions.md +++ b/doc/versions.md @@ -2,6 +2,7 @@ | 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 | diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a6fb3fa..2c6f127 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(simple) add_subdirectory(custom_0) +add_subdirectory(custom_1) diff --git a/examples/custom_1/CMakeLists.txt b/examples/custom_1/CMakeLists.txt new file mode 100644 index 0000000..31ee14b --- /dev/null +++ b/examples/custom_1/CMakeLists.txt @@ -0,0 +1,22 @@ +set(SRC + ${CMAKE_CURRENT_SOURCE_DIR}/custom_1.cpp) + +set(CUSTOM_1_NAME "${PROJECT_NAME}_custom1_app") + +add_executable(${CUSTOM_1_NAME} ${SRC}) + +target_link_libraries(${CUSTOM_1_NAME} + PRIVATE pixelariumimagelib + PRIVATE pixelariumrenderlib + PRIVATE pixelariumutilslib + PRIVATE pixelariumresourcelib + PRIVATE pixelariumapplicationlib) + +target_include_directories(${CUSTOM_1_NAME} + PRIVATE ${PROJECT_SOURCE_DIR}/src + PRIVATE ${PROJECT_SOURCE_DIR}/lib + PRIVATE ${PROJECT_SOURCE_DIR}/lib/imaging + PRIVATE ${PROJECT_SOURCE_DIR}/lib/app + PRIVATE ${spdlog_DIR}/include + PRIVATE ${LIBCZI_INCLUDE_DIR} + PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/examples/custom_1/custom_1.cpp b/examples/custom_1/custom_1.cpp new file mode 100644 index 0000000..d144ad8 --- /dev/null +++ b/examples/custom_1/custom_1.cpp @@ -0,0 +1,146 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "DefaultApp.hpp" +#include "IPixelariumImage.hpp" +#include "imgui.h" +#include "impl/PixelariumMem.hpp" +#include "resources/resource.hpp" +#include "utilities/ILog.hpp" +#include "utilities/SpdLogger.hpp" + +using namespace pixelarium; +using namespace std; +using Log = utils::log::ILog; +using Pool = resources::ImageResourcePool; + +// setup a logger +#ifdef _WIN32 +unique_ptr logger{ + make_unique(string(getenv("APPDATA")) + "/pixelarium/simple_app.log", "default")}; +#else +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; + +class Selector +{ + using Pool = resources::ImageResourcePool; + Pool& pool_; + std::string preview_0_{"None"}; + std::string preview_1_{"None"}; + resources::ResourceKey selected_key_0; + resources::ResourceKey selected_key_1; + int idx_{0}; + + public: + Selector(resources::ImageResourcePool& pool) : pool_(pool) {} + void SelectImage() + { + ImGui::Begin("Image Multiply"); + static int selected_idx_0{0}; + static int selected_idx_1{0}; + if (ImGui::BeginCombo("Select first image", preview_0_.c_str())) + { + pool_.Enumerate( + [&](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)) + { + selected_idx_0 = idx; + preview_0_ = img.Name(); + selected_key_0 = key; + } + + if (is_selected) + { + ImGui::SetItemDefaultFocus(); + } + }); + + ImGui::EndCombo(); + } + + if (ImGui::BeginCombo("Select second image", preview_1_.c_str())) + { + pool_.Enumerate( + [&](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)) + { + selected_idx_1 = idx; + preview_1_ = img.Name(); + selected_key_1 = key; + } + + if (is_selected) + { + ImGui::SetItemDefaultFocus(); + } + }); + + ImGui::EndCombo(); + } + + if (ImGui::Button("Process")) + { + auto img0 = pool_.GetResource(selected_key_0); + auto img_mat0 = img0.lock()->TryGetImage(); + 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->size != img_mat1->size) return; + + cv::multiply(*img_mat0, *img_mat1, *img_mat0); + + std::string name{std::format("Multiply_{}", idx_)}; + pool_.SetResource(std::make_unique(*img_mat0, name, *logger)); + + ++idx_; + } + + ImGui::End(); + } +}; + +// create a custom app inheriting from the library's default app +class MyApp : public application::DefaultApp +{ + Selector select_; + + public: + MyApp(const Log& log, Pool& pool) : application::DefaultApp(log, pool), select_(pool) {} + + // override some of the defaults member functions + void Run() override; +}; + +int main() +{ + // some initial log message + logger->Info(std::format("{}: Starting Application {}", __FUNCTION__, "Pixelarium")); + + // create a custom application, inject its dependencies and start it + auto app{MyApp(*logger, image_pool)}; + + app.Start(); +} + +void MyApp::Run() +{ + application::DefaultApp::Run(); + select_.SelectImage(); +} diff --git a/lib/app/PixelariumGallery.hpp b/lib/app/PixelariumGallery.hpp index 6c36d8c..f1d3813 100644 --- a/lib/app/PixelariumGallery.hpp +++ b/lib/app/PixelariumGallery.hpp @@ -6,9 +6,17 @@ 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& r) { static_cast&>(r); }; +concept GalleryT = requires(P& p) { static_cast&>(p); }; +/// @brief Interface for a Pixelarium gallery. +/// +/// Defines generic functionality for a gallery of a specific +/// resource type given by the template argument. +/// @tparam GalleryT The type of IResourcePool that the given implementation +/// provides a gallery for. template class IPixelariumGallery { @@ -17,6 +25,7 @@ class IPixelariumGallery virtual void RenderGallery() = 0; }; +/// @brief Implements IPixelariumGallery for a ImageResourcePool class PixelariumImageGallery : IPixelariumGallery { using Pool = resources::ImageResourcePool; diff --git a/lib/imaging/CMakeLists.txt b/lib/imaging/CMakeLists.txt index b109bd3..3f25051 100644 --- a/lib/imaging/CMakeLists.txt +++ b/lib/imaging/CMakeLists.txt @@ -15,6 +15,10 @@ set(IMAGELIBSRC impl/PixelariumPng.cpp impl/PixelariumCzi.hpp impl/PixelariumCzi.cpp + impl/PixelariumTiff.hpp + impl/PixelariumTiff.cpp + impl/PixelariumMem.hpp + impl/PixelariumMem.cpp ) set(IMAGELIBLIBNAME pixelariumimagelib) diff --git a/lib/imaging/IPixelariumImage.hpp b/lib/imaging/IPixelariumImage.hpp index e5b8727..045582c 100644 --- a/lib/imaging/IPixelariumImage.hpp +++ b/lib/imaging/IPixelariumImage.hpp @@ -23,6 +23,10 @@ enum class ImageFileType kJpg = 2, /// @brief Represents a CZI image file. kCzi = 3, + /// @brief Represents a TIFF image file. + kTiff = 4, + /// @brief Represents an in-memory image. + kMemory = 5, }; /// @brief An abstract interface to define a semantic query diff --git a/lib/imaging/PixelariumImageFactory.cpp b/lib/imaging/PixelariumImageFactory.cpp index 8b2d5c0..b2c162a 100644 --- a/lib/imaging/PixelariumImageFactory.cpp +++ b/lib/imaging/PixelariumImageFactory.cpp @@ -3,9 +3,11 @@ #include #include +#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 pixelarium::imaging::PixelariumImageFactory::CreateImage(const std::string& uri, const Log& log) @@ -17,18 +19,19 @@ pixelarium::imaging::PixelariumImageFactory::CreateImage(const std::string& uri, { case ImageFileType::kUnknown: return {}; - break; case ImageFileType::kAbstract: return {}; - break; case ImageFileType::kPng: return std::make_unique(uri); - break; case ImageFileType::kJpg: return std::make_unique(uri); - break; case ImageFileType::kCzi: return std::make_unique(uri, log); - break; + case ImageFileType::kTiff: + return std::make_unique(uri, log); + case ImageFileType::kMemory: + return std::make_unique(cv::Mat(), uri, log); + default: + return {}; } } diff --git a/lib/imaging/PixelariumImageFactory.hpp b/lib/imaging/PixelariumImageFactory.hpp index 39cebea..4114329 100644 --- a/lib/imaging/PixelariumImageFactory.hpp +++ b/lib/imaging/PixelariumImageFactory.hpp @@ -23,6 +23,10 @@ constexpr pixelarium::imaging::ImageFileType ExtensionToType(const std::string& { return pixelarium::imaging::ImageFileType::kCzi; } + if (lower_ext == ".tiff" || lower_ext == ".tif") + { + return pixelarium::imaging::ImageFileType::kTiff; + } return pixelarium::imaging::ImageFileType::kUnknown; } diff --git a/lib/imaging/impl/PixelariumMem.cpp b/lib/imaging/impl/PixelariumMem.cpp new file mode 100644 index 0000000..e9ac226 --- /dev/null +++ b/lib/imaging/impl/PixelariumMem.cpp @@ -0,0 +1,26 @@ +#include "PixelariumMem.hpp" + +#include +#include +#include +#include + +pixelarium::imaging::PixelariumMem::PixelariumMem(const cv::Mat& img, const std::string& name, const Log& log) + : img_(img), log_(log), name_(name) +{ + this->is_empty_ = false; + this->uri_ = std::filesystem::path(); +} + +std::unique_ptr 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 + // of. + // I.e., it only makes sense for the file types that do not already manage a cv::Mat in memory. + // PixelariumMem is meant for exactly this in-memory management of a cv::Mat though. + // 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_); +} diff --git a/lib/imaging/impl/PixelariumMem.hpp b/lib/imaging/impl/PixelariumMem.hpp new file mode 100644 index 0000000..9a7619f --- /dev/null +++ b/lib/imaging/impl/PixelariumMem.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "../IPixelariumImage.hpp" +#include "utilities/ILog.hpp" + +namespace pixelarium::imaging +{ +/// @brief Implements support for in-memory images in the realm of IPixelariumImage +class PixelariumMem : public IPixelariumImage +{ + using Log = pixelarium::utils::log::ILog; + + public: + explicit PixelariumMem(const cv::Mat& img, const std::string& name, const Log& log); + + // IPixelariumImage member implementations + public: + std::unique_ptr TryGetImage() override; + + std::unique_ptr TryGetImage(const IImageQuery&) override { throw std::runtime_error("Not implemented."); } + + std::vector> TryGetImages(const IImageQuery&) override + { + throw std::runtime_error("Not implemented."); + } + + void SetImage(const cv::Mat& img) { this->img_ = img; } + + std::string Name() const noexcept override { return this->name_; } + + bool Empty() const noexcept override { return this->is_empty_; } + + public: + const static ImageFileType type_{ImageFileType::kMemory}; + + private: + // this should be set by each image getter + // after a new cv::Mat could be instantiated + bool is_empty_{true}; + cv::Mat img_; + const Log& log_; + std::string name_; +}; +} // namespace pixelarium::imaging diff --git a/lib/imaging/impl/PixelariumTiff.cpp b/lib/imaging/impl/PixelariumTiff.cpp new file mode 100644 index 0000000..67a5a28 --- /dev/null +++ b/lib/imaging/impl/PixelariumTiff.cpp @@ -0,0 +1,34 @@ +#include "PixelariumTiff.hpp" + +#include +#include +#include +#include + +pixelarium::imaging::PixelariumTiff::PixelariumTiff(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); +} + +std::unique_ptr pixelarium::imaging::PixelariumTiff::TryGetImage() +{ + try + { + auto img = std::make_unique(cv::imread(this->uri_.string())); + + this->is_empty_ = false; + + return img; + } + catch (const std::exception& e) + { + this->is_empty_ = true; + return {}; + } +} diff --git a/lib/imaging/impl/PixelariumTiff.hpp b/lib/imaging/impl/PixelariumTiff.hpp new file mode 100644 index 0000000..95880a1 --- /dev/null +++ b/lib/imaging/impl/PixelariumTiff.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include "../IPixelariumImage.hpp" +#include "utilities/ILog.hpp" + +namespace pixelarium::imaging +{ +/// @brief Implements support for .tiff-images in the realm of IPixelariumImage +class PixelariumTiff : public IPixelariumImage +{ + using Log = pixelarium::utils::log::ILog; + + public: + explicit PixelariumTiff(const std::string& uri, const Log& log); + + // IPixelariumImage member implementations + public: + std::unique_ptr TryGetImage() override; + + std::unique_ptr TryGetImage(const IImageQuery&) override + { + // ToDo: proper error + throw std::runtime_error("Not possible with tiff."); + } + + std::vector> TryGetImages(const IImageQuery&) override + { + // ToDo: proper error + throw std::runtime_error("Not possible with tiff."); + } + + bool Empty() const noexcept override { return this->is_empty_; } + + public: + const static ImageFileType type_{ImageFileType::kTiff}; + + private: + // this should be set by each image getter + // after a new cv::Mat could be instantiated + bool is_empty_{true}; + const Log& log_; +}; +} // namespace pixelarium::imaging diff --git a/lib/rendering/ImageViewFactory.cpp b/lib/rendering/ImageViewFactory.cpp index 4ab3796..34b4cf0 100644 --- a/lib/rendering/ImageViewFactory.cpp +++ b/lib/rendering/ImageViewFactory.cpp @@ -3,6 +3,7 @@ #include #include +#include "imaging/IPixelariumImage.hpp" #include "imaging/PixelariumImageFactory.hpp" #include "rendering/IPixelariumImageView.hpp" #include "rendering/PixelariumImageViewCzi.hpp" @@ -15,6 +16,7 @@ std::unique_ptr pixelarium::render::ImageViewFactory::RenderImage( resources::ResourceKey image_id) { + using ImageType = imaging::ImageFileType; auto res{this->image_pool_.GetResource(image_id)}; auto img{res.lock()}; @@ -30,18 +32,24 @@ std::unique_ptr pixelarium::render::Im } auto type = imaging::ExtensionToType(img->Uri().extension().string()); + if (img->Uri().empty()) + { + log_.Info(std::format("{}: empty Uri for {}.", __PRETTY_FUNCTION__, img->Name())); + type = ImageType::kMemory; + } switch (type) { - case imaging::ImageFileType::kUnknown: - case imaging::ImageFileType::kAbstract: - return {}; - case imaging::ImageFileType::kPng: - case imaging::ImageFileType::kJpg: + case ImageType::kUnknown: + case ImageType::kAbstract: + case ImageType::kPng: + case ImageType::kJpg: + case ImageType::kTiff: + case ImageType::kMemory: 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::kCzi: + case ImageType::kCzi: 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, log_);