diff --git a/TODO.md b/TODO.md index d993bd3..33c617e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,9 @@ # To-Do List +## General + +* Check memory leaks + ## CairoCanvas * Add functions to draw common geometry primitives diff --git a/src/imview/CMakeLists.txt b/src/imview/CMakeLists.txt index 0a1952b..a403e97 100644 --- a/src/imview/CMakeLists.txt +++ b/src/imview/CMakeLists.txt @@ -25,8 +25,8 @@ add_library(imview # widgets src/widget/cv_image_widget.cpp src/widget/buffered_cv_image_widget.cpp + src/widget/cairo_widget.cpp src/widget/cairo/cairo_context.cpp - src/widget/cairo/cairo_widget.cpp src/widget/cairo/cairo_draw.cpp # data buffer src/buffer/buffer_registry.cpp diff --git a/src/imview/include/imview/fonts.hpp b/src/imview/include/imview/fonts.hpp index 56a316b..f94e6dd 100644 --- a/src/imview/include/imview/fonts.hpp +++ b/src/imview/include/imview/fonts.hpp @@ -25,6 +25,7 @@ enum class FontSize { class Fonts { public: static void LoadFonts(); + static void UnloadFonts(); static ImFont *GetFont(FontSize size); }; } // namespace quickviz diff --git a/src/imview/include/imview/widget/cairo/cairo_context.hpp b/src/imview/include/imview/widget/cairo/cairo_context.hpp index 61aed16..1f12eed 100644 --- a/src/imview/include/imview/widget/cairo/cairo_context.hpp +++ b/src/imview/include/imview/widget/cairo/cairo_context.hpp @@ -26,9 +26,10 @@ class CairoContext { public: // default constructor: empty cairo surface CairoContext() = default; - // normalize coordinate so that drawing area will be x,y in [0, 1] - CairoContext(uint32_t width, uint32_t height, - bool normalize_coordinate = false); + // unify coordinate so that drawing area will be defined as: + // x (width): [0, 1.0 * aspect_ratio] + // y (height): [0, 1.0] + CairoContext(uint32_t width, uint32_t height, bool unify_coordinate = false); ~CairoContext(); @@ -61,7 +62,7 @@ class CairoContext { uint32_t width_; uint32_t height_; - bool normalize_coordinate_; + bool unified_coordinate_; std::stack scaler_stack_; cairo_surface_t* surface_ = nullptr; diff --git a/src/imview/include/imview/widget/cairo_widget.hpp b/src/imview/include/imview/widget/cairo_widget.hpp index 6d06cf0..4a0ec2f 100644 --- a/src/imview/include/imview/widget/cairo_widget.hpp +++ b/src/imview/include/imview/widget/cairo_widget.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -21,42 +22,39 @@ #include "imview/panel.hpp" #include "imview/widget/cairo/cairo_context.hpp" +#include "imview/widget/cairo/cairo_draw.hpp" namespace quickviz { class CairoWidget : public Panel { public: - CairoWidget(const std::string& widget_name, uint32_t width, uint32_t height, + CairoWidget(const std::string& widget_name, bool normalize_coordinate = false); - ~CairoWidget() override; + ~CairoWidget() override = default; void Draw() override; void OnResize(float width, float height) override; - float GetAspectRatio() const; + // draw vector graphics with user function + using CairoDrawFunc = std::function; + void AttachDrawFunction(CairoDrawFunc DrawFunc); - // resize/fill cairo surface - void Resize(uint32_t width, uint32_t height); + private: void Fill(ImVec4 color = {1, 1, 1, 0.6}); void Clear(); - // draw vector graphics with user function - using CairoDrawFunc = std::function; - void Draw(CairoDrawFunc DrawFunc); - - // draw text to cairo surface void DrawText(std::string text, double pos_x, double pos_y, double angle = 0.0, ImVec4 color = {0, 0, 0, 1}, double size = 14.0, double line_width = 3.0, const char* font = "Sans"); - // render cairo content to OpenGL context to display with ImGUI void Render(const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), const ImVec4& border_col = ImVec4(0, 0, 0, 0)); - private: std::unique_ptr ctx_; + std::mutex draw_func_mutex_; + CairoDrawFunc draw_func_; }; } // namespace quickviz diff --git a/src/imview/src/fonts.cpp b/src/imview/src/fonts.cpp index 3c6e581..0b5e857 100644 --- a/src/imview/src/fonts.cpp +++ b/src/imview/src/fonts.cpp @@ -8,6 +8,8 @@ #include "imview/fonts.hpp" +#include + #include #include "fonts/opensans_regular.hpp" @@ -36,6 +38,17 @@ void Fonts::LoadFonts() { OpenSansRegular_compressed_data, OpenSansRegular_compressed_size, 40.f); } +void Fonts::UnloadFonts() { + ImGuiIO &io = ImGui::GetIO(); + io.Fonts->ClearFonts(); + + // Reference: + // [1] + // https://stackoverflow.com/questions/51174295/cairo-show-text-memory-leak + // [2] https://gitlab.freedesktop.org/cairo/cairo/-/issues/393 + FcFini(); +} + ImFont *Fonts::GetFont(FontSize size) { if (custom_fonts_.find(size) != custom_fonts_.end()) { return custom_fonts_[size]; diff --git a/src/imview/src/viewer.cpp b/src/imview/src/viewer.cpp index 6a8f689..6c4825e 100644 --- a/src/imview/src/viewer.cpp +++ b/src/imview/src/viewer.cpp @@ -94,6 +94,7 @@ Viewer::Viewer(std::string title, uint32_t width, uint32_t height, } Viewer::~Viewer() { + Fonts::UnloadFonts(); ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImPlot::DestroyContext(); diff --git a/src/imview/src/widget/cairo/cairo_context.cpp b/src/imview/src/widget/cairo/cairo_context.cpp index ec7433e..e9c83b5 100644 --- a/src/imview/src/widget/cairo/cairo_context.cpp +++ b/src/imview/src/widget/cairo/cairo_context.cpp @@ -13,10 +13,8 @@ namespace quickviz { CairoContext::CairoContext(uint32_t width, uint32_t height, - bool normalize_coordinate) - : width_(width), - height_(height), - normalize_coordinate_(normalize_coordinate) { + bool unify_coordinate) + : width_(width), height_(height), unified_coordinate_(unify_coordinate) { // create cairo context CreateSurface(); GenGlTexture(); @@ -58,8 +56,7 @@ void CairoContext::CreateSurface() { "[ERROR] create_cairo_context() - Couldn't create context\n"); } - if (normalize_coordinate_) { - // auto min = std::min(width_, height_); + if (unified_coordinate_) { cairo_scale(cr_, height_, height_); } diff --git a/src/imview/src/widget/cairo/cairo_widget.cpp b/src/imview/src/widget/cairo_widget.cpp similarity index 67% rename from src/imview/src/widget/cairo/cairo_widget.cpp rename to src/imview/src/widget/cairo_widget.cpp index f17363c..82c8f3b 100644 --- a/src/imview/src/widget/cairo/cairo_widget.cpp +++ b/src/imview/src/widget/cairo_widget.cpp @@ -15,29 +15,21 @@ #include namespace quickviz { -CairoWidget::CairoWidget(const std::string& widget_name, uint32_t width, - uint32_t height, bool normalize_coordinate) - : Panel(widget_name), - ctx_(new CairoContext(width, height, normalize_coordinate)) {} - -CairoWidget::~CairoWidget() { - // font-related memory cleanup - // Reference: - // [1] - // https://stackoverflow.com/questions/51174295/cairo-show-text-memory-leak - // [2] https://gitlab.freedesktop.org/cairo/cairo/-/issues/393 - cairo_debug_reset_static_data(); - FcFini(); +CairoWidget::CairoWidget(const std::string& widget_name, + bool normalize_coordinate) + : Panel(widget_name), ctx_(new CairoContext(0, 0, normalize_coordinate)) {} + +void CairoWidget::Draw() { + Begin(); + Render(); + End(); } -void CairoWidget::Draw() {} - void CairoWidget::OnResize(float width, float height) { Panel::OnResize(width, height); + ctx_->Resize(width, height); } -float CairoWidget::GetAspectRatio() const { return ctx_->GetAspectRatio(); } - void CairoWidget::Fill(ImVec4 color) { auto cr = ctx_->GetCairoObject(); cairo_set_source_rgba(cr, color.x, color.y, color.z, color.w); @@ -52,15 +44,9 @@ void CairoWidget::Clear() { cairo_paint(cr); } -void CairoWidget::Draw(CairoDrawFunc DrawFunc) { - assert(ctx_ != nullptr && ctx_->GetCairoObject() != NULL); - - // do actual paint with cairo - DrawFunc(ctx_->GetCairoObject()); -} - -void CairoWidget::Resize(uint32_t width, uint32_t height) { - ctx_->Resize(width, height); +void CairoWidget::AttachDrawFunction(CairoDrawFunc DrawFunc) { + std::lock_guard lock(draw_func_mutex_); + draw_func_ = DrawFunc; } void CairoWidget::DrawText(std::string text, double pos_x, double pos_y, @@ -88,6 +74,24 @@ void CairoWidget::DrawText(std::string text, double pos_x, double pos_y, void CairoWidget::Render(const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) { + assert(ctx_ != nullptr && ctx_->GetCairoObject() != NULL); + + // check if window size has changed + if (width_ == 0 || height_ == 0) return; + auto current_size = ImGui::GetWindowSize(); + if (current_size.x != width_ || current_size.y != height_) { + OnResize(current_size.x, current_size.y); + } + + // draw user-defined graphics + CairoDrawFunc draw_func; + { + std::lock_guard lock(draw_func_mutex_); + draw_func = draw_func_; + } + draw_func(ctx_->GetCairoObject(), ctx_->GetAspectRatio()); + + // render cairo content to OpenGL texture GLuint image = ctx_->RenderToGlTexture(); ImGui::Image((void*)(intptr_t)image, ImGui::GetContentRegionAvail(), uv0, uv1, tint_col, border_col); diff --git a/src/imview/test/feature/test_cairo_widget.cpp b/src/imview/test/feature/test_cairo_widget.cpp index 9090d3e..e1b3fe5 100644 --- a/src/imview/test/feature/test_cairo_widget.cpp +++ b/src/imview/test/feature/test_cairo_widget.cpp @@ -13,54 +13,76 @@ #include #include "imview/viewer.hpp" -#include "imview/widget/cv_image_widget.hpp" +#include "imview/widget/cairo_widget.hpp" #include "scene_objects/gl_triangle_scene_object.hpp" using namespace quickviz; -bool keep_running = true; -std::shared_ptr image_widget; +void PaintUnifiedCoordinate(cairo_t* cr, float aspect_ratio) { + float pos_x = 0.5 * aspect_ratio; + float pos_y = 0.5; -void CaptureVideo() { - cv::VideoCapture cap(0); // Open the default camera - if (!cap.isOpened()) { - std::cerr << "Error: Could not open video capture device." << std::endl; - return; - } + DrawPoint(cr, {pos_x, pos_y}, 0.01, {1, 0.2, 0.2, 0.6}); + DrawCircle(cr, {pos_x, pos_y}, 0.3, 0.002, {1, 0.2, 0.2, 0.6}); + DrawRectangle(cr, {pos_x - 0.2f, pos_y - 0.2f}, {pos_x + 0.2f, pos_y + 0.2f}, + 0.002); +} + +void Paint(cairo_t* cr, float aspect_ratio) { + DrawPoint(cr, {860, 200}); + DrawPoint(cr, {1060, 200}, 10, {1, 0.2, 0.2, 0.6}); + + DrawLine(cr, {860, 150}, {1060, 150}); + DrawLine(cr, {860, 250}, {1060, 250}, 2, {1, 0.2, 0.2, 0.6}); + + DrawCircle(cr, {960, 540}, 30); + DrawCircle(cr, {960, 540}, 50, 5, {1, 0.2, 0.2, 0.6}); + + DrawRing(cr, {960, 540}, 100, 130, 0, M_PI / 4.0); + DrawRing(cr, {960, 540}, 140, 180, 0, M_PI / 4.0, 5, colors[GREEN]); - while (keep_running) { - cv::Mat frame; - cap >> frame; // Capture a new frame - if (frame.empty()) { - break; // End of video stream - } + DrawRing(cr, {960, 540}, 100, 200, M_PI, M_PI * 1.5f, 5, colors[YELLOW], + true); + DrawRing(cr, {960, 540}, 140, 220, M_PI / 2.0, 2 * M_PI / 3.0, 5, + colors[GREEN], true); + DrawRing(cr, {960, 540}, 140, 220, M_PI / 2.0, 5 * M_PI / 6.0, 5, + colors[PURPLE], false); - if (image_widget != nullptr) image_widget->UpdateImage(frame); - std::this_thread::sleep_for( - std::chrono::milliseconds(30)); // Simulate frame rate + DrawArc(cr, {600, 800}, 60, M_PI, 2 * M_PI / 4.0); + DrawArc(cr, {600, 800}, 70, M_PI / 4.0, M_PI, 5, {1, 0.2, 0.2, 0.6}); + + DrawArcSector(cr, {1060, 800}, 60, 0, -M_PI / 4.0); + DrawArcSector(cr, {1060, 800}, 80, M_PI / 4.0, M_PI, 5, {1, 0.2, 0.2, 0.6}); + DrawArcSector(cr, {1060, 800}, 85, 5.0 * M_PI / 4.0, 6.0 * M_PI / 4.0, 5, + {1, 0.2, 0.2, 0.3}, true); + + DrawRectangle(cr, {400, 400}, {500, 600}); + DrawRectangle(cr, {550, 400}, {600, 600}, 5, colors[MAGENTA]); + DrawRectangle(cr, {650, 400}, {700, 600}, 5, colors[CYAN], true); + + for (int i = 0; i < COLOR_LAST; ++i) { + DrawLine(cr, {200.0f, 300.0f + 20 * i}, {250.0f, 300.0f + 20 * i}, 10, + colors[i]); } } int main(int argc, char* argv[]) { - // set up video capture thread --> producer - std::thread capture_thread(CaptureVideo); - - // set up viewer --> consumer Viewer viewer; - auto gl_triangle = std::make_shared(); - viewer.AddSceneObject(gl_triangle); +// auto gl_triangle = std::make_shared(); +// viewer.AddSceneObject(gl_triangle); - image_widget = std::make_shared("camera"); - image_widget->OnResize(300, 200); - image_widget->SetPosition(0, 0); - viewer.AddSceneObject(image_widget); + auto cairo_widget = std::make_shared("cairo_unified", true); + cairo_widget->OnResize(300, 200); + cairo_widget->AttachDrawFunction(PaintUnifiedCoordinate); + viewer.AddSceneObject(cairo_widget); - viewer.Show(); + auto cairo_widget2 = std::make_shared("cairo"); + cairo_widget2->OnResize(300, 200); + cairo_widget2->AttachDrawFunction(Paint); + viewer.AddSceneObject(cairo_widget2); - // clean up - keep_running = false; - capture_thread.join(); + viewer.Show(); return 0; } \ No newline at end of file