diff --git a/CMakeLists.txt b/CMakeLists.txt index 414a775..671cb83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.23) -project(pixelarium VERSION 0.0.11) +project(pixelarium VERSION 0.0.12) + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_CXX_STANDARD 23) @@ -21,32 +23,27 @@ message(STATUS "SPDLOG:\t" ${spdlog_DIR}) # Options option(PIXELARIUM_BUILD_UNITTESTS "Generate Unittests" ON) option(PIXELARIUM_BUILD_DOCS "Generate Documentation" ON) -option(PIXELARIUM_BUILD_DOCS_ONLY "Build only Documentation (no compilatoin)" OFF) +option(PIXELARIUM_BUILD_DOCS_ONLY "Build only Documentation (no compilation)" OFF) 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") - find_package(Doxygen) - if (DOXYGEN_FOUND) + find_package(Doxygen REQUIRED) + include(awesomeDoxygen) + set(MAINPAGE_FILE "${PROJECT_SOURCE_DIR}/doc/index.md") - set(DOXYGEN_IN ${PROJECT_SOURCE_DIR}/doc/Doxyfile.in) - set(DOXYGEN_OUT ${CMAKE_BINARY_DIR}/Doxyfile) + set(DOXYGEN_IN ${PROJECT_SOURCE_DIR}/doc/Doxyfile.in) + set(DOXYGEN_OUT ${CMAKE_BINARY_DIR}/Doxyfile) - configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) - message(STATUS "Building Docs") + configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) + message(STATUS "Building Docs") - add_custom_target(doxygen ALL - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Generating Docs") - - else (DOXYGEN_FOUND) - message(FATAL_ERROR "Doxygen need to be installed to generate the doxygen documentation") - endif (DOXYGEN_FOUND) + add_custom_target(doxygen ALL + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Generating Docs") if(PIXELARIUM_BUILD_DOCS_ONLY) message(STATUS "Documentation Generated successful") diff --git a/CMakePresets.json b/CMakePresets.json index b18b986..6446e73 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -16,7 +16,7 @@ "FETCHCONTENT_FULLY_DISCONNECTED": "OFF", "CMAKE_VERBOSE_MAKEFILE": "ON", "PIXELARIUM_BUILD_UNITTESTS": "ON", - "PIXELARIUM_BUILD_DOCS": "OFF", + "PIXELARIUM_BUILD_DOCS": "ON", "PIXELARIUM_BUILD_DOCS_ONLY": "OFF", "PIXELARIUM_BUILD_EXAMPLES": "ON" } diff --git a/Readme.org b/Readme.org index c9e15eb..07d27b2 100644 --- a/Readme.org +++ b/Readme.org @@ -99,7 +99,19 @@ 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. +As a usage example, it implements a simple binary image reader. It can be presented with a binary file of layout +#+begin_src C++ + struct ParsedImage + { + uint8_t depth; + uint8_t channels; + uint16_t width; + uint16_t height; + void* data; + }; +#+end_src +i.e., a header encoding 1 byte for the pixel-depth, 1 byte for the channel count, 2 byte each for width and height in pixel followed by the actual pixeldata. ** [[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. +An example showcasing how to inject a user defined control into the existing scaffolding of =DefaultApp= using a multiplication filter. This is in many ways similar to the previous example. diff --git a/cmake/awesomeDoxygen.cmake b/cmake/awesomeDoxygen.cmake index e2e8f0e..48da109 100644 --- a/cmake/awesomeDoxygen.cmake +++ b/cmake/awesomeDoxygen.cmake @@ -2,6 +2,7 @@ include(FetchContent) FetchContent_Declare( doxygen-awesome-css URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/heads/main.zip + DOWNLOAD_EXTRACT_TIMESTAMP 1 ) FetchContent_MakeAvailable(doxygen-awesome-css) diff --git a/cmake/implot.cmake b/cmake/implot.cmake new file mode 100644 index 0000000..80dd3db --- /dev/null +++ b/cmake/implot.cmake @@ -0,0 +1,9 @@ +include(FetchContent) +FetchContent_Declare( + implot + URL https://github.com/epezent/implot/archive/refs/tags/v0.17.zip + DOWNLOAD_EXTRACT_TIMESTAMP 1) + +FetchContent_MakeAvailable(implot) + +FetchContent_GetProperties(implot SOURCE_DIR IMPLOT_DIR) diff --git a/cmake/libCZI.cmake b/cmake/libCZI.cmake index 46922f0..dd754a3 100644 --- a/cmake/libCZI.cmake +++ b/cmake/libCZI.cmake @@ -4,6 +4,7 @@ FetchContent_Declare( libCZI GIT_REPOSITORY https://github.com/ZEISS/libczi.git GIT_TAG main + DOWNLOAD_EXTRACT_TIMESTAMP 1 ) if(NOT libCZI_POPULATED) diff --git a/cmake/googletest.cmake b/cmake/testgoogle.cmake similarity index 84% rename from cmake/googletest.cmake rename to cmake/testgoogle.cmake index 0b023cf..de8369c 100644 --- a/cmake/googletest.cmake +++ b/cmake/testgoogle.cmake @@ -2,12 +2,13 @@ include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip + DOWNLOAD_EXTRACT_TIMESTAMP 1 ) + # From this documentation: https://google.github.io/googletest/quickstart-cmake.html # For Windows: Prevent overriding the parent project's compiler/linker settings - if(WIN32) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) endif() -FetchContent_MakeAvailable(googletest) \ No newline at end of file +FetchContent_MakeAvailable(googletest) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 26db1ba..06a6b29 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -2,19 +2,19 @@ PROJECT_NAME = @PIXELARIUM_TITLE@ PROJECT_NUMBER = @CMAKE_PROJECT_VERSION@ OUTPUT_DIRECTORY = @PROJECT_BINARY_DIR@/doc -INPUT = @PROJECT_SOURCE_DIR@/lib/app \ - @PROJECT_SOURCE_DIR@/doc \ - @PROJECT_SOURCE_DIR@/lib/imaging \ - @PROJECT_SOURCE_DIR@/lib/imaging/impl \ - @PROJECT_SOURCE_DIR@/lib/utilities \ - @PROJECT_SOURCE_DIR@/lib/rendering \ - @PROJECT_SOURCE_DIR@/lib/resources +INPUT = @PROJECT_SOURCE_DIR@/doc/ \ + @PROJECT_SOURCE_DIR@/lib/app/include \ + @PROJECT_SOURCE_DIR@/lib/app/rendering/include \ + @PROJECT_SOURCE_DIR@/lib/imaging/include \ + @PROJECT_SOURCE_DIR@/lib/imaging/impl/include \ + @PROJECT_SOURCE_DIR@/lib/utilities/include \ + @PROJECT_SOURCE_DIR@/lib/resources/include IMAGE_PATH = @PROJECT_SOURCE_DIR@/doc/figures DOXYFILE_ENCODING = UTF-8 GENERATE_LATEX = NO FULL_PATH_NAMES = NO -USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/doc/index.md +USE_MDFILE_AS_MAINPAGE = @MAINPAGE_FILE@ HTML_EXTRA_STYLESHEET = @AWESOME_CSS_DIR@/doxygen-awesome.css diff --git a/doc/index.md b/doc/index.md index 6fce9d9..ede2a21 100644 --- a/doc/index.md +++ b/doc/index.md @@ -54,15 +54,15 @@ If you want to specify compiler settings and options which are not defined in a # Usage -The [examples](https://github.com/m-aXimilian/pixelarium/tree/fd400bf545ade029696c21119a50cf4bb67ffbac/examples) directory aims to showcase a few usage examples of this project. +The [examples](https://github.com/m-aXimilian/pixelarium/tree/main/examples) directory aims to showcase a few usage examples of this project. -All there is to do in order to get an initial window on screen is to create an instance of [`AppGLFW`](https://github.com/m-aXimilian/pixelarium/blob/fd400bf545ade029696c21119a50cf4bb67ffbac/lib/app/AppGLFW.hpp) (or one of its child classes) and start it. +All there is to do in order to get an initial window on screen is to create an instance of [`AppGLFW`](https://github.com/m-aXimilian/pixelarium/blob/main/lib/app/include/AppGLFW.hpp) (or one of its child classes) and start it. ```cpp - unique_ptr logger = make_unique("logfile.log", "loggername"); + const auto logger {SpdLogger("logfile.log", "loggername")}; ImageResourcePool image_pool; - auto app {DefaultApp(*logger, image_pool)}; + auto app {DefaultApp(logger, image_pool)}; app.Start(); ``` @@ -72,10 +72,28 @@ All there is to do in order to get an initial window on screen is to create an i ## simple -This is the most straight-forward usage of Pixelarium. It simply instantiates a [`DefaultApp`](https://github.com/m-aXimilian/pixelarium/blob/fd400bf545ade029696c21119a50cf4bb67ffbac/lib/app/DefaultApp.hpp) and runs it. +This is the most straight-forward usage of Pixelarium. It simply instantiates a [`DefaultApp`](https://github.com/m-aXimilian/pixelarium/blob/main/lib/app/include/DefaultApp.hpp) and runs it. ## custom_0 -This is meant to showcase that [`DefaultApp`]((https://github.com/m-aXimilian/pixelarium/blob/fd400bf545ade029696c21119a50cf4bb67ffbac/lib/app/DefaultApp.hpp)) ([`AppGLFW`](https://github.com/m-aXimilian/pixelarium/blob/fd400bf545ade029696c21119a50cf4bb67ffbac/lib/app/AppGLFW.hpp) as well) can be customized via inheritance. +This is meant to showcase that [`DefaultApp`]((https://github.com/m-aXimilian/pixelarium/blob/main/lib/app/include/DefaultApp.hpp)) ([`AppGLFW`](https://github.com/m-aXimilian/pixelarium/blob/main/lib/app/include/AppGLFW.hpp) as well) can be customized via inheritance. +As a usage example, it implements a simple binary image reader. It can be presented with a binary file of layout + +```cpp + struct ParsedImage + { + uint8_t depth; + uint8_t channels; + uint16_t width; + uint16_t height; + void* data; + }; +``` + +i.e., a header encoding 1 byte for the pixel-depth, 1 byte for the channel count, 2 byte each for width and height in pixel followed by the actual pixeldata. + +## custom_1 + +An example showcasing how to inject a user defined control into the existing scaffolding of `DefaultApp` using a multiplication filter. This is in many ways similar to the previous example. diff --git a/doc/versions.md b/doc/versions.md index 03d5b8f..5983d73 100644 --- a/doc/versions.md +++ b/doc/versions.md @@ -2,6 +2,7 @@ | Version | Description | |:--------:|:------------------------------------------------------------------------------------------------------------| +| 0.0.12 | Build system simplifications, binary image-reader in custom\_0, a simple POC for histograms in views | | 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 | diff --git a/examples/custom_0/CMakeLists.txt b/examples/custom_0/CMakeLists.txt index 8062b27..e6446dc 100644 --- a/examples/custom_0/CMakeLists.txt +++ b/examples/custom_0/CMakeLists.txt @@ -6,16 +6,4 @@ set(CUSTOM_0_NAME "${PROJECT_NAME}_custom_app") add_executable(${CUSTOM_0_NAME} ${SRC}) target_link_libraries(${CUSTOM_0_NAME} - PRIVATE pixelariumimagelib - PRIVATE pixelariumutilslib - PRIVATE pixelariumresourcelib - PRIVATE pixelariumapplicationlib) - -target_include_directories(${CUSTOM_0_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}) + PRIVATE pixelarium::lib::application_static) diff --git a/examples/custom_0/custom_0.cpp b/examples/custom_0/custom_0.cpp index 1d5b5b6..5156ed4 100644 --- a/examples/custom_0/custom_0.cpp +++ b/examples/custom_0/custom_0.cpp @@ -8,12 +8,13 @@ #include #include "DefaultApp.hpp" +#include "ILog.hpp" +#include "PixelariumLogger.hpp" +#include "PixelariumMem.hpp" #include "imgui.h" -#include "impl/PixelariumMem.hpp" +#include "implot.h" #include "portable-file-dialogs.h" -#include "resources/resource.hpp" -#include "utilities/ILog.hpp" -#include "utilities/SpdLogger.hpp" +#include "resource.hpp" using namespace pixelarium; using namespace std; @@ -22,17 +23,15 @@ using Pool = resources::ImageResourcePool; // setup a logger #ifdef _WIN32 -unique_ptr logger{ - make_unique(string(getenv("APPDATA")) + "/pixelarium/simple_app.log", "default")}; +auto logger{utils::log::PixelariumLogger("pixellog", string(getenv("APPDATA")) + "/pixelarium/simple_app.log")}; #else -unique_ptr logger{ - make_unique(string(getenv("HOME")) + "/.cache/pixelarium/simple_app.log", "default")}; +auto logger{utils::log::PixelariumLogger("pixellog", string(getenv("HOME")) + "/.cache/pixelarium/simple_app.log")}; #endif // instantiate an image pool for the application resources::ImageResourcePool image_pool; -constexpr auto ToCVPixelType(size_t depth, size_t chans) -> int +constexpr auto ToCVPixelType(size_t depth, size_t chans) { int tp{}; switch (depth) @@ -49,7 +48,7 @@ constexpr auto ToCVPixelType(size_t depth, size_t chans) -> int if (chans > 1) { - return CV_MAKETYPE(tp, chans); + return static_cast(CV_MAKETYPE(tp, chans)); } return tp; @@ -68,7 +67,6 @@ class BinaryReader vector buffer{}; uintmax_t file_size; - // struct __attribute__((packed)) ParsedImage // gcc and clang only #pragma pack(push, 1) struct ParsedImage { @@ -97,7 +95,7 @@ class BinaryReader // 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)); + image_pool.SetResource(make_unique(mat, name.c_str(), logger)); } auto ReadFile(const filesystem::path& file, const StatusReport& report) -> ParsedImage @@ -107,6 +105,7 @@ class BinaryReader uint16_t width{}; uint16_t height{}; uint64_t pixel_count{}; + if (!filesystem::exists(file)) return {}; auto sz = filesystem::file_size(file); @@ -128,7 +127,6 @@ class BinaryReader buffer.clear(); } - // buffer = static_cast(malloc(sz)); ifstream inp_stream(file, ios::binary); if (inp_stream) { @@ -137,7 +135,7 @@ class BinaryReader 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)); + logger.Info(format("{}(): Pixel count {}", __FUNCTION__, pixel_count)); if (pixel_count <= sz - header_size) { @@ -146,8 +144,8 @@ class BinaryReader } } - logger->Info(format("{}: Parsed image with width: {}, height: {}, depth: {}, channels: {}", __PRETTY_FUNCTION__, - width, height, depth, channels)); + 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)); @@ -169,7 +167,7 @@ class BinaryReader if (filesystem::exists(bin_file)) { file_size = filesystem::file_size(bin_file); - Text("File: %s (%lu)", bin_file.filename().c_str(), file_size); + Text("File: %s (%ju)", bin_file.filename().c_str(), file_size); if (Button("Parse File")) { auto buff = ReadFile(bin_file, report); @@ -187,23 +185,25 @@ class MyApp : public application::DefaultApp { private: BinaryReader bin_read; + bool plot_demop_{false}; public: MyApp(const Log& log, Pool& pool) : application::DefaultApp(log, pool) {} + ~MyApp() {} // override some of the defaults member functions void Run() override; - void MenuBarOptionsColumn1() override {}; + void MenuBarOptionsColumn1() override; void MenuBarOptionsColumn2() override {}; }; int main() { // some initial log message - logger->Info(std::format("{}: Starting Application {}", __FUNCTION__, "Pixelarium")); + logger.Info(std::format("{}: Starting Application {}", __FUNCTION__, "Pixelarium")); // create a custom application, inject its dependencies and start it - auto app{MyApp(*logger, image_pool)}; + auto app{MyApp(logger, image_pool)}; app.Start(); } @@ -216,4 +216,11 @@ void MyApp::Run() StatusReport{.report_status = [this](const std::string& msg) { this->SetStatusTimed(msg, 5); }, .reset_status = [this]() { this->ResetStatus(); }}; bin_read.Present(reporter); + + if (plot_demop_) + { + ImPlot::ShowDemoWindow(&plot_demop_); + } } + +void MyApp::MenuBarOptionsColumn1() { ImGui::MenuItem("Show Plotdemos", NULL, &this->plot_demop_); } diff --git a/examples/custom_1/CMakeLists.txt b/examples/custom_1/CMakeLists.txt index 9f8643d..0570921 100644 --- a/examples/custom_1/CMakeLists.txt +++ b/examples/custom_1/CMakeLists.txt @@ -6,16 +6,4 @@ set(CUSTOM_1_NAME "${PROJECT_NAME}_custom1_app") add_executable(${CUSTOM_1_NAME} ${SRC}) target_link_libraries(${CUSTOM_1_NAME} - PRIVATE pixelariumimagelib - 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 index 5912324..40e9d8c 100644 --- a/examples/custom_1/custom_1.cpp +++ b/examples/custom_1/custom_1.cpp @@ -8,12 +8,12 @@ #include #include "DefaultApp.hpp" +#include "ILog.hpp" #include "IPixelariumImage.hpp" +#include "PixelariumMem.hpp" +#include "SpdLogger.hpp" #include "imgui.h" -#include "impl/PixelariumMem.hpp" -#include "resources/resource.hpp" -#include "utilities/ILog.hpp" -#include "utilities/SpdLogger.hpp" +#include "resource.hpp" using namespace pixelarium; using namespace std; diff --git a/examples/simple/CMakeLists.txt b/examples/simple/CMakeLists.txt index 12962c5..236b934 100644 --- a/examples/simple/CMakeLists.txt +++ b/examples/simple/CMakeLists.txt @@ -6,16 +6,5 @@ set(SIMPLE_NAME "${PROJECT_NAME}_SIMPLE") add_executable(${SIMPLE_NAME} ${SRC}) target_link_libraries(${SIMPLE_NAME} - PRIVATE pixelariumimagelib - PRIVATE pixelariumutilslib - PRIVATE pixelariumresourcelib PRIVATE pixelariumapplicationlib) -target_include_directories(${SIMPLE_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/simple/simple.cpp b/examples/simple/simple.cpp index 13df2b7..dac0a6c 100644 --- a/examples/simple/simple.cpp +++ b/examples/simple/simple.cpp @@ -1,9 +1,9 @@ #include #include "DefaultApp.hpp" -#include "resources/resource.hpp" -#include "utilities/ILog.hpp" -#include "utilities/SpdLogger.hpp" +#include "ILog.hpp" +#include "SpdLogger.hpp" +#include "resource.hpp" using namespace pixelarium; using namespace std; @@ -12,23 +12,22 @@ using Pool = resources::ImageResourcePool; // setup a logger #ifdef _WIN32 -unique_ptr logger{ - make_unique(string(getenv("APPDATA")) + "/pixelarium/simple_app.log", "default")}; +const auto logger{utils::log::SpdLogger(string(getenv("APPDATA")) + "/pixelarium/simple_app.log", "default")}; #else -unique_ptr logger{ - make_unique(string(getenv("HOME")) + "/.cache/pixelarium/simple_app.log", "default")}; +const auto logger{ + utils::log::SpdLogger(string(getenv("HOME")) + "/.cache/pixelarium/simple_app.log", "default")}; #endif int main() { // some initial log message and default log level setting - logger->Info(format("{}: Starting Application {}", __FUNCTION__, "Pixelarium")); - logger->ChangeLevel(utils::log::LogLevel::kDebug); + logger.Info(format("{}: Starting Application {}", __FUNCTION__, "Pixelarium")); + logger.ChangeLevel(utils::log::LogLevel::kDebug); // instantiate an image pool for the application resources::ImageResourcePool image_pool; // create an application, inject its dependencies and start it - auto app{application::DefaultApp(*logger, image_pool)}; + auto app{application::DefaultApp(logger, image_pool)}; app.Start(); } diff --git a/lib/app/AppGLFW.cpp b/lib/app/AppGLFW.cpp index 1a9b111..39bae95 100644 --- a/lib/app/AppGLFW.cpp +++ b/lib/app/AppGLFW.cpp @@ -5,7 +5,7 @@ #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" #include "imgui_internal.h" -#include "utilities/simple_thread_pool.hpp" +#include "simple_thread_pool.hpp" // see https://github.com/ocornut/imgui/issues/3518 bool PixelBeginStatusBar() @@ -214,20 +214,7 @@ void pixelarium::application::AppGLFW::MenuBar() // main menu if (ImGui::BeginMenu(MAINMENUNAME)) { - if (ImGui::BeginCombo(LOGLEVELSELECT, LOGLEVELS[log_level_].data())) - { - for (int n = 0; n < static_cast(LOGLEVELS.size()); n++) - { - bool is_selected = (LOGLEVELS[log_level_] == LOGLEVELS[n]); - if (ImGui::Selectable(LOGLEVELS[n].data(), is_selected)) - { - log_level_ = n; - this->logger_.ChangeLevel(static_cast(1 << log_level_)); - } - if (is_selected) ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } + LogLevelSelect(); // consumer main menu bar entries this->MenuBarOptionsColumn1(); @@ -279,7 +266,7 @@ void pixelarium::application::AppGLFW::LogLevelSelect() void pixelarium::application::AppGLFW::SetStatusTimed(const std::string& status, size_t seconds) { SetStatus(status); - utils::simple_thread_pool::run_asynch( + utils::pixelarium_pool::enqueue( [this, seconds]() { std::this_thread::sleep_for(std::chrono::seconds(seconds)); diff --git a/lib/app/CMakeLists.txt b/lib/app/CMakeLists.txt index 8280012..02fc2e6 100644 --- a/lib/app/CMakeLists.txt +++ b/lib/app/CMakeLists.txt @@ -1,45 +1,65 @@ +# Fetch implot +include(implot) +message(STATUS "IMPLOT sources at ${IMPLOT_DIR}") + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/app_resources_default.h.in ${CMAKE_BINARY_DIR}/app_resources_default.h @ONLY) +set(IMPLOTSRC + "${IMPLOT_DIR}/implot.h" + "${IMPLOT_DIR}/implot_internal.h" + "${IMPLOT_DIR}/implot_items.cpp" + "${IMPLOT_DIR}/implot.cpp" + "${IMPLOT_DIR}/implot_demo.cpp" +) + +set(IMGUISRC + "${imgui_DIR}/imgui.cpp" + "${imgui_DIR}/misc/cpp/imgui_stdlib.cpp" + "${imgui_DIR}/imgui_demo.cpp" + "${imgui_DIR}/imgui_draw.cpp" + "${imgui_DIR}/imgui_tables.cpp" + "${imgui_DIR}/imgui_widgets.cpp" + "${imgui_DIR}/backends/imgui_impl_opengl3.cpp" + "${imgui_DIR}/backends/imgui_impl_glfw.cpp") + set(APPLIBSRC - AppGLFW.hpp + include/imgui_proxy.hpp + include/AppGLFW.hpp + include/DefaultApp.hpp + include/PixelariumGallery.hpp AppGLFW.cpp - DefaultApp.hpp DefaultApp.cpp - PixelariumGallery.hpp - PixelariumGallery.cpp - ${imgui_DIR}/imgui.cpp - ${imgui_DIR}/imgui_demo.cpp - ${imgui_DIR}/imgui_draw.cpp - ${imgui_DIR}/imgui_tables.cpp - ${imgui_DIR}/imgui_widgets.cpp - ${imgui_DIR}/backends/imgui_impl_opengl3.cpp - ${imgui_DIR}/backends/imgui_impl_glfw.cpp) + PixelariumGallery.cpp) set(RENDERSRC - rendering/RenderHelpers.hpp + rendering/include/RenderHelpers.hpp + rendering/include/RenderImageManager.hpp + rendering/include/CvMatRender.hpp + rendering/include/IPixelariumImageView.hpp + rendering/include/PixelariumImageViewDefault.hpp + rendering/include/PixelariumImageViewCzi.hpp + rendering/include/ImageViewFactory.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/RenderImageManager.cpp rendering/PixelariumImageViewDefault.cpp - rendering/PixelariumImageViewCzi.hpp rendering/PixelariumImageViewCzi.cpp - rendering/ImageViewFactory.hpp rendering/ImageViewFactory.cpp) set(APPLIBNAME pixelariumapplicationlib) add_library(${APPLIBNAME} - STATIC ${APPLIBSRC} ${RENDERSRC}) + STATIC ${APPLIBSRC} ${IMGUISRC} ${IMPLOTSRC} ${RENDERSRC}) + +add_library(pixelarium::lib::application_static ALIAS ${APPLIBNAME}) target_link_libraries(${APPLIBNAME} - PRIVATE pixelariumutilslib - PRIVATE pixelariumimagelib) + PUBLIC + pixelarium::lib::utilities_static + pixelarium::lib::imaging_static + pixelarium::lib::resources_static) # This needs to be public to let the consumer know about it. if(WIN32) @@ -59,10 +79,12 @@ if(APPLE) endif() target_include_directories(${APPLIBNAME} + INTERFACE PRIVATE ${CMAKE_BINARY_DIR} - PRIVATE ${PROJECT_SOURCE_DIR}/lib - PRIVATE ${PROJECT_SOURCE_DIR}/lib/imaging - PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/rendering + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/rendering/include + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include PUBLIC ${pfd_DIR} PUBLIC ${imgui_DIR} - PUBLIC ${imgui_DIR}/backends) + PUBLIC ${imgui_DIR}/misc/cpp + PUBLIC ${imgui_DIR}/backends + ${IMPLOT_DIR}) diff --git a/lib/app/DefaultApp.cpp b/lib/app/DefaultApp.cpp index f04648a..caee6c1 100644 --- a/lib/app/DefaultApp.cpp +++ b/lib/app/DefaultApp.cpp @@ -3,12 +3,12 @@ #include #include +#include "ILog.hpp" #include "PixelariumImageFactory.hpp" #include "app_resources_default.h" #include "imgui.h" #include "portable-file-dialogs.h" -#include "resources/resource.hpp" -#include "utilities/ILog.hpp" +#include "resource.hpp" using namespace pixelarium::imaging; using namespace pixelarium::application; diff --git a/lib/app/AppGLFW.hpp b/lib/app/include/AppGLFW.hpp similarity index 81% rename from lib/app/AppGLFW.hpp rename to lib/app/include/AppGLFW.hpp index bf8bb84..a2d07c2 100644 --- a/lib/app/AppGLFW.hpp +++ b/lib/app/include/AppGLFW.hpp @@ -4,7 +4,8 @@ #include -#include "utilities/ILog.hpp" +#include "ILog.hpp" +#include "imgui_proxy.hpp" namespace pixelarium::application { @@ -13,7 +14,18 @@ namespace pixelarium::application class AppGLFW { public: - explicit AppGLFW(const utils::log::ILog& log) : logger_(log) { this->InitMainWindow(); } + explicit AppGLFW(const utils::log::ILog& log) : logger_(log), plot_context_(ImPlot::CreateContext()) + { + this->InitMainWindow(); + } + + ~AppGLFW() { ImPlot::DestroyContext(plot_context_); } + + AppGLFW(AppGLFW&) = delete; + AppGLFW(const AppGLFW&) = delete; + AppGLFW(AppGLFW&&) = delete; + AppGLFW& operator=(AppGLFW&) = delete; + AppGLFW& operator=(AppGLFW&&) = delete; /// @brief Start the main render loop void Start() { this->RunLoop(); } @@ -61,6 +73,7 @@ class AppGLFW void LogLevelSelect(); int log_level_{0}; GLFWwindow* window = nullptr; + ImPlotContext* plot_context_ = nullptr; bool show_status_{false}; std::string status_message_{}; }; diff --git a/lib/app/DefaultApp.hpp b/lib/app/include/DefaultApp.hpp similarity index 89% rename from lib/app/DefaultApp.hpp rename to lib/app/include/DefaultApp.hpp index 0ba9d60..41c96a1 100644 --- a/lib/app/DefaultApp.hpp +++ b/lib/app/include/DefaultApp.hpp @@ -3,10 +3,10 @@ #include #include "AppGLFW.hpp" -#include "app/PixelariumGallery.hpp" -#include "imgui.h" -#include "resources/resource.hpp" -#include "utilities/ILog.hpp" +#include "ILog.hpp" +#include "PixelariumGallery.hpp" +#include "imgui_proxy.hpp" +#include "resource.hpp" namespace pixelarium::application { diff --git a/lib/app/PixelariumGallery.hpp b/lib/app/include/PixelariumGallery.hpp similarity index 94% rename from lib/app/PixelariumGallery.hpp rename to lib/app/include/PixelariumGallery.hpp index e6eaff4..87fb2d2 100644 --- a/lib/app/PixelariumGallery.hpp +++ b/lib/app/include/PixelariumGallery.hpp @@ -1,8 +1,8 @@ #pragma once -#include "rendering/RenderImageManager.hpp" -#include "resources/resource.hpp" -#include "utilities/ILog.hpp" +#include "ILog.hpp" +#include "RenderImageManager.hpp" +#include "resource.hpp" namespace pixelarium::application { diff --git a/lib/app/include/imgui_proxy.hpp b/lib/app/include/imgui_proxy.hpp new file mode 100644 index 0000000..10640c2 --- /dev/null +++ b/lib/app/include/imgui_proxy.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include "imgui.h" +#include "imgui_stdlib.h" +#include "implot.h" diff --git a/lib/app/rendering/CvMatRender.cpp b/lib/app/rendering/CvMatRender.cpp index efcb9ef..2514d1c 100644 --- a/lib/app/rendering/CvMatRender.cpp +++ b/lib/app/rendering/CvMatRender.cpp @@ -3,7 +3,7 @@ #include #include -#include "imaging/IPixelariumImage.hpp" +#include "IPixelariumImage.hpp" using namespace pixelarium::imaging; diff --git a/lib/app/rendering/IPixelariumImageView.cpp b/lib/app/rendering/IPixelariumImageView.cpp index 4f93676..ecffeaf 100644 --- a/lib/app/rendering/IPixelariumImageView.cpp +++ b/lib/app/rendering/IPixelariumImageView.cpp @@ -3,22 +3,30 @@ #include #include "app_resources_default.h" +#include "imgui.h" #include "portable-file-dialogs.h" auto pixelarium::application::IPixelariumImageView::ImageViewMenuBar() -> void { if (ImGui::BeginMenuBar()) { - if (ImGui::MenuItem(SAVEAS)) + if (ImGui::BeginMenu("File")) { - auto dest = pfd::save_file("Save File", ".", {"Image Files", "*.png *.jpg *.jpeg *.tiff"}, - pfd::opt::force_overwrite) - .result(); - if (!dest.empty()) + if (ImGui::MenuItem(SAVEAS)) { - // this->img_->SaveImage(dest); - cv::imwrite(dest, cached_image_); + 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_); + } } + + ImageViewMenuBarAdditions(); + + ImGui::EndMenu(); } ImGui::EndMenuBar(); diff --git a/lib/app/rendering/ImageViewFactory.cpp b/lib/app/rendering/ImageViewFactory.cpp index cec9a3a..c2e3b46 100644 --- a/lib/app/rendering/ImageViewFactory.cpp +++ b/lib/app/rendering/ImageViewFactory.cpp @@ -3,11 +3,11 @@ #include #include +#include "IPixelariumImage.hpp" #include "IPixelariumImageView.hpp" +#include "PixelariumImageFactory.hpp" #include "PixelariumImageViewCzi.hpp" #include "PixelariumImageViewDefault.hpp" -#include "imaging/IPixelariumImage.hpp" -#include "imaging/PixelariumImageFactory.hpp" /// @brief Creates a PixelariumImageView from a resource image. /// @param image_id The ID of the image resource to render. @@ -19,7 +19,7 @@ std::unique_ptr pixelarium::appli using ImageType = imaging::ImageFileType; auto res{this->image_pool_.GetResource(image_id)}; - auto img{res.lock()}; + const auto img{res.lock()}; if (img == nullptr) { @@ -48,11 +48,26 @@ std::unique_ptr pixelarium::appli 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); + try + { + auto view{std::make_unique(img)}; + return view; + } + catch (const std::exception& ex) + { + log_.Error(std::format("{}: Creating view failed: {}", __PRETTY_FUNCTION__, ex.what())); + } 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_); + try + { + auto view{std::make_unique(img, log_)}; + return view; + } + catch (const std::exception& ex) + { + log_.Error(std::format("{}: Creating view failed: {}", __PRETTY_FUNCTION__, ex.what())); + } default: return {}; } diff --git a/lib/app/rendering/PixelariumImageViewCzi.cpp b/lib/app/rendering/PixelariumImageViewCzi.cpp index aef35bb..40ecc3c 100644 --- a/lib/app/rendering/PixelariumImageViewCzi.cpp +++ b/lib/app/rendering/PixelariumImageViewCzi.cpp @@ -2,11 +2,12 @@ #include #include +#include #include "CvMatRender.hpp" +#include "IPixelariumImage.hpp" +#include "PixelariumCzi.hpp" #include "RenderHelpers.hpp" -#include "imaging/IPixelariumImage.hpp" -#include "imaging/impl/PixelariumCzi.hpp" #include "imgui.h" void pixelarium::application::PixelariumImageViewCzi::RefreshCachedImage() @@ -26,7 +27,7 @@ void pixelarium::application::PixelariumImageViewCzi::RefreshCachedImage() } pixelarium::application::PixelariumImageViewCzi::PixelariumImageViewCzi(std::shared_ptr img, const Log& log) - : log_(log), render_(std::make_unique(*img->TryGetImage())) + : log_(log) { img_ = img; auto czi_img = std::static_pointer_cast(this->img_); @@ -39,6 +40,18 @@ pixelarium::application::PixelariumImageViewCzi::PixelariumImageViewCzi(std::sha return true; }); + auto render_mat = img->TryGetImage(); + if (render_mat.has_value()) + { + this->render_ = std::make_unique(render_mat.value()); + } + else + { + auto msg{std::format("{}: fetching image failed: {}", __PRETTY_FUNCTION__, img->Name())}; + log_.Error(msg); + throw std::runtime_error(msg); + } + // this->SetInitialSize(); log_.Info(std::format("{}: dimension map size: {}", __PRETTY_FUNCTION__, dimension_map_.size())); } diff --git a/lib/app/rendering/PixelariumImageViewDefault.cpp b/lib/app/rendering/PixelariumImageViewDefault.cpp index 903d5fc..98c60ca 100644 --- a/lib/app/rendering/PixelariumImageViewDefault.cpp +++ b/lib/app/rendering/PixelariumImageViewDefault.cpp @@ -1,11 +1,22 @@ #include "PixelariumImageViewDefault.hpp" -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "IPixelariumImage.hpp" #include "RenderHelpers.hpp" #include "app_resources_default.h" -#include "imaging/IPixelariumImage.hpp" #include "imgui.h" +#include "implot.h" +#include "simple_thread_pool.hpp" void pixelarium::application::PixelariumImageViewDefault::RefreshCachedImage() { @@ -16,6 +27,11 @@ void pixelarium::application::PixelariumImageViewDefault::RefreshCachedImage() } } +void pixelarium::application::PixelariumImageViewDefault::ImageViewMenuBarAdditions() +{ + ImGui::MenuItem("Histogram", NULL, &this->show_hists_); +} + /// @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 @@ -40,6 +56,7 @@ void pixelarium::application::PixelariumImageViewDefault::ShowImage() const auto cached_heigth{cached_image_.rows}; const auto ratio{static_cast(cached_heigth) / cached_width}; SetInitialSize(kInitialWindowWidth, (kInitialWindowWidth * ratio + 100)); + utils::pixelarium_pool::enqueue([this]() { GenerateHistogram(); }); } ImGui::Begin(this->img_->Name().c_str(), &this->open_p, @@ -62,8 +79,67 @@ void pixelarium::application::PixelariumImageViewDefault::ShowImage() aspect_const_dimensions(dim, new_dim)); ImGui::Separator(); + + if (show_hists_ && hist_available_) + { + if (ImPlot::BeginPlot("Histogram")) + { + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit); + using style_pair = std::pair; + + constexpr std::array names = { + {{"Blue", ImVec4(0, 0, 1, 1)}, {"Green", ImVec4(0, 1, 0, 1)}, {"Red", ImVec4(1, 0, 0, 1)}}}; + // for (auto& e : hist_planes_) + // { + // ImPlot::PlotHistogram(name.c_str(), e.data, e.rows * e.cols, 16); + // for (auto& e : name) + // { + // e++; + // } + // } + for (size_t i{0}; i < hist_planes_.size(); ++i) + { + if (hist_planes_.at(i).type() == CV_32F) + { + // ImPlot::PlotHistogram(names.at(i % 3), reinterpret_cast(hist_planes_[i].data), + // hist_planes_[i].rows * hist_planes_[i].cols, 16, 1.0, ImPlotRange(), + // ImPlotHistogramFlags_Horizontal); + ImPlot::PushStyleColor(ImPlotCol_Line, names.at(i % 3).second); + ImPlot::PlotLine(std::format("{}-line", names.at(i % 3).first).c_str(), + reinterpret_cast(hist_planes_[i].data), + hist_planes_[i].rows * hist_planes_[i].cols); + ImPlot::PopStyleColor(); + } + } + + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square, 6, ImPlot::GetColormapColor(1), IMPLOT_AUTO, + ImPlot::GetColormapColor(1)); + + ImPlot::EndPlot(); + } + } + ImGui::Text("%s", std::format(" Raw Dimensions W : {}, H: {}", dim.x, dim.y).c_str()); ImGui::Text("%s", std::format("Render Dimensions W : {}, H: {}", curr_dim_.x, curr_dim_.y).c_str()); ImGui::End(); } + +auto pixelarium::application::PixelariumImageViewDefault::GenerateHistogram() -> void +{ + cv::split(cached_image_, bgr_planes_); + hist_planes_.resize(bgr_planes_.size()); + + int histSize = 256; + float range[] = {0, 256}; // the upper boundary is exclusive + const float* histRange[] = {range}; + + for (auto [bgr, hist] : std::ranges::views::zip(bgr_planes_, hist_planes_)) + { + cv::calcHist(&bgr, 1, 0, cv::Mat(), hist, 1, &histSize, histRange, true, false); + // cv::normalize(hist, hist); + } + + hist_available_ = true; +} diff --git a/lib/app/rendering/RenderImageManager.cpp b/lib/app/rendering/RenderImageManager.cpp index 03d76a7..bf479a8 100644 --- a/lib/app/rendering/RenderImageManager.cpp +++ b/lib/app/rendering/RenderImageManager.cpp @@ -46,9 +46,10 @@ bool pixelarium::application::RenderImageManager::Remove(resources::ResourceKey /// @return void. No exception is thrown. void pixelarium::application::RenderImageManager::Add(resources::ResourceKey key) noexcept { - // we don't want to add what's already there - // or empty render images - if (this->render_image_map_.contains(key)) + // we don't want to add what's already there, render empty images, or failed keys + if (this->render_image_map_.contains(key) + // || this->failed_keys_cache_.contains(key) + ) { return; } @@ -56,6 +57,8 @@ void pixelarium::application::RenderImageManager::Add(resources::ResourceKey key auto current_view = this->view_factory_->RenderImage(key); if (current_view == nullptr) { + // failed to create view, cache this key to avoid repeated attempts + this->failed_keys_cache_.insert(key); return; } diff --git a/lib/app/rendering/CvMatRender.hpp b/lib/app/rendering/include/CvMatRender.hpp similarity index 100% rename from lib/app/rendering/CvMatRender.hpp rename to lib/app/rendering/include/CvMatRender.hpp diff --git a/lib/app/rendering/IPixelariumImageView.hpp b/lib/app/rendering/include/IPixelariumImageView.hpp similarity index 93% rename from lib/app/rendering/IPixelariumImageView.hpp rename to lib/app/rendering/include/IPixelariumImageView.hpp index e92ca14..347109a 100644 --- a/lib/app/rendering/IPixelariumImageView.hpp +++ b/lib/app/rendering/include/IPixelariumImageView.hpp @@ -2,7 +2,7 @@ #include -#include "imaging/IPixelariumImage.hpp" +#include "IPixelariumImage.hpp" #include "imgui.h" namespace pixelarium::application @@ -29,6 +29,7 @@ class IPixelariumImageView protected: virtual void ImageViewMenuBar(); + virtual void ImageViewMenuBarAdditions() {}; protected: std::shared_ptr img_{}; diff --git a/lib/app/rendering/ImageViewFactory.hpp b/lib/app/rendering/include/ImageViewFactory.hpp similarity index 90% rename from lib/app/rendering/ImageViewFactory.hpp rename to lib/app/rendering/include/ImageViewFactory.hpp index 77b5cdf..ae340b4 100644 --- a/lib/app/rendering/ImageViewFactory.hpp +++ b/lib/app/rendering/include/ImageViewFactory.hpp @@ -1,8 +1,8 @@ #pragma once +#include "ILog.hpp" #include "IPixelariumImageView.hpp" -#include "resources/resource.hpp" -#include "utilities/ILog.hpp" +#include "resource.hpp" namespace pixelarium::application { diff --git a/lib/app/rendering/PixelariumImageViewCzi.hpp b/lib/app/rendering/include/PixelariumImageViewCzi.hpp similarity index 94% rename from lib/app/rendering/PixelariumImageViewCzi.hpp rename to lib/app/rendering/include/PixelariumImageViewCzi.hpp index 6f9b058..0b47f52 100644 --- a/lib/app/rendering/PixelariumImageViewCzi.hpp +++ b/lib/app/rendering/include/PixelariumImageViewCzi.hpp @@ -4,11 +4,11 @@ #include #include "CvMatRender.hpp" +#include "ILog.hpp" +#include "IPixelariumImage.hpp" #include "IPixelariumImageView.hpp" -#include "imaging/IPixelariumImage.hpp" #include "imgui.h" #include "libCZI_DimCoordinate.h" -#include "utilities/ILog.hpp" namespace pixelarium::application { diff --git a/lib/app/rendering/PixelariumImageViewDefault.hpp b/lib/app/rendering/include/PixelariumImageViewDefault.hpp similarity index 77% rename from lib/app/rendering/PixelariumImageViewDefault.hpp rename to lib/app/rendering/include/PixelariumImageViewDefault.hpp index 0f784d8..4bc76b5 100644 --- a/lib/app/rendering/PixelariumImageViewDefault.hpp +++ b/lib/app/rendering/include/PixelariumImageViewDefault.hpp @@ -1,10 +1,11 @@ #pragma once #include +#include #include "CvMatRender.hpp" +#include "IPixelariumImage.hpp" #include "IPixelariumImageView.hpp" -#include "imaging/IPixelariumImage.hpp" #include "imgui.h" namespace pixelarium::application @@ -16,11 +17,8 @@ class PixelariumImageViewDefault : public IPixelariumImageView using Image = imaging::IPixelariumImageCvMat; public: - explicit PixelariumImageViewDefault(std::shared_ptr img) : render_(*img->TryGetImage()) - { - img_ = img; - // this->SetInitialSize(); - } + explicit PixelariumImageViewDefault(std::shared_ptr img) : render_(*img->TryGetImage()) { img_ = img; } + PixelariumImageViewDefault() = delete; PixelariumImageViewDefault(PixelariumImageViewDefault&) = delete; PixelariumImageViewDefault(const PixelariumImageViewDefault&) = delete; @@ -30,9 +28,17 @@ class PixelariumImageViewDefault : public IPixelariumImageView void ShowImage() override; + void ImageViewMenuBarAdditions() override; + + void GenerateHistogram(); + private: ImVec2 curr_dim_{}; CvMatRender render_; + bool show_hists_{false}; + bool hist_available_{false}; + std::vector bgr_planes_; + std::vector hist_planes_; private: void RefreshCachedImage(); diff --git a/lib/app/rendering/RenderHelpers.hpp b/lib/app/rendering/include/RenderHelpers.hpp similarity index 100% rename from lib/app/rendering/RenderHelpers.hpp rename to lib/app/rendering/include/RenderHelpers.hpp diff --git a/lib/app/rendering/RenderImageManager.hpp b/lib/app/rendering/include/RenderImageManager.hpp similarity index 94% rename from lib/app/rendering/RenderImageManager.hpp rename to lib/app/rendering/include/RenderImageManager.hpp index bd1f4e9..91feccb 100644 --- a/lib/app/rendering/RenderImageManager.hpp +++ b/lib/app/rendering/include/RenderImageManager.hpp @@ -4,10 +4,10 @@ #include #include +#include "ILog.hpp" #include "IPixelariumImageView.hpp" #include "ImageViewFactory.hpp" -#include "resources/resource.hpp" -#include "utilities/ILog.hpp" +#include "resource.hpp" // This is intended as an additional abstraction // aggregating views that should be rendered (or not) @@ -68,6 +68,7 @@ class RenderImageManager std::unique_ptr view_factory_; std::mutex mut_; std::unordered_set keys_to_delete_; + std::unordered_set failed_keys_cache_; const utils::log::ILog& log_; }; diff --git a/lib/imaging/CMakeLists.txt b/lib/imaging/CMakeLists.txt index 04d23a4..23b5c74 100644 --- a/lib/imaging/CMakeLists.txt +++ b/lib/imaging/CMakeLists.txt @@ -1,4 +1,4 @@ -include(${CMAKE_SOURCE_DIR}/cmake/libCZI.cmake) +include(libCZI) find_package(OpenCV REQUIRED) @@ -6,19 +6,19 @@ message(STATUS "Found opencv: " ${OpenCV_INCLUDE_DIRS}) message(STATUS "OpenCV_LIBs from: " ${OpenCV_LIBS}) set(IMAGELIBSRC - IPixelariumImage.hpp + include/IPixelariumImage.hpp + include/PixelariumImageFactory.hpp + impl/include/PixelariumJpg.hpp + impl/include/PixelariumPng.hpp + impl/include/PixelariumCzi.hpp + impl/include/PixelariumTiff.hpp + impl/include/PixelariumMem.hpp IPixelariumImage.cpp - PixelariumImageFactory.hpp PixelariumImageFactory.cpp - impl/PixelariumJpg.hpp impl/PixelariumJpg.cpp - impl/PixelariumPng.hpp impl/PixelariumPng.cpp - impl/PixelariumCzi.hpp impl/PixelariumCzi.cpp - impl/PixelariumTiff.hpp impl/PixelariumTiff.cpp - impl/PixelariumMem.hpp impl/PixelariumMem.cpp ) @@ -27,15 +27,19 @@ set(IMAGELIBLIBNAME pixelariumimagelib) add_library(${IMAGELIBLIBNAME} STATIC ${IMAGELIBSRC}) +add_library(pixelarium::lib::imaging_static ALIAS ${IMAGELIBLIBNAME}) + target_compile_definitions(${IMAGELIBLIBNAME} PUBLIC _LIBCZISTATICLIB) target_link_libraries(${IMAGELIBLIBNAME} PUBLIC ${OpenCV_LIBS} - PUBLIC pixelariumutilslib + PUBLIC pixelarium::lib::utilities_static PRIVATE libCZIStatic) target_include_directories(${IMAGELIBLIBNAME} - PRIVATE ${CMAKE_SOURCE_DIR}/lib + INTERFACE + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/impl/include PUBLIC ${OpenCV_INCLUDE_DIRS} PUBLIC ${LIBCZI_INCLUDE_DIR}) diff --git a/lib/imaging/PixelariumImageFactory.cpp b/lib/imaging/PixelariumImageFactory.cpp index a001ddb..e12d769 100644 --- a/lib/imaging/PixelariumImageFactory.cpp +++ b/lib/imaging/PixelariumImageFactory.cpp @@ -3,12 +3,12 @@ #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" +#include "IPixelariumImage.hpp" +#include "PixelariumCzi.hpp" +#include "PixelariumJpg.hpp" +#include "PixelariumMem.hpp" +#include "PixelariumPng.hpp" +#include "PixelariumTiff.hpp" /*static*/ std::unique_ptr pixelarium::imaging::PixelariumImageFactory::CreateImage(const std::string& uri, const Log& log) diff --git a/lib/imaging/impl/PixelariumCzi.cpp b/lib/imaging/impl/PixelariumCzi.cpp index dcf87e1..12ce45d 100644 --- a/lib/imaging/impl/PixelariumCzi.cpp +++ b/lib/imaging/impl/PixelariumCzi.cpp @@ -7,11 +7,13 @@ #include #include #include -#include +#include "ILog.hpp" #include "libCZI.h" -#include "utilities/ILog.hpp" +#include "libCZI_Pixels.h" +namespace +{ bool comp_blockinfo_params(const pixelarium::imaging::CziParams& params, const libCZI::SubBlockInfo& info) { bool res{true}; @@ -50,94 +52,62 @@ constexpr int try_get_index_match(const pixelarium::imaging::CziParams& params, return index; } +} // namespace + std::optional 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; + const auto cv_pixel = GetCVPixelTypeAndSize(pixeltype); - 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; - default: - pixel_size = -1; - break; - } - - if (pixel_size < 0) return std::nullopt; + // ToDo(MAK): fix this makeshift pixel-type-catch + if (cv_pixel.size < 0 || cv_pixel.size > 4) 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)); + static_cast(pixeltype), cv_pixel.type, cv_pixel.size)); size_t height{bitmap->GetHeight()}; size_t width{bitmap->GetWidth()}; - auto fill_mat = cv::Mat(height, width, target_type); + auto fill_mat = cv::Mat(height, width, cv_pixel.type); - auto bitmap_info = bitmap->Lock(); - - for (size_t h{0}; h < height; ++h) + if (const auto bitmap_info = bitmap->Lock(); bitmap_info.ptrDataRoi) { - unsigned char* source_row = ((unsigned char*)bitmap_info.ptrDataRoi) + bitmap_info.stride * h; - unsigned char* target_row = fill_mat.ptr(h); - - for (size_t w{0}; w < width; ++w) + for (size_t h{0}; h < height; ++h) { - switch (pixel_size) + unsigned char* source_row = ((unsigned char*)bitmap_info.ptrDataRoi) + bitmap_info.stride * h; + unsigned char* target_row = fill_mat.ptr(h); + + for (size_t w{0}; w < width; ++w) { - case 1: - target_row[w] = source_row[w]; - break; - case 2: - reinterpret_cast(target_row)[w] = reinterpret_cast(source_row)[w]; - break; - case 3: - target_row[3 * w] = source_row[3 * w]; - 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; + switch (cv_pixel.size) + { + case 1: + target_row[w] = source_row[w]; + break; + case 2: + reinterpret_cast(target_row)[w] = + reinterpret_cast(source_row)[w]; + break; + case 3: + target_row[3 * w] = source_row[3 * w]; + 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; + } } } } + // this is outside of the previous if-clause on purpose: + // no matter if we reach the inner scope of this clause, we locked the bitmap, + // so here it has to be unlocked. bitmap->Unlock(); + return fill_mat; } diff --git a/lib/imaging/impl/PixelariumCzi.hpp b/lib/imaging/impl/include/PixelariumCzi.hpp similarity index 70% rename from lib/imaging/impl/PixelariumCzi.hpp rename to lib/imaging/impl/include/PixelariumCzi.hpp index 36ad5fb..6512572 100644 --- a/lib/imaging/impl/PixelariumCzi.hpp +++ b/lib/imaging/impl/include/PixelariumCzi.hpp @@ -3,9 +3,41 @@ #include #include -#include "../IPixelariumImage.hpp" +#include "ILog.hpp" +#include "IPixelariumImage.hpp" #include "libCZI.h" -#include "utilities/ILog.hpp" +#include "libCZI_Pixels.h" + +namespace +{ +struct CvPixelTypeAndSize +{ + int size{-1}; + int type{-1}; +}; + +template +[[nodiscard]] constexpr auto GetCVPixelTypeAndSize(P p) noexcept -> CvPixelTypeAndSize +{ + switch (p) + { + case libCZI::PixelType::Gray8: + return {1, CV_8U}; + case libCZI::PixelType::Gray16: + return {2, CV_16U}; + case libCZI::PixelType::Bgr24: + return {3, CV_8UC3}; + case libCZI::PixelType::Bgra32: + return {4, CV_8UC4}; + case libCZI::PixelType::Gray32: + return {4, CV_32S}; + case libCZI::PixelType::Gray32Float: + return {4, CV_32F}; + default: + return {}; + } +} +} // namespace namespace pixelarium::imaging { diff --git a/lib/imaging/impl/PixelariumJpg.hpp b/lib/imaging/impl/include/PixelariumJpg.hpp similarity index 96% rename from lib/imaging/impl/PixelariumJpg.hpp rename to lib/imaging/impl/include/PixelariumJpg.hpp index 37ef378..43f0fa5 100644 --- a/lib/imaging/impl/PixelariumJpg.hpp +++ b/lib/imaging/impl/include/PixelariumJpg.hpp @@ -3,7 +3,7 @@ #include #include -#include "../IPixelariumImage.hpp" +#include "IPixelariumImage.hpp" namespace pixelarium::imaging { diff --git a/lib/imaging/impl/PixelariumMem.hpp b/lib/imaging/impl/include/PixelariumMem.hpp similarity index 95% rename from lib/imaging/impl/PixelariumMem.hpp rename to lib/imaging/impl/include/PixelariumMem.hpp index 41137d9..c35efa7 100644 --- a/lib/imaging/impl/PixelariumMem.hpp +++ b/lib/imaging/impl/include/PixelariumMem.hpp @@ -3,8 +3,8 @@ #include #include -#include "../IPixelariumImage.hpp" -#include "utilities/ILog.hpp" +#include "ILog.hpp" +#include "IPixelariumImage.hpp" namespace pixelarium::imaging { diff --git a/lib/imaging/impl/PixelariumPng.hpp b/lib/imaging/impl/include/PixelariumPng.hpp similarity index 96% rename from lib/imaging/impl/PixelariumPng.hpp rename to lib/imaging/impl/include/PixelariumPng.hpp index a3cc897..60ad983 100644 --- a/lib/imaging/impl/PixelariumPng.hpp +++ b/lib/imaging/impl/include/PixelariumPng.hpp @@ -3,7 +3,7 @@ #include #include -#include "../IPixelariumImage.hpp" +#include "IPixelariumImage.hpp" namespace pixelarium::imaging { diff --git a/lib/imaging/impl/PixelariumTiff.hpp b/lib/imaging/impl/include/PixelariumTiff.hpp similarity index 94% rename from lib/imaging/impl/PixelariumTiff.hpp rename to lib/imaging/impl/include/PixelariumTiff.hpp index 6d653e0..3c7b404 100644 --- a/lib/imaging/impl/PixelariumTiff.hpp +++ b/lib/imaging/impl/include/PixelariumTiff.hpp @@ -3,8 +3,8 @@ #include #include -#include "../IPixelariumImage.hpp" -#include "utilities/ILog.hpp" +#include "ILog.hpp" +#include "IPixelariumImage.hpp" namespace pixelarium::imaging { diff --git a/lib/imaging/IPixelariumImage.hpp b/lib/imaging/include/IPixelariumImage.hpp similarity index 97% rename from lib/imaging/IPixelariumImage.hpp rename to lib/imaging/include/IPixelariumImage.hpp index c2657b9..81c8bb2 100644 --- a/lib/imaging/IPixelariumImage.hpp +++ b/lib/imaging/include/IPixelariumImage.hpp @@ -93,6 +93,8 @@ class IPixelariumImage std::filesystem::path uri_; }; +/// @brief Interface template specialization of IPixelariumImage +/// using cv::Mat as the wrapped data type. class IPixelariumImageCvMat : public IPixelariumImage { public: diff --git a/lib/imaging/PixelariumImageFactory.hpp b/lib/imaging/include/PixelariumImageFactory.hpp similarity index 97% rename from lib/imaging/PixelariumImageFactory.hpp rename to lib/imaging/include/PixelariumImageFactory.hpp index b098afe..ecf3753 100644 --- a/lib/imaging/PixelariumImageFactory.hpp +++ b/lib/imaging/include/PixelariumImageFactory.hpp @@ -2,8 +2,9 @@ #include +#include "ILog.hpp" #include "IPixelariumImage.hpp" -#include "utilities/ILog.hpp" + namespace pixelarium::imaging { constexpr pixelarium::imaging::ImageFileType ExtensionToType(const std::string& extension) diff --git a/lib/resources/CMakeLists.txt b/lib/resources/CMakeLists.txt index 45d5ad7..0b873d4 100644 --- a/lib/resources/CMakeLists.txt +++ b/lib/resources/CMakeLists.txt @@ -1,14 +1,17 @@ set(RESOURCELIBNAME pixelariumresourcelib) set(RESOURCELIBSRC - resource.hpp + include/resource.hpp resource.cpp) add_library(${RESOURCELIBNAME} STATIC ${RESOURCELIBSRC}) +add_library(pixelarium::lib::resources_static ALIAS ${RESOURCELIBNAME}) + target_link_libraries(${RESOURCELIBNAME} - PRIVATE pixelariumimagelib) + PRIVATE pixelarium::lib::imaging_static) target_include_directories(${RESOURCELIBNAME} - PRIVATE ${CMAKE_SOURCE_DIR}/lib) + INTERFACE + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/lib/resources/resource.hpp b/lib/resources/include/resource.hpp similarity index 98% rename from lib/resources/resource.hpp rename to lib/resources/include/resource.hpp index f72f4b2..1c312c3 100644 --- a/lib/resources/resource.hpp +++ b/lib/resources/include/resource.hpp @@ -6,7 +6,7 @@ #include #include -#include "imaging/IPixelariumImage.hpp" +#include "IPixelariumImage.hpp" namespace pixelarium::resources { diff --git a/lib/resources/resource.cpp b/lib/resources/resource.cpp index d9c6bc6..122c068 100644 --- a/lib/resources/resource.cpp +++ b/lib/resources/resource.cpp @@ -5,7 +5,7 @@ #include #include -#include "imaging/IPixelariumImage.hpp" +#include "IPixelariumImage.hpp" using Image = pixelarium::imaging::IPixelariumImageCvMat; using namespace std; diff --git a/lib/utilities/CMakeLists.txt b/lib/utilities/CMakeLists.txt index 12fc276..0d3a6da 100644 --- a/lib/utilities/CMakeLists.txt +++ b/lib/utilities/CMakeLists.txt @@ -1,14 +1,17 @@ set(UTILSLIBNAME pixelariumutilslib) set(UTILSLIBSRC - ILog.hpp - SpdLogger.hpp + include/ILog.hpp + include/SpdLogger.hpp + include/PixelariumLogger.hpp + include/simple_thread_pool.hpp SpdLogger.cpp - simple_thread_pool.hpp - simple_thread_pool.cpp) + PixelariumLogger.cpp) add_library(${UTILSLIBNAME} STATIC ${UTILSLIBSRC}) +add_library(pixelarium::lib::utilities_static ALIAS ${UTILSLIBNAME}) + # won't work # target_compile_options(${UTILSLIBNAME} # PRIVATE @@ -16,7 +19,9 @@ add_library(${UTILSLIBNAME} STATIC ${UTILSLIBSRC}) # "$<$:/utf-8>") target_include_directories(${UTILSLIBNAME} + INTERFACE + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include PRIVATE ${spdlog_DIR}/include) target_link_libraries(${UTILSLIBNAME} - PRIVATE spdlog::spdlog_header_only) + PUBLIC spdlog::spdlog_header_only) diff --git a/lib/utilities/PixelariumLogger.cpp b/lib/utilities/PixelariumLogger.cpp new file mode 100644 index 0000000..d43b3fd --- /dev/null +++ b/lib/utilities/PixelariumLogger.cpp @@ -0,0 +1,73 @@ +#include "PixelariumLogger.hpp" + +#include +#include +#include +#include +#include + +using namespace pixelarium::utils::log; + +struct PixelariumLogger::LogStream +{ + LogStream(const std::string& sink) + { + out_stream_ = std::ofstream(sink, std::ios::app); + if (!out_stream_) + { + throw std::runtime_error("Failed to open log stream"); + } + } + + ~LogStream() + { + if (out_stream_.is_open()) + { + out_stream_.close(); + } + } + + LogStream& operator<<(const std::string& str) + { + this->out_stream_ << str; + return *this; + } + + private: + std::ofstream out_stream_; +}; + +PixelariumLogger::PixelariumLogger(const std::string& name, const std::string& file_sink) + : name_(name), file_sink_(file_sink) +{ + log_stream_ = std::make_unique(file_sink_); +} + +PixelariumLogger::~PixelariumLogger() = default; + +auto PixelariumLogger::Write(LogLevel lvl, const std::string& msg) const -> void +{ + if (lvl < this->level_) + { + return; + } + + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + std::tm tm; +#ifdef _WIN32 + localtime_s(&tm, &time_t); +#else + localtime_r(&time_t, &tm); +#endif + + auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + + auto timestamp = std::format("{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}", tm.tm_year + 1900, tm.tm_mon + 1, + tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, ms.count()); + + { + std::lock_guard guard(mutex_); + *log_stream_ << std::format("[{}] [{}] [{}] {}\n", timestamp, name_, LogLevelToString(lvl), msg); + } +} diff --git a/lib/utilities/ILog.hpp b/lib/utilities/include/ILog.hpp similarity index 63% rename from lib/utilities/ILog.hpp rename to lib/utilities/include/ILog.hpp index b06e74c..86f3f61 100644 --- a/lib/utilities/ILog.hpp +++ b/lib/utilities/include/ILog.hpp @@ -15,6 +15,24 @@ enum class LogLevel kWarn = 1 << 3, kError = 1 << 4, }; + +constexpr auto LogLevelToString(LogLevel lvl) -> std::string +{ + switch (lvl) + { + case LogLevel::kTrace: + return "Trace"; + case LogLevel::kDebug: + return "Debug"; + case LogLevel::kInfo: + return "Info"; + case LogLevel::kWarn: + return "Warning"; + case LogLevel::kError: + return "Error"; + } +} + /// @brief Interface for logging implementations. class ILog { diff --git a/lib/utilities/include/PixelariumLogger.hpp b/lib/utilities/include/PixelariumLogger.hpp new file mode 100644 index 0000000..c9305c5 --- /dev/null +++ b/lib/utilities/include/PixelariumLogger.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +#include "ILog.hpp" + +namespace pixelarium::utils::log +{ +class PixelariumLogger : public ILog +{ + private: + struct LogStream; + + public: + explicit PixelariumLogger(const std::string& name, const std::string& file_sink); + ~PixelariumLogger(); + + void Info(const std::string& msg) const override { this->Write(LogLevel::kInfo, msg); } + void Debug(const std::string& msg) const override { this->Write(LogLevel::kDebug, msg); } + void Warn(const std::string& msg) const override { this->Write(LogLevel::kWarn, msg); } + void Error(const std::string& msg) const override { this->Write(LogLevel::kError, msg); } + void ChangeLevel(LogLevel lvl) const override { this->level_ = lvl; } + + private: + void Write(LogLevel, const std::string&) const; + + private: + std::mutex mutable mutex_; + std::string name_; + std::string file_sink_; + std::unique_ptr log_stream_{}; + LogLevel mutable level_{LogLevel::kDebug}; +}; +} // namespace pixelarium::utils::log diff --git a/lib/utilities/SpdLogger.hpp b/lib/utilities/include/SpdLogger.hpp similarity index 100% rename from lib/utilities/SpdLogger.hpp rename to lib/utilities/include/SpdLogger.hpp diff --git a/lib/utilities/include/simple_thread_pool.hpp b/lib/utilities/include/simple_thread_pool.hpp new file mode 100644 index 0000000..82ab4ff --- /dev/null +++ b/lib/utilities/include/simple_thread_pool.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pixelarium::utils +{ +template +class simple_thread_pool +{ + public: + simple_thread_pool() + { + static_assert(N > 0, "Pool must have at least one thread."); + for (size_t i{0}; i < N; ++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(); + ++running_tasks_; + } + + job(); + --running_tasks_; + } + }); + } + } + + 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() + { + { + std::unique_lock lck(thread_mutex_); + shutdown_ = true; + } + + cv_.notify_all(); + for (auto& th : workers_) + { + th.join(); + } + } + + 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(); + } + + [[nodiscard]] + decltype(auto) RunningTasks() const + { + return running_tasks_.load(); + } + + decltype(auto) Joinable() const + { + std::unique_lock lck(thread_mutex_); + + return task_queue_.empty() && RunningTasks() == 0; + } + + private: + std::vector workers_; + std::condition_variable cv_; + std::mutex mutable thread_mutex_; + std::queue> task_queue_; + bool shutdown_{false}; + std::atomic_size_t running_tasks_{0}; +}; + +class pixelarium_pool +{ + public: + [[nodiscard]] + static decltype(auto) RunningTasks() + { + return pixelarium_pool::instance().RunningTasks(); + } + + static decltype(auto) Joinable() { return pixelarium_pool::instance().Joinable(); } + + template + requires std::invocable + static auto enqueue(Callable&& fun) -> void + { + pixelarium_pool::instance().enqueue(std::forward(fun)); + } + + private: + static constexpr auto kThreadCount{20}; + static simple_thread_pool& instance() + { + static simple_thread_pool pixelarium_pool_instance; + return pixelarium_pool_instance; + } +}; +} // namespace pixelarium::utils diff --git a/lib/utilities/simple_thread_pool.cpp b/lib/utilities/simple_thread_pool.cpp deleted file mode 100644 index 05504a7..0000000 --- a/lib/utilities/simple_thread_pool.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#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 deleted file mode 100644 index 8b15a03..0000000 --- a/lib/utilities/simple_thread_pool.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#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/CMakeLists.txt b/tests/CMakeLists.txt index 19e1676..6ceadc7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,17 +1,13 @@ -include(${PROJECT_SOURCE_DIR}/cmake/googletest.cmake) +# this will trigger the FetchContent +include(testgoogle) add_executable(libpixelarium_UnitTests - lib/resources/test_resource.cpp) - -target_include_directories(libpixelarium_UnitTests - PUBLIC ${PROJECT_SOURCE_DIR}/lib - PUBLIC ${OpenCV_INCLUDE_DIRS} -) - + lib/resources/test_resource.cpp + lib/utilities/test_simple_thread_pool.cpp) target_link_libraries(libpixelarium_UnitTests - pixelariumresourcelib - pixelariumimagelib + pixelarium::lib::resources_static + pixelarium::lib::imaging_static GTest::gtest_main GTest::gmock) diff --git a/tests/lib/resources/test_resource.cpp b/tests/lib/resources/test_resource.cpp index 7295672..77b14b6 100644 --- a/tests/lib/resources/test_resource.cpp +++ b/tests/lib/resources/test_resource.cpp @@ -2,8 +2,8 @@ #include -#include "imaging/IPixelariumImage.hpp" -#include "resources/resource.hpp" +#include "IPixelariumImage.hpp" +#include "resource.hpp" namespace { diff --git a/tests/lib/utilities/test_simple_thread_pool.cpp b/tests/lib/utilities/test_simple_thread_pool.cpp new file mode 100644 index 0000000..f9204af --- /dev/null +++ b/tests/lib/utilities/test_simple_thread_pool.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include +#include + +#include "simple_thread_pool.hpp" + +TEST(SimpleThreadPoolTest, MutatesFromDifferentThread) +{ + using namespace std::chrono_literals; + using namespace pixelarium::utils; + std::mutex mut; + constexpr auto vecsize{10}; + std::vector mutation_vector(vecsize, 0); + + auto mutate = [&mutation_vector](int val, int loc) -> void { mutation_vector.at(loc) = val; }; + + for (auto i{0}; i < vecsize; ++i) + { + pixelarium_pool::enqueue( + [&mutate, i]() + { + const auto wait = std::chrono::milliseconds(i); + std::this_thread::sleep_for(wait); + mutate(i, i); + }); + } + + // wait until each spawned task finished + while (!pixelarium_pool::Joinable()) std::this_thread::sleep_for(5ms); + + EXPECT_EQ(mutation_vector.size(), vecsize); + for (auto i{0}; i < vecsize; ++i) + { + EXPECT_EQ(mutation_vector.at(i), i); + } +} + +// TEST(SimpleThreadPoolTest, ZeroThreads) +// { +// using namespace pixelarium::utils; +// // Creating a thread pool with 0 threads should not throw an exception +// // and should be joinable immediately. +// simple_thread_pool<0> pool; +// EXPECT_TRUE(pool.Joinable()); + +// // Enqueuing a task on a 0-thread pool should not crash. +// // The task will never run, but the enqueue operation should be valid. +// pool.enqueue([]() {}); +// EXPECT_FALSE(pool.Joinable()); // Now it's not joinable because there's a task +// EXPECT_EQ(pool.RunningTasks(), 0); // No tasks are running + +// // Destructor should also handle the 0-thread case gracefully. +// }