From 70f69cb265188e7760be3297d88c0642fb7b0dc2 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sat, 1 May 2021 13:13:58 +0000 Subject: [PATCH] highgui: backends and plugins --- cmake/OpenCVFindLibsGUI.cmake | 37 +- cmake/OpenCVMinDepVersions.cmake | 1 - cmake/OpenCVModule.cmake | 9 + cmake/templates/cvconfig.h.in | 15 - modules/CMakeLists.txt | 2 + .../core/utils/plugin_loader.private.hpp | 4 +- modules/highgui/CMakeLists.txt | 135 +++- modules/highgui/cmake/detect_gtk.cmake | 43 ++ modules/highgui/cmake/init.cmake | 25 + modules/highgui/cmake/plugin.cmake | 61 ++ modules/highgui/misc/plugins/build_plugins.sh | 69 ++ .../misc/plugins/plugin_gtk/CMakeLists.txt | 44 ++ .../plugins/plugin_gtk/Dockerfile-ubuntu-gtk2 | 21 + .../plugins/plugin_gtk/Dockerfile-ubuntu-gtk3 | 21 + .../highgui/misc/plugins/plugin_gtk/build.sh | 13 + modules/highgui/src/backend.cpp | 181 +++++ modules/highgui/src/backend.hpp | 131 ++++ modules/highgui/src/factory.hpp | 48 ++ modules/highgui/src/plugin_api.hpp | 72 ++ modules/highgui/src/plugin_wrapper.impl.hpp | 283 ++++++++ modules/highgui/src/precomp.hpp | 13 + modules/highgui/src/registry.hpp | 25 + modules/highgui/src/registry.impl.hpp | 183 +++++ modules/highgui/src/window.cpp | 399 ++++++++++- modules/highgui/src/window_QT.cpp | 9 +- modules/highgui/src/window_QT.h | 2 +- modules/highgui/src/window_gtk.cpp | 668 +++++++++++++++--- modules/highgui/test/test_gui.cpp | 68 +- 28 files changed, 2395 insertions(+), 187 deletions(-) create mode 100644 modules/highgui/cmake/detect_gtk.cmake create mode 100644 modules/highgui/cmake/init.cmake create mode 100644 modules/highgui/cmake/plugin.cmake create mode 100755 modules/highgui/misc/plugins/build_plugins.sh create mode 100644 modules/highgui/misc/plugins/plugin_gtk/CMakeLists.txt create mode 100644 modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk2 create mode 100644 modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk3 create mode 100755 modules/highgui/misc/plugins/plugin_gtk/build.sh create mode 100644 modules/highgui/src/backend.cpp create mode 100644 modules/highgui/src/backend.hpp create mode 100644 modules/highgui/src/factory.hpp create mode 100644 modules/highgui/src/plugin_api.hpp create mode 100644 modules/highgui/src/plugin_wrapper.impl.hpp create mode 100644 modules/highgui/src/registry.hpp create mode 100644 modules/highgui/src/registry.impl.hpp diff --git a/cmake/OpenCVFindLibsGUI.cmake b/cmake/OpenCVFindLibsGUI.cmake index e3593d4dc9..8030e8b0c0 100644 --- a/cmake/OpenCVFindLibsGUI.cmake +++ b/cmake/OpenCVFindLibsGUI.cmake @@ -11,7 +11,7 @@ if(WITH_WIN32UI) CMAKE_FLAGS "-DLINK_LIBRARIES:STRING=user32;gdi32") endif() -# --- QT4 --- +# --- QT4/5 --- ocv_clear_vars(HAVE_QT HAVE_QT5) if(WITH_QT) if(NOT WITH_QT EQUAL 4) @@ -34,41 +34,6 @@ if(WITH_QT) endif() endif() -# --- GTK --- -ocv_clear_vars(HAVE_GTK HAVE_GTK3 HAVE_GTHREAD HAVE_GTKGLEXT) -if(WITH_GTK AND NOT HAVE_QT) - if(NOT WITH_GTK_2_X) - ocv_check_modules(GTK3 gtk+-3.0) - if(HAVE_GTK3) - ocv_append_build_options(HIGHGUI GTK3) - set(HAVE_GTK TRUE) - endif() - endif() - if(NOT HAVE_GTK) - ocv_check_modules(GTK2 gtk+-2.0) - if(HAVE_GTK2) - if (GTK2_VERSION VERSION_LESS MIN_VER_GTK) - message (FATAL_ERROR "GTK support requires a minimum version of ${MIN_VER_GTK} (${GTK2_VERSION} found)") - else() - ocv_append_build_options(HIGHGUI GTK2) - set(HAVE_GTK TRUE) - endif() - endif() - endif() - ocv_check_modules(GTHREAD gthread-2.0) - if(HAVE_GTK AND NOT HAVE_GTHREAD) - message(FATAL_ERROR "gthread not found. This library is required when building with GTK support") - else() - ocv_append_build_options(HIGHGUI GTHREAD) - endif() - if(WITH_OPENGL AND NOT HAVE_GTK3) - ocv_check_modules(GTKGLEXT gtkglext-1.0) - if(HAVE_GTKGLEXT) - ocv_append_build_options(HIGHGUI GTKGLEXT) - endif() - endif() -endif() - # --- OpenGl --- ocv_clear_vars(HAVE_OPENGL HAVE_QT_OPENGL) if(WITH_OPENGL) diff --git a/cmake/OpenCVMinDepVersions.cmake b/cmake/OpenCVMinDepVersions.cmake index ce0c0ba816..db225e2ab5 100644 --- a/cmake/OpenCVMinDepVersions.cmake +++ b/cmake/OpenCVMinDepVersions.cmake @@ -6,4 +6,3 @@ set(MIN_VER_CUDNN 7.5) set(MIN_VER_PYTHON2 2.7) set(MIN_VER_PYTHON3 3.2) set(MIN_VER_ZLIB 1.2.3) -set(MIN_VER_GTK 2.18.0) diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index 0e783dfec6..7c48aad9c2 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -1183,6 +1183,9 @@ function(ocv_add_perf_tests) if(TARGET opencv_videoio_plugins) add_dependencies(${the_target} opencv_videoio_plugins) endif() + if(TARGET opencv_highgui_plugins) + add_dependencies(${the_target} opencv_highgui_plugins) + endif() if(HAVE_HPX) message("Linking HPX to Perf test of module ${name}") @@ -1278,6 +1281,9 @@ function(ocv_add_accuracy_tests) if(TARGET opencv_videoio_plugins) add_dependencies(${the_target} opencv_videoio_plugins) endif() + if(TARGET opencv_highgui_plugins) + add_dependencies(${the_target} opencv_highgui_plugins) + endif() if(HAVE_HPX) message("Linking HPX to Perf test of module ${name}") @@ -1368,6 +1374,9 @@ function(ocv_add_samples) if(TARGET opencv_videoio_plugins) add_dependencies(${the_target} opencv_videoio_plugins) endif() + if(TARGET opencv_highgui_plugins) + add_dependencies(${the_target} opencv_highgui_plugins) + endif() if(INSTALL_BIN_EXAMPLES) install(TARGETS ${the_target} RUNTIME DESTINATION "${OPENCV_SAMPLES_BIN_INSTALL_PATH}/${module_id}" COMPONENT samples) diff --git a/cmake/templates/cvconfig.h.in b/cmake/templates/cvconfig.h.in index c0f073604b..e79e1ec0a1 100644 --- a/cmake/templates/cvconfig.h.in +++ b/cmake/templates/cvconfig.h.in @@ -28,9 +28,6 @@ /* Clp support */ #cmakedefine HAVE_CLP -/* Cocoa API */ -#cmakedefine HAVE_COCOA - /* NVIDIA CUDA Runtime API*/ #cmakedefine HAVE_CUDA @@ -56,12 +53,6 @@ /* Geospatial Data Abstraction Library */ #cmakedefine HAVE_GDAL -/* GTK+ 2.0 Thread support */ -#cmakedefine HAVE_GTHREAD - -/* GTK+ 2.x toolkit */ -#cmakedefine HAVE_GTK - /* Halide support */ #cmakedefine HAVE_HALIDE @@ -121,12 +112,6 @@ /* parallel_for with pthreads */ #cmakedefine HAVE_PTHREADS_PF -/* Qt support */ -#cmakedefine HAVE_QT - -/* Qt OpenGL support */ -#cmakedefine HAVE_QT_OPENGL - /* Intel Threading Building Blocks */ #cmakedefine HAVE_TBB diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 6a8004036b..f2389d0c1b 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,3 +1,5 @@ +ocv_cmake_dump_vars("" TOFILE "CMakeVars2.txt") +set(OCV_TEST_VAR 123) add_definitions(-D__OPENCV_BUILD=1) if(NOT OPENCV_MODULES_PATH) diff --git a/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp b/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp index bc3ae4d08a..d6390fc74a 100644 --- a/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp +++ b/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp @@ -80,7 +80,9 @@ LibHandle_t libraryLoad_(const FileSystemPath_t& filename) return LoadLibraryW(filename.c_str()); #endif #elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) - return dlopen(filename.c_str(), RTLD_NOW); + void* handle = dlopen(filename.c_str(), RTLD_NOW); + CV_LOG_IF_DEBUG(NULL, !handle, "dlopen() error: " << dlerror()); + return handle; #endif } diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index 7a546616a4..9b68b4672e 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -1,10 +1,46 @@ set(the_description "High-level GUI") + +set(ENABLE_PLUGINS_DEFAULT ON) +if(EMSCRIPTEN OR IOS OR WINRT) + set(ENABLE_PLUGINS_DEFAULT OFF) +endif() +set(HIGHGUI_PLUGIN_LIST "" CACHE STRING "List of GUI backends to be compiled as plugins (gtk, gtk2/gtk3, qt, win32 or special value 'all')") +set(HIGHGUI_ENABLE_PLUGINS "${ENABLE_PLUGINS_DEFAULT}" CACHE BOOL "Allow building and using of GUI plugins") +mark_as_advanced(HIGHGUI_PLUGIN_LIST HIGHGUI_ENABLE_PLUGINS) + +string(REPLACE "," ";" HIGHGUI_PLUGIN_LIST "${HIGHGUI_PLUGIN_LIST}") # support comma-separated list (,) too +if(NOT HIGHGUI_ENABLE_PLUGINS) + if(HIGHGUI_PLUGIN_LIST) + message(WARNING "HighGUI: plugins are disabled through HIGHGUI_ENABLE_PLUGINS, so HIGHGUI_PLUGIN_LIST='${HIGHGUI_PLUGIN_LIST}' is ignored") + set(HIGHGUI_PLUGIN_LIST "") + endif() +else() + # Make virtual plugins target + if(NOT TARGET opencv_highgui_plugins) + add_custom_target(opencv_highgui_plugins ALL) + endif() +endif() + if(ANDROID) ocv_add_module(highgui opencv_imgproc opencv_imgcodecs OPTIONAL opencv_videoio WRAP python) else() ocv_add_module(highgui opencv_imgproc opencv_imgcodecs OPTIONAL opencv_videoio WRAP python java) endif() +include(${CMAKE_CURRENT_LIST_DIR}/cmake/plugin.cmake) + +set(tgts "PRIVATE") + +set(highgui_hdrs + ${CMAKE_CURRENT_LIST_DIR}/src/precomp.hpp + ) + +set(highgui_srcs + ${CMAKE_CURRENT_LIST_DIR}/src/backend.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/window.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/roiSelector.cpp + ) + # ---------------------------------------------------------------------------- # CMake file for highgui. See root CMakeLists.txt # Some parts taken from version of Hartmut Seichter, HIT Lab NZ. @@ -24,15 +60,6 @@ if(HAVE_WEBP) add_definitions(-DHAVE_WEBP) endif() -set(highgui_hdrs - ${CMAKE_CURRENT_LIST_DIR}/src/precomp.hpp - ) - -set(highgui_srcs - ${CMAKE_CURRENT_LIST_DIR}/src/window.cpp - ${CMAKE_CURRENT_LIST_DIR}/src/roiSelector.cpp - ) - file(GLOB highgui_ext_hdrs "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/*.hpp" "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/*.hpp" @@ -42,6 +69,8 @@ file(GLOB highgui_ext_hdrs list(REMOVE_ITEM highgui_ext_hdrs "${CMAKE_CURRENT_LIST_DIR}/include/opencv2/${name}/highgui_winrt.hpp") if(HAVE_QT5) + add_definitions(-DHAVE_QT) + # "Automoc" doesn't work properly with opencv_world build, use QT5_WRAP_CPP() directly #set(CMAKE_AUTOMOC ON) @@ -62,13 +91,16 @@ if(HAVE_QT5) endforeach() if(HAVE_QT_OPENGL) + add_definitions(-DHAVE_QT_OPENGL) add_definitions(${Qt5OpenGL_DEFINITIONS}) include_directories(${Qt5OpenGL_INCLUDE_DIRS}) list(APPEND HIGHGUI_LIBRARIES ${Qt5OpenGL_LIBRARIES}) endif() elseif(HAVE_QT) - if (HAVE_QT_OPENGL) + add_definitions(-DHAVE_QT) + if(HAVE_QT_OPENGL) + add_definitions(-DHAVE_QT_OPENGL) set(QT_USE_QTOPENGL TRUE) endif() include(${QT_USE_FILE}) @@ -121,13 +153,64 @@ elseif(HAVE_WIN32UI) if(OpenCV_ARCH STREQUAL "ARM64") list(APPEND HIGHGUI_LIBRARIES "comdlg32" "advapi32") endif() -elseif(HAVE_GTK OR HAVE_GTK3) - list(APPEND highgui_srcs ${CMAKE_CURRENT_LIST_DIR}/src/window_gtk.cpp) elseif(HAVE_COCOA) + add_definitions(-DHAVE_COCOA) list(APPEND highgui_srcs ${CMAKE_CURRENT_LIST_DIR}/src/window_cocoa.mm) list(APPEND HIGHGUI_LIBRARIES "-framework Cocoa") endif() +if(TARGET ocv.3rdparty.gtk3 OR TARGET ocv.3rdparty.gtk2) + if(TARGET ocv.3rdparty.gtk3 AND NOT WITH_GTK_2_X) + set(__gtk_dependency "ocv.3rdparty.gtk3") + else() + set(__gtk_dependency "ocv.3rdparty.gtk2") + endif() + if( + NOT HIGHGUI_PLUGIN_LIST STREQUAL "all" + AND NOT "gtk" IN_LIST HIGHGUI_PLUGIN_LIST + AND NOT "gtk2" IN_LIST HIGHGUI_PLUGIN_LIST + AND NOT "gtk3" IN_LIST HIGHGUI_PLUGIN_LIST + ) + list(APPEND highgui_srcs ${CMAKE_CURRENT_LIST_DIR}/src/window_gtk.cpp) + list(APPEND tgts ${__gtk_dependency}) + if(TARGET ocv.3rdparty.gthread) + list(APPEND tgts ocv.3rdparty.gthread) + endif() + if(TARGET ocv.3rdparty.gtkglext + AND NOT OPENCV_GTK_DISABLE_GTKGLEXT + ) + list(APPEND tgts ocv.3rdparty.gtkglext) + endif() + elseif("gtk" IN_LIST HIGHGUI_PLUGIN_LIST) + ocv_create_builtin_highgui_plugin(opencv_highgui_gtk ${__gtk_dependency} "window_gtk.cpp") + if(TARGET ocv.3rdparty.gthread) + ocv_target_link_libraries(opencv_highgui_gtk ocv.3rdparty.gthread) + endif() + if(TARGET ocv.3rdparty.gtkglext) + ocv_target_link_libraries(opencv_highgui_gtk ocv.3rdparty.gtkglext) + endif() + else() + if(TARGET ocv.3rdparty.gtk3 AND ("gtk3" IN_LIST HIGHGUI_PLUGIN_LIST OR HIGHGUI_PLUGIN_LIST STREQUAL "all")) + ocv_create_builtin_highgui_plugin(opencv_highgui_gtk3 ocv.3rdparty.gtk3 "window_gtk.cpp") + if(TARGET ocv.3rdparty.gthread) + ocv_target_link_libraries(opencv_highgui_gtk3 ocv.3rdparty.gthread) + endif() + if(TARGET ocv.3rdparty.gtkglext) + ocv_target_link_libraries(opencv_highgui_gtk3 ocv.3rdparty.gtkglext) + endif() + endif() + if(TARGET ocv.3rdparty.gtk2 AND ("gtk2" IN_LIST HIGHGUI_PLUGIN_LIST OR HIGHGUI_PLUGIN_LIST STREQUAL "all")) + ocv_create_builtin_highgui_plugin(opencv_highgui_gtk2 ocv.3rdparty.gtk2 "window_gtk.cpp") + if(TARGET ocv.3rdparty.gthread) + ocv_target_link_libraries(opencv_highgui_gtk2 ocv.3rdparty.gthread) + endif() + if(TARGET ocv.3rdparty.gtkglext) + ocv_target_link_libraries(opencv_highgui_gtk2 ocv.3rdparty.gtkglext) + endif() + endif() + endif() +endif() + if(TRUE) # these variables are set by 'ocv_append_build_options(HIGHGUI ...)' foreach(P ${HIGHGUI_INCLUDE_DIRS}) @@ -139,6 +222,21 @@ if(TRUE) endforeach() endif() +if(tgts STREQUAL "PRIVATE") + set(tgts "") +endif() + +# install used dependencies only +if(NOT BUILD_SHARED_LIBS + AND NOT (CMAKE_VERSION VERSION_LESS "3.13.0") # upgrade CMake: https://gitlab.kitware.com/cmake/cmake/-/merge_requests/2152 +) + foreach(tgt in ${tgts}) + if(tgt MATCHES "^ocv\.3rdparty\.") + install(TARGETS ${tgt} EXPORT OpenCVModules) + endif() + endforeach() +endif() + source_group("Src" FILES ${highgui_srcs} ${highgui_hdrs}) source_group("Include" FILES ${highgui_ext_hdrs}) ocv_set_module_sources(HEADERS ${highgui_ext_hdrs} SOURCES ${highgui_srcs} ${highgui_hdrs}) @@ -162,5 +260,14 @@ if(NOT BUILD_opencv_world) ocv_highgui_configure_target() endif() -ocv_add_accuracy_tests() -ocv_add_perf_tests() +ocv_add_accuracy_tests(${tgts}) +#ocv_add_perf_tests(${tgts}) + +if(HIGHGUI_ENABLE_PLUGINS) + ocv_target_compile_definitions(${the_module} PRIVATE ENABLE_PLUGINS) + if(TARGET opencv_test_highgui) + ocv_target_compile_definitions(opencv_test_highgui PRIVATE ENABLE_PLUGINS) + endif() +endif() + +ocv_target_link_libraries(${the_module} LINK_PRIVATE ${tgts}) diff --git a/modules/highgui/cmake/detect_gtk.cmake b/modules/highgui/cmake/detect_gtk.cmake new file mode 100644 index 0000000000..7c5939ed97 --- /dev/null +++ b/modules/highgui/cmake/detect_gtk.cmake @@ -0,0 +1,43 @@ +# --- GTK --- +ocv_clear_vars(HAVE_GTK HAVE_GTK3 HAVE_GTHREAD HAVE_GTKGLEXT) +if(WITH_GTK AND NOT HAVE_QT) + if(NOT WITH_GTK_2_X) + ocv_check_modules(GTK3 gtk+-3.0) + if(HAVE_GTK3) + ocv_add_external_target(gtk3 "${GTK3_INCLUDE_DIRS}" "${GTK3_LIBRARIES}" "HAVE_GTK3;HAVE_GTK") + set(HAVE_GTK TRUE) + set(GTK3_VERSION "${GTK3_VERSION}" PARENT_SCOPE) # informational + endif() + endif() + if(TRUE) + ocv_check_modules(GTK2 gtk+-2.0) + if(HAVE_GTK2) + set(MIN_VER_GTK "2.18.0") + if(GTK2_VERSION VERSION_LESS MIN_VER_GTK) + message(FATAL_ERROR "GTK support requires a minimum version of ${MIN_VER_GTK} (${GTK2_VERSION} found)") + else() + ocv_add_external_target(gtk2 "${GTK2_INCLUDE_DIRS}" "${GTK2_LIBRARIES}" "HAVE_GTK2;HAVE_GTK") + set(HAVE_GTK TRUE) + set(GTK2_VERSION "${GTK2_VERSION}" PARENT_SCOPE) # informational + endif() + endif() + endif() + ocv_check_modules(GTHREAD gthread-2.0) + if(HAVE_GTK AND NOT HAVE_GTHREAD) + message(FATAL_ERROR "gthread not found. This library is required when building with GTK support") + else() + ocv_add_external_target(gthread "${GTHREAD_INCLUDE_DIRS}" "${GTHREAD_LIBRARIES}" "HAVE_GTHREAD") + set(HAVE_GTHREAD "${HAVE_GTHREAD}" PARENT_SCOPE) # informational + set(GTHREAD_VERSION "${GTHREAD_VERSION}" PARENT_SCOPE) # informational + endif() + if(WITH_OPENGL AND NOT HAVE_GTK3) + ocv_check_modules(GTKGLEXT gtkglext-1.0) + if(HAVE_GTKGLEXT) + ocv_add_external_target(gtkglext "${GTKGLEXT_INCLUDE_DIRS}" "${GTKGLEXT_LIBRARIES}" "HAVE_GTKGLEXT") + set(HAVE_GTKGLEXT "${HAVE_GTKGLEXT}" PARENT_SCOPE) # informational + set(GTKGLEXT_VERSION "${GTKGLEXT_VERSION}" PARENT_SCOPE) # informational + endif() + endif() +endif() + +set(HAVE_GTK ${HAVE_GTK} PARENT_SCOPE) diff --git a/modules/highgui/cmake/init.cmake b/modules/highgui/cmake/init.cmake new file mode 100644 index 0000000000..1a115f22ed --- /dev/null +++ b/modules/highgui/cmake/init.cmake @@ -0,0 +1,25 @@ +include(FindPkgConfig) + +# FIXIT: stop using PARENT_SCOPE in dependencies +if(PROJECT_NAME STREQUAL "OpenCV") + macro(add_backend backend_id cond_var) + if(${cond_var}) + include("${CMAKE_CURRENT_LIST_DIR}/detect_${backend_id}.cmake") + endif() + endmacro() +else() + function(add_backend backend_id cond_var) + if(${cond_var}) + include("${CMAKE_CURRENT_LIST_DIR}/detect_${backend_id}.cmake") + endif() + endfunction() +endif() + +add_backend("gtk" WITH_GTK) + +# TODO win32 +# TODO cocoa +# TODO qt +# TODO opengl + +# FIXIT: move content of cmake/OpenCVFindLibsGUI.cmake here (need to resolve CMake scope issues) diff --git a/modules/highgui/cmake/plugin.cmake b/modules/highgui/cmake/plugin.cmake new file mode 100644 index 0000000000..6e0ddd2dc5 --- /dev/null +++ b/modules/highgui/cmake/plugin.cmake @@ -0,0 +1,61 @@ +function(ocv_create_builtin_highgui_plugin name target) + + ocv_debug_message("ocv_create_builtin_highgui_plugin(${ARGV})") + + if(NOT TARGET ${target}) + message(FATAL_ERROR "${target} does not exist!") + endif() + if(NOT OpenCV_SOURCE_DIR) + message(FATAL_ERROR "OpenCV_SOURCE_DIR must be set to build the plugin!") + endif() + + message(STATUS "HighGUI: add builtin plugin '${name}'") + + foreach(src ${ARGN}) + list(APPEND sources "${CMAKE_CURRENT_LIST_DIR}/src/${src}") + endforeach() + + add_library(${name} MODULE ${sources}) + target_include_directories(${name} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + target_compile_definitions(${name} PRIVATE BUILD_PLUGIN) + target_link_libraries(${name} PRIVATE ${target}) + + foreach(mod opencv_highgui + opencv_core + opencv_imgproc + opencv_imgcodecs + opencv_videoio # TODO remove this dependency + ) + ocv_target_link_libraries(${name} LINK_PRIVATE ${mod}) + ocv_target_include_directories(${name} "${OPENCV_MODULE_${mod}_LOCATION}/include") + endforeach() + + if(WIN32) + set(OPENCV_PLUGIN_VERSION "${OPENCV_DLLVERSION}" CACHE STRING "") + if(CMAKE_CXX_SIZEOF_DATA_PTR EQUAL 8) + set(OPENCV_PLUGIN_ARCH "_64" CACHE STRING "") + else() + set(OPENCV_PLUGIN_ARCH "" CACHE STRING "") + endif() + else() + set(OPENCV_PLUGIN_VERSION "" CACHE STRING "") + set(OPENCV_PLUGIN_ARCH "" CACHE STRING "") + endif() + + set_target_properties(${name} PROPERTIES + CXX_STANDARD 11 + CXX_VISIBILITY_PRESET hidden + DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" + OUTPUT_NAME "${name}${OPENCV_PLUGIN_VERSION}${OPENCV_PLUGIN_ARCH}" + ) + + if(WIN32) + set_target_properties(${name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) + install(TARGETS ${name} OPTIONAL LIBRARY DESTINATION ${OPENCV_BIN_INSTALL_PATH} COMPONENT plugins) + else() + install(TARGETS ${name} OPTIONAL LIBRARY DESTINATION ${OPENCV_LIB_INSTALL_PATH} COMPONENT plugins) + endif() + + add_dependencies(opencv_highgui_plugins ${name}) + +endfunction() diff --git a/modules/highgui/misc/plugins/build_plugins.sh b/modules/highgui/misc/plugins/build_plugins.sh new file mode 100755 index 0000000000..a27f4a0eca --- /dev/null +++ b/modules/highgui/misc/plugins/build_plugins.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +set -e + +if [ -z $1 ] ; then + echo "$0 " + exit 1 +fi + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +OCV="$( cd "${DIR}/../../../.." >/dev/null 2>&1 && pwd )" +mkdir -p "${1}" # Docker creates non-existed mounts with 'root' owner, lets ensure that dir exists under the current user to avoid "Permission denied" problem +DST="$( cd "$1" >/dev/null 2>&1 && pwd )" +CFG=$2 + +do_build() +{ +TAG=$1 +D=$2 +F=$3 +shift 3 +docker build \ + --build-arg http_proxy \ + --build-arg https_proxy \ + $@ \ + -t $TAG \ + -f "${D}/${F}" \ + "${D}" +} + +do_run() +{ +TAG=$1 +shift 1 +docker run \ + -it \ + --rm \ + -v "${OCV}":/opencv:ro \ + -v "${DST}":/dst \ + -e CFG=$CFG \ + --user $(id -u):$(id -g) \ + $TAG \ + $@ +} + +build_gtk2_ubuntu() +{ +VER=$1 +TAG=opencv_highgui_ubuntu_gtk2_builder:${VER} +do_build $TAG "${DIR}/plugin_gtk" Dockerfile-ubuntu-gtk2 --build-arg VER=${VER} +do_run $TAG /opencv/modules/highgui/misc/plugins/plugin_gtk/build.sh /dst gtk2_ubuntu${VER} ${CFG} + +} + +build_gtk3_ubuntu() +{ +VER=$1 +TAG=opencv_highgui_ubuntu_gtk3_builder:${VER} +do_build $TAG "${DIR}/plugin_gtk" Dockerfile-ubuntu-gtk3 --build-arg VER=${VER} +do_run $TAG /opencv/modules/highgui/misc/plugins/plugin_gtk/build.sh /dst gtk3_ubuntu${VER} ${CFG} +} + +echo "OpenCV: ${OCV}" +echo "Destination: ${DST}" + +build_gtk2_ubuntu 16.04 +build_gtk2_ubuntu 18.04 +build_gtk3_ubuntu 18.04 +build_gtk3_ubuntu 20.04 diff --git a/modules/highgui/misc/plugins/plugin_gtk/CMakeLists.txt b/modules/highgui/misc/plugins/plugin_gtk/CMakeLists.txt new file mode 100644 index 0000000000..ad869057a6 --- /dev/null +++ b/modules/highgui/misc/plugins/plugin_gtk/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.5) +project(opencv_highgui_gtk) + +get_filename_component(OpenCV_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../../.." ABSOLUTE) +include("${OpenCV_SOURCE_DIR}/cmake/OpenCVPluginStandalone.cmake") + +# scan dependencies +set(WITH_GTK ON) +include("${OpenCV_SOURCE_DIR}/modules/highgui/cmake/init.cmake") + +ocv_warnings_disable(CMAKE_CXX_FLAGS -Wno-deprecated-declarations) + +set(OPENCV_PLUGIN_DEPS core imgproc imgcodecs) +if(TARGET ocv.3rdparty.gtk3) + set(__deps ocv.3rdparty.gtk3) +elseif(TARGET ocv.3rdparty.gtk2) + set(__deps ocv.3rdparty.gtk2) +elseif(TARGET ocv.3rdparty.gtk) + set(__deps ocv.3rdparty.gtk) +else() + message(FATAL_ERROR "Missing dependency target for GTK libraries") +endif() +ocv_create_plugin(highgui "opencv_highgui_gtk" "${__deps}" "GTK" "src/window_gtk.cpp") + +message(STATUS "GTK: ${GTK2_VERSION}") +if(HAVE_GTK3) + message(STATUS "GTK+: ver ${GTK3_VERSION}") +elseif(HAVE_GTK) + message(STATUS "GTK+: ver ${GTK2_VERSION}") +else() + message(FATAL_ERROR "GTK+: NO") +endif() +if(HAVE_GTK) + if(HAVE_GTHREAD) + message(STATUS "GThread : YES (ver ${GTHREAD_VERSION})") + else() + message(STATUS "GThread : NO") + endif() + if(HAVE_GTKGLEXT) + message(STATUS "GtkGlExt: YES (ver ${GTKGLEXT_VERSION})") + else() + message(STATUS "GtkGlExt: NO") + endif() +endif() diff --git a/modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk2 b/modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk2 new file mode 100644 index 0000000000..81836cb384 --- /dev/null +++ b/modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk2 @@ -0,0 +1,21 @@ +ARG VER +FROM ubuntu:$VER + +RUN \ + apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + pkg-config \ + cmake \ + g++ \ + ninja-build \ + && \ + rm -rf /var/lib/apt/lists/* + +RUN \ + apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + libgtk2.0-dev \ + && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /tmp diff --git a/modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk3 b/modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk3 new file mode 100644 index 0000000000..2c6625ae14 --- /dev/null +++ b/modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk3 @@ -0,0 +1,21 @@ +ARG VER +FROM ubuntu:$VER + +RUN \ + apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + pkg-config \ + cmake \ + g++ \ + ninja-build \ + && \ + rm -rf /var/lib/apt/lists/* + +RUN \ + apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + libgtk-3-dev \ + && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /tmp diff --git a/modules/highgui/misc/plugins/plugin_gtk/build.sh b/modules/highgui/misc/plugins/plugin_gtk/build.sh new file mode 100755 index 0000000000..58048698db --- /dev/null +++ b/modules/highgui/misc/plugins/plugin_gtk/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +cmake -GNinja \ + -DOPENCV_PLUGIN_NAME=opencv_highgui_$2 \ + -DOPENCV_PLUGIN_DESTINATION=$1 \ + -DCMAKE_BUILD_TYPE=$3 \ + $DIR + +ninja -v diff --git a/modules/highgui/src/backend.cpp b/modules/highgui/src/backend.cpp new file mode 100644 index 0000000000..4c0de0584e --- /dev/null +++ b/modules/highgui/src/backend.cpp @@ -0,0 +1,181 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#include "precomp.hpp" +#include "backend.hpp" + +#include +#include +#ifdef NDEBUG +#define CV_LOG_STRIP_LEVEL CV_LOG_LEVEL_DEBUG + 1 +#else +#define CV_LOG_STRIP_LEVEL CV_LOG_LEVEL_VERBOSE + 1 +#endif +#include + + +#include "registry.hpp" +#include "registry.impl.hpp" + +#include "plugin_api.hpp" +#include "plugin_wrapper.impl.hpp" + + +namespace cv { namespace highgui_backend { + +UIBackend::~UIBackend() +{ + // nothing +} + +UIWindowBase::~UIWindowBase() +{ + // nothing +} + +UIWindow::~UIWindow() +{ + // nothing +} + +UITrackbar::~UITrackbar() +{ + // nothing +} + +static +std::string& getUIBackendName() +{ + static std::string g_backendName = toUpperCase(cv::utils::getConfigurationParameterString("OPENCV_UI_BACKEND", "")); + return g_backendName; +} + +static bool g_initializedUIBackend = false; + +static +std::shared_ptr createUIBackend() +{ + const std::string& name = getUIBackendName(); + bool isKnown = false; + const auto& backends = getBackendsInfo(); + if (!name.empty()) + { + CV_LOG_INFO(NULL, "UI: requested backend name: " << name); + } + for (size_t i = 0; i < backends.size(); i++) + { + const auto& info = backends[i]; + if (!name.empty()) + { + if (name != info.name) + { + continue; + } + isKnown = true; + } + try + { + CV_LOG_DEBUG(NULL, "UI: trying backend: " << info.name << " (priority=" << info.priority << ")"); + if (!info.backendFactory) + { + CV_LOG_DEBUG(NULL, "UI: factory is not available (plugins require filesystem support): " << info.name); + continue; + } + std::shared_ptr backend = info.backendFactory->create(); + if (!backend) + { + CV_LOG_VERBOSE(NULL, 0, "UI: not available: " << info.name); + continue; + } + CV_LOG_INFO(NULL, "UI: using backend: " << info.name << " (priority=" << info.priority << ")"); + g_initializedUIBackend = true; + getUIBackendName() = info.name; + return backend; + } + catch (const std::exception& e) + { + CV_LOG_WARNING(NULL, "UI: can't initialize " << info.name << " backend: " << e.what()); + } + catch (...) + { + CV_LOG_WARNING(NULL, "UI: can't initialize " << info.name << " backend: Unknown C++ exception"); + } + } + if (name.empty()) + { + CV_LOG_DEBUG(NULL, "UI: fallback on builtin code"); + } + else + { + if (!isKnown) + CV_LOG_INFO(NULL, "UI: unknown backend: " << name); + } + g_initializedUIBackend = true; + return std::shared_ptr(); +} + +static inline +std::shared_ptr createDefaultUIBackend() +{ + CV_LOG_DEBUG(NULL, "UI: Initializing backend..."); + return createUIBackend(); +} + +std::shared_ptr& getCurrentUIBackend() +{ + static std::shared_ptr g_currentUIBackend = createDefaultUIBackend(); + return g_currentUIBackend; +} + +void setUIBackend(const std::shared_ptr& api) +{ + getCurrentUIBackend() = api; +} + +bool setUIBackend(const std::string& backendName) +{ + CV_TRACE_FUNCTION(); + + std::string backendName_u = toUpperCase(backendName); + if (g_initializedUIBackend) + { + // ... already initialized + if (getUIBackendName() == backendName_u) + { + CV_LOG_INFO(NULL, "UI: backend is already activated: " << (backendName.empty() ? "builtin(legacy)" : backendName)); + return true; + } + else + { + // ... re-create new + CV_LOG_DEBUG(NULL, "UI: replacing backend..."); + getUIBackendName() = backendName_u; + getCurrentUIBackend() = createUIBackend(); + } + } + else + { + // ... no backend exists, just specify the name (initialization is triggered by getCurrentUIBackend() call) + getUIBackendName() = backendName_u; + } + std::shared_ptr api = getCurrentUIBackend(); + if (!api) + { + if (!backendName.empty()) + { + CV_LOG_WARNING(NULL, "UI: backend is not available: " << backendName << " (using builtin legacy code)"); + return false; + } + else + { + CV_LOG_WARNING(NULL, "UI: switched to builtin code (legacy)"); + } + } + if (!backendName_u.empty()) + { + CV_Assert(backendName_u == getUIBackendName()); // data race? + } + return true; +} + +}} // namespace cv::highgui_backend diff --git a/modules/highgui/src/backend.hpp b/modules/highgui/src/backend.hpp new file mode 100644 index 0000000000..14c88b2387 --- /dev/null +++ b/modules/highgui/src/backend.hpp @@ -0,0 +1,131 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +#ifndef OPENCV_HIGHGUI_BACKEND_HPP +#define OPENCV_HIGHGUI_BACKEND_HPP + +#include +#include + +namespace cv { namespace highgui_backend { + +class CV_EXPORTS UIWindowBase +{ +public: + typedef std::shared_ptr Ptr; + typedef std::weak_ptr WeakPtr; + + virtual ~UIWindowBase(); + + virtual const std::string& getID() const = 0; // internal name, used for logging + + virtual bool isActive() const = 0; + + virtual void destroy() = 0; +}; // UIWindowBase + +class UITrackbar; + +class CV_EXPORTS UIWindow : public UIWindowBase +{ +public: + virtual ~UIWindow(); + + virtual void imshow(InputArray image) = 0; + + virtual double getProperty(int prop) const = 0; + virtual bool setProperty(int prop, double value) = 0; + + virtual void resize(int width, int height) = 0; + virtual void move(int x, int y) = 0; + + virtual Rect getImageRect() const = 0; + + virtual void setTitle(const std::string& title) = 0; + + virtual void setMouseCallback(MouseCallback onMouse, void* userdata /*= 0*/) = 0; + + //TODO: handle both keys and mouse events (both with mouse coordinates) + //virtual void setInputCallback(InputCallback onInputEvent, void* userdata /*= 0*/) = 0; + + virtual std::shared_ptr createTrackbar( + const std::string& name, + int count, + TrackbarCallback onChange /*= 0*/, + void* userdata /*= 0*/ + ) = 0; + + virtual std::shared_ptr findTrackbar(const std::string& name) = 0; + +#if 0 // QT only + virtual void displayOverlay(const std::string& text, int delayms = 0) = 0; + virtual void displayStatusBar(const std::string& text, int delayms /*= 0*/) = 0; + virtual int createButton( + const std::string& bar_name, ButtonCallback on_change, + void* userdata = 0, int type /*= QT_PUSH_BUTTON*/, + bool initial_button_state /*= false*/ + ) = 0; + // addText, QtFont stuff +#endif + +#if 0 // OpenGL + virtual void imshow(const ogl::Texture2D& tex) = 0; + virtual void setOpenGlDrawCallback(OpenGlDrawCallback onOpenGlDraw, void* userdata = 0) = 0; + virtual void setOpenGlContext() = 0; + virtual void updateWindow() = 0; +#endif + +}; // UIWindow + + +class CV_EXPORTS UITrackbar : public UIWindowBase +{ +public: + virtual ~UITrackbar(); + + virtual int getPos() const = 0; + virtual void setPos(int pos) = 0; + + virtual cv::Range getRange() const = 0; + virtual void setRange(const cv::Range& range) = 0; +}; // UITrackbar + + +class CV_EXPORTS UIBackend +{ +public: + virtual ~UIBackend(); + + virtual void destroyAllWindows() = 0; + + // namedWindow + virtual std::shared_ptr createWindow( + const std::string& winname, + int flags + ) = 0; + + virtual int waitKeyEx(int delay /*= 0*/) = 0; + virtual int pollKey() = 0; +}; + +std::shared_ptr& getCurrentUIBackend(); +void setUIBackend(const std::shared_ptr& api); +bool setUIBackend(const std::string& backendName); + +#ifndef BUILD_PLUGIN + +#ifdef HAVE_GTK +std::shared_ptr createUIBackendGTK(); +#endif + +#if 0 // TODO: defined HAVE_QT +std::shared_ptr createUIBackendQT(); +#endif + +#endif // BUILD_PLUGIN + +} // namespace highgui_backend + +} // namespace cv + +#endif // OPENCV_HIGHGUI_BACKEND_HPP diff --git a/modules/highgui/src/factory.hpp b/modules/highgui/src/factory.hpp new file mode 100644 index 0000000000..c40358bb20 --- /dev/null +++ b/modules/highgui/src/factory.hpp @@ -0,0 +1,48 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_UI_FACTORY_HPP +#define OPENCV_UI_FACTORY_HPP + +#include "backend.hpp" + +namespace cv { namespace highgui_backend { + +class IUIBackendFactory +{ +public: + virtual ~IUIBackendFactory() {} + virtual std::shared_ptr create() const = 0; +}; + + +class StaticBackendFactory CV_FINAL: public IUIBackendFactory +{ +protected: + std::function(void)> create_fn_; + +public: + StaticBackendFactory(std::function(void)>&& create_fn) + : create_fn_(create_fn) + { + // nothing + } + + ~StaticBackendFactory() CV_OVERRIDE {} + + std::shared_ptr create() const CV_OVERRIDE + { + return create_fn_(); + } +}; + +// +// PluginUIBackendFactory is implemented in plugin_wrapper +// + +std::shared_ptr createPluginUIBackendFactory(const std::string& baseName); + +}} // namespace + +#endif // OPENCV_UI_FACTORY_HPP diff --git a/modules/highgui/src/plugin_api.hpp b/modules/highgui/src/plugin_api.hpp new file mode 100644 index 0000000000..fb57b7593e --- /dev/null +++ b/modules/highgui/src/plugin_api.hpp @@ -0,0 +1,72 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef UI_PLUGIN_API_HPP +#define UI_PLUGIN_API_HPP + +#include +#include + +#include "backend.hpp" + +#if !defined(BUILD_PLUGIN) + +/// increased for backward-compatible changes, e.g. add new function +/// Caller API <= Plugin API -> plugin is fully compatible +/// Caller API > Plugin API -> plugin is not fully compatible, caller should use extra checks to use plugins with older API +#define API_VERSION 0 // preview + +/// increased for incompatible changes, e.g. remove function argument +/// Caller ABI == Plugin ABI -> plugin is compatible +/// Caller ABI > Plugin ABI -> plugin is not compatible, caller should use shim code to use old ABI plugins (caller may know how lower ABI works, so it is possible) +/// Caller ABI < Plugin ABI -> plugin can't be used (plugin should provide interface with lower ABI to handle that) +#define ABI_VERSION 0 // preview + +#else // !defined(BUILD_PLUGIN) + +#if !defined(ABI_VERSION) || !defined(API_VERSION) +#error "Plugin must define ABI_VERSION and API_VERSION before including plugin_api.hpp" +#endif + +#endif // !defined(BUILD_PLUGIN) + +typedef cv::highgui_backend::UIBackend* CvPluginUIBackend; + +struct OpenCV_UI_Plugin_API_v0_0_api_entries +{ + /** @brief Get backend API instance + + @param[out] handle pointer on backend API handle + + @note API-CALL 1, API-Version == 0 + */ + CvResult (CV_API_CALL *getInstance)(CV_OUT CvPluginUIBackend* handle) CV_NOEXCEPT; +}; // OpenCV_UI_Plugin_API_v0_0_api_entries + +typedef struct OpenCV_UI_Plugin_API_v0 +{ + OpenCV_API_Header api_header; + struct OpenCV_UI_Plugin_API_v0_0_api_entries v0; +} OpenCV_UI_Plugin_API_v0; + +#if ABI_VERSION == 0 && API_VERSION == 0 +typedef OpenCV_UI_Plugin_API_v0 OpenCV_UI_Plugin_API; +#else +#error "Not supported configuration: check ABI_VERSION/API_VERSION" +#endif + +#ifdef BUILD_PLUGIN +extern "C" { + +CV_PLUGIN_EXPORTS +const OpenCV_UI_Plugin_API* CV_API_CALL opencv_ui_plugin_init_v0 + (int requested_abi_version, int requested_api_version, void* reserved /*NULL*/) CV_NOEXCEPT; + +} // extern "C" +#else // BUILD_PLUGIN +typedef const OpenCV_UI_Plugin_API* (CV_API_CALL *FN_opencv_ui_plugin_init_t) + (int requested_abi_version, int requested_api_version, void* reserved /*NULL*/); +#endif // BUILD_PLUGIN + +#endif // UI_PLUGIN_API_HPP diff --git a/modules/highgui/src/plugin_wrapper.impl.hpp b/modules/highgui/src/plugin_wrapper.impl.hpp new file mode 100644 index 0000000000..e68f73cb01 --- /dev/null +++ b/modules/highgui/src/plugin_wrapper.impl.hpp @@ -0,0 +1,283 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +// +// Not a standalone header, part of backend.cpp +// + +//================================================================================================== +// Dynamic backend implementation + +#include "opencv2/core/utils/plugin_loader.private.hpp" + +namespace cv { namespace impl { + +using namespace cv::highgui_backend; + +#if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) + +using namespace cv::plugin::impl; // plugin_loader.hpp + +class PluginUIBackend CV_FINAL: public std::enable_shared_from_this +{ +protected: + void initPluginAPI() + { + const char* init_name = "opencv_ui_plugin_init_v0"; + FN_opencv_ui_plugin_init_t fn_init = reinterpret_cast(lib_->getSymbol(init_name)); + if (fn_init) + { + CV_LOG_DEBUG(NULL, "Found entry: '" << init_name << "'"); + for (int supported_api_version = API_VERSION; supported_api_version >= 0; supported_api_version--) + { + plugin_api_ = fn_init(ABI_VERSION, supported_api_version, NULL); + if (plugin_api_) + break; + } + if (!plugin_api_) + { + CV_LOG_INFO(NULL, "UI: plugin is incompatible (can't be initialized): " << lib_->getName()); + return; + } + if (!checkCompatibility(plugin_api_->api_header, ABI_VERSION, API_VERSION, false)) + { + plugin_api_ = NULL; + return; + } + CV_LOG_INFO(NULL, "UI: plugin is ready to use '" << plugin_api_->api_header.api_description << "'"); + } + else + { + CV_LOG_INFO(NULL, "UI: plugin is incompatible, missing init function: '" << init_name << "', file: " << lib_->getName()); + } + } + + + bool checkCompatibility(const OpenCV_API_Header& api_header, unsigned int abi_version, unsigned int api_version, bool checkMinorOpenCVVersion) + { + if (api_header.opencv_version_major != CV_VERSION_MAJOR) + { + CV_LOG_ERROR(NULL, "UI: wrong OpenCV major version used by plugin '" << api_header.api_description << "': " << + cv::format("%d.%d, OpenCV version is '" CV_VERSION "'", api_header.opencv_version_major, api_header.opencv_version_minor)) + return false; + } + if (!checkMinorOpenCVVersion) + { + // no checks for OpenCV minor version + } + else if (api_header.opencv_version_minor != CV_VERSION_MINOR) + { + CV_LOG_ERROR(NULL, "UI: wrong OpenCV minor version used by plugin '" << api_header.api_description << "': " << + cv::format("%d.%d, OpenCV version is '" CV_VERSION "'", api_header.opencv_version_major, api_header.opencv_version_minor)) + return false; + } + CV_LOG_DEBUG(NULL, "UI: initialized '" << api_header.api_description << "': built with " + << cv::format("OpenCV %d.%d (ABI/API = %d/%d)", + api_header.opencv_version_major, api_header.opencv_version_minor, + api_header.min_api_version, api_header.api_version) + << ", current OpenCV version is '" CV_VERSION "' (ABI/API = " << abi_version << "/" << api_version << ")" + ); + if (api_header.min_api_version != abi_version) // future: range can be here + { + // actually this should never happen due to checks in plugin's init() function + CV_LOG_ERROR(NULL, "UI: plugin is not supported due to incompatible ABI = " << api_header.min_api_version); + return false; + } + if (api_header.api_version != api_version) + { + CV_LOG_INFO(NULL, "UI: NOTE: plugin is supported, but there is API version mismath: " + << cv::format("plugin API level (%d) != OpenCV API level (%d)", api_header.api_version, api_version)); + if (api_header.api_version < api_version) + { + CV_LOG_INFO(NULL, "UI: NOTE: some functionality may be unavailable due to lack of support by plugin implementation"); + } + } + return true; + } + +public: + std::shared_ptr lib_; + const OpenCV_UI_Plugin_API* plugin_api_; + + PluginUIBackend(const std::shared_ptr& lib) + : lib_(lib) + , plugin_api_(NULL) + { + initPluginAPI(); + } + + std::shared_ptr create() const + { + CV_Assert(plugin_api_); + + CvPluginUIBackend instancePtr = NULL; + + if (plugin_api_->v0.getInstance) + { + if (CV_ERROR_OK == plugin_api_->v0.getInstance(&instancePtr)) + { + CV_Assert(instancePtr); + // TODO C++20 "aliasing constructor" + return std::shared_ptr(instancePtr, [](cv::highgui_backend::UIBackend*){}); // empty deleter + } + } + return std::shared_ptr(); + } +}; + + +class PluginUIBackendFactory CV_FINAL: public IUIBackendFactory +{ +public: + std::string baseName_; + std::shared_ptr backend; + bool initialized; +public: + PluginUIBackendFactory(const std::string& baseName) + : baseName_(baseName) + , initialized(false) + { + // nothing, plugins are loaded on demand + } + + std::shared_ptr create() const CV_OVERRIDE + { + if (!initialized) + { + const_cast(this)->initBackend(); + } + if (backend) + return backend->create(); + return std::shared_ptr(); + } +protected: + void initBackend() + { + AutoLock lock(getInitializationMutex()); + try + { + if (!initialized) + loadPlugin(); + } + catch (...) + { + CV_LOG_INFO(NULL, "UI: exception during plugin loading: " << baseName_ << ". SKIP"); + } + initialized = true; + } + void loadPlugin(); +}; + +static +std::vector getPluginCandidates(const std::string& baseName) +{ + using namespace cv::utils; + using namespace cv::utils::fs; + const std::string baseName_l = toLowerCase(baseName); + const std::string baseName_u = toUpperCase(baseName); + const FileSystemPath_t baseName_l_fs = toFileSystemPath(baseName_l); + std::vector paths; + // TODO OPENCV_PLUGIN_PATH + const std::vector paths_ = getConfigurationParameterPaths("OPENCV_CORE_PLUGIN_PATH", std::vector()); + if (paths_.size() != 0) + { + for (size_t i = 0; i < paths_.size(); i++) + { + paths.push_back(toFileSystemPath(paths_[i])); + } + } + else + { + FileSystemPath_t binaryLocation; + if (getBinLocation(binaryLocation)) + { + binaryLocation = getParent(binaryLocation); +#ifndef CV_UI_PLUGIN_SUBDIRECTORY + paths.push_back(binaryLocation); +#else + paths.push_back(binaryLocation + toFileSystemPath("/") + toFileSystemPath(CV_UI_PLUGIN_SUBDIRECTORY_STR)); +#endif + } + } + const std::string default_expr = libraryPrefix() + "opencv_highgui_" + baseName_l + "*" + librarySuffix(); + const std::string plugin_expr = getConfigurationParameterString((std::string("OPENCV_UI_PLUGIN_") + baseName_u).c_str(), default_expr.c_str()); + std::vector results; +#ifdef _WIN32 + FileSystemPath_t moduleName = toFileSystemPath(libraryPrefix() + "opencv_highgui_" + baseName_l + librarySuffix()); + if (plugin_expr != default_expr) + { + moduleName = toFileSystemPath(plugin_expr); + results.push_back(moduleName); + } + for (const FileSystemPath_t& path : paths) + { + results.push_back(path + L"\\" + moduleName); + } + results.push_back(moduleName); +#else + CV_LOG_DEBUG(NULL, "UI: " << baseName << " plugin's glob is '" << plugin_expr << "', " << paths.size() << " location(s)"); + for (const std::string& path : paths) + { + if (path.empty()) + continue; + std::vector candidates; + cv::glob(utils::fs::join(path, plugin_expr), candidates); + CV_LOG_DEBUG(NULL, " - " << path << ": " << candidates.size()); + copy(candidates.begin(), candidates.end(), back_inserter(results)); + } +#endif + CV_LOG_DEBUG(NULL, "Found " << results.size() << " plugin(s) for " << baseName); + return results; +} + +void PluginUIBackendFactory::loadPlugin() +{ + for (const FileSystemPath_t& plugin : getPluginCandidates(baseName_)) + { + auto lib = std::make_shared(plugin); + if (!lib->isLoaded()) + { + continue; + } + try + { + auto pluginBackend = std::make_shared(lib); + if (!pluginBackend) + { + continue; + } + if (pluginBackend->plugin_api_ == NULL) + { + CV_LOG_ERROR(NULL, "UI: no compatible plugin API for backend: " << baseName_ << " in " << toPrintablePath(plugin)); + continue; + } + // NB: we are going to use UI backend, so prevent automatic library unloading + lib->disableAutomaticLibraryUnloading(); + backend = pluginBackend; + return; + } + catch (...) + { + CV_LOG_WARNING(NULL, "UI: exception during plugin initialization: " << toPrintablePath(plugin) << ". SKIP"); + } + } +} + +#endif // OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) + +} // namespace + +namespace highgui_backend { + +std::shared_ptr createPluginUIBackendFactory(const std::string& baseName) +{ +#if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) + return std::make_shared(baseName); +#else + CV_UNUSED(baseName); + return std::shared_ptr(); +#endif +} + +}} // namespace diff --git a/modules/highgui/src/precomp.hpp b/modules/highgui/src/precomp.hpp index a5b176c9dd..275cc556ae 100644 --- a/modules/highgui/src/precomp.hpp +++ b/modules/highgui/src/precomp.hpp @@ -42,10 +42,16 @@ #ifndef __HIGHGUI_H_ #define __HIGHGUI_H_ +#if defined(__OPENCV_BUILD) && defined(BUILD_PLUGIN) +#undef __OPENCV_BUILD // allow public API only +#endif + #include "opencv2/highgui.hpp" #include "opencv2/core/utility.hpp" +#if defined(__OPENCV_BUILD) #include "opencv2/core/private.hpp" +#endif #include "opencv2/imgproc.hpp" #include "opencv2/imgproc/imgproc_c.h" @@ -169,4 +175,11 @@ inline void convertToShow(const cv::Mat &src, const CvMat* arr, bool toRGB = tru } +namespace cv { + +CV_EXPORTS Mutex& getWindowMutex(); +static inline Mutex& getInitializationMutex() { return getWindowMutex(); } + +} // namespace + #endif /* __HIGHGUI_H_ */ diff --git a/modules/highgui/src/registry.hpp b/modules/highgui/src/registry.hpp new file mode 100644 index 0000000000..77c1234f05 --- /dev/null +++ b/modules/highgui/src/registry.hpp @@ -0,0 +1,25 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_HIGHGUI_REGISTRY_HPP +#define OPENCV_HIGHGUI_REGISTRY_HPP + +#include "factory.hpp" + +namespace cv { namespace highgui_backend { + +struct BackendInfo +{ + int priority; // 1000- - default builtin priority + // 0 - disabled (OPENCV_UI_PRIORITY_ = 0) + // >10000 - prioritized list (OPENCV_UI_PRIORITY_LIST) + std::string name; + std::shared_ptr backendFactory; +}; + +const std::vector& getBackendsInfo(); + +}} // namespace + +#endif // OPENCV_HIGHGUI_REGISTRY_HPP diff --git a/modules/highgui/src/registry.impl.hpp b/modules/highgui/src/registry.impl.hpp new file mode 100644 index 0000000000..a2e4dbea47 --- /dev/null +++ b/modules/highgui/src/registry.impl.hpp @@ -0,0 +1,183 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +// +// Not a standalone header, part of backend.cpp +// + +#include "opencv2/core/utils/filesystem.private.hpp" // OPENCV_HAVE_FILESYSTEM_SUPPORT + +namespace cv { namespace highgui_backend { + +#if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS) +#define DECLARE_DYNAMIC_BACKEND(name) \ +BackendInfo { \ + 1000, name, createPluginUIBackendFactory(name) \ +}, +#else +#define DECLARE_DYNAMIC_BACKEND(name) /* nothing */ +#endif + +#define DECLARE_STATIC_BACKEND(name, createBackendAPI) \ +BackendInfo { \ + 1000, name, std::make_shared([=] () -> std::shared_ptr { return createBackendAPI(); }) \ +}, + +static +std::vector& getBuiltinBackendsInfo() +{ + static std::vector g_backends + { +#ifdef HAVE_GTK + DECLARE_STATIC_BACKEND("GTK", createUIBackendGTK) +#if defined(HAVE_GTK3) + DECLARE_STATIC_BACKEND("GTK3", createUIBackendGTK) +#elif defined(HAVE_GTK2) + DECLARE_STATIC_BACKEND("GTK2", createUIBackendGTK) +#else +#warning "HAVE_GTK definition issue. Register new GTK backend" +#endif +#elif defined(ENABLE_PLUGINS) + DECLARE_DYNAMIC_BACKEND("GTK") + DECLARE_DYNAMIC_BACKEND("GTK3") + DECLARE_DYNAMIC_BACKEND("GTK2") +#endif + +#if 0 // TODO +#ifdef HAVE_QT + DECLARE_STATIC_BACKEND("QT", createUIBackendQT) +#elif defined(ENABLE_PLUGINS) + DECLARE_DYNAMIC_BACKEND("QT") +#endif +#endif + }; + return g_backends; +}; + +static +bool sortByPriority(const BackendInfo &lhs, const BackendInfo &rhs) +{ + return lhs.priority > rhs.priority; +} + +/** @brief Manages list of enabled backends + */ +class UIBackendRegistry +{ +protected: + std::vector enabledBackends; + UIBackendRegistry() + { + enabledBackends = getBuiltinBackendsInfo(); + int N = (int)enabledBackends.size(); + for (int i = 0; i < N; i++) + { + BackendInfo& info = enabledBackends[i]; + info.priority = 1000 - i * 10; + } + CV_LOG_DEBUG(NULL, "UI: Builtin backends(" << N << "): " << dumpBackends()); + if (readPrioritySettings()) + { + CV_LOG_INFO(NULL, "UI: Updated backends priorities: " << dumpBackends()); + N = (int)enabledBackends.size(); + } + int enabled = 0; + for (int i = 0; i < N; i++) + { + BackendInfo& info = enabledBackends[enabled]; + if (enabled != i) + info = enabledBackends[i]; + size_t param_priority = utils::getConfigurationParameterSizeT(cv::format("OPENCV_UI_PRIORITY_%s", info.name.c_str()).c_str(), (size_t)info.priority); + CV_Assert(param_priority == (size_t)(int)param_priority); // overflow check + if (param_priority > 0) + { + info.priority = (int)param_priority; + enabled++; + } + else + { + CV_LOG_INFO(NULL, "UI: Disable backend: " << info.name); + } + } + enabledBackends.resize(enabled); + CV_LOG_DEBUG(NULL, "UI: Available backends(" << enabled << "): " << dumpBackends()); + std::sort(enabledBackends.begin(), enabledBackends.end(), sortByPriority); + CV_LOG_INFO(NULL, "UI: Enabled backends(" << enabled << ", sorted by priority): " << (enabledBackends.empty() ? std::string("N/A") : dumpBackends())); + } + + static std::vector tokenize_string(const std::string& input, char token) + { + std::vector result; + std::string::size_type prev_pos = 0, pos = 0; + while((pos = input.find(token, pos)) != std::string::npos) + { + result.push_back(input.substr(prev_pos, pos-prev_pos)); + prev_pos = ++pos; + } + result.push_back(input.substr(prev_pos)); + return result; + } + bool readPrioritySettings() + { + bool hasChanges = false; + cv::String prioritized_backends = utils::getConfigurationParameterString("OPENCV_UI_PRIORITY_LIST", NULL); + if (prioritized_backends.empty()) + return hasChanges; + CV_LOG_INFO(NULL, "UI: Configured priority list (OPENCV_UI_PRIORITY_LIST): " << prioritized_backends); + const std::vector names = tokenize_string(prioritized_backends, ','); + for (size_t i = 0; i < names.size(); i++) + { + const std::string& name = names[i]; + int priority = (int)(100000 + (names.size() - i) * 1000); + bool found = false; + for (size_t k = 0; k < enabledBackends.size(); k++) + { + BackendInfo& info = enabledBackends[k]; + if (name == info.name) + { + info.priority = priority; + CV_LOG_DEBUG(NULL, "UI: New backend priority: '" << name << "' => " << info.priority); + found = true; + hasChanges = true; + break; + } + } + if (!found) + { + CV_LOG_INFO(NULL, "UI: Adding backend (plugin): '" << name << "'"); + enabledBackends.push_back(BackendInfo{priority, name, createPluginUIBackendFactory(name)}); + hasChanges = true; + } + } + return hasChanges; + } +public: + std::string dumpBackends() const + { + std::ostringstream os; + for (size_t i = 0; i < enabledBackends.size(); i++) + { + if (i > 0) os << "; "; + const BackendInfo& info = enabledBackends[i]; + os << info.name << '(' << info.priority << ')'; + } + return os.str(); + } + + static UIBackendRegistry& getInstance() + { + static UIBackendRegistry g_instance; + return g_instance; + } + + inline const std::vector& getEnabledBackends() const { return enabledBackends; } +}; + + +const std::vector& getBackendsInfo() +{ + return cv::highgui_backend::UIBackendRegistry::getInstance().getEnabledBackends(); +} + +}} // namespace diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index d2cf1e1e48..782480805b 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -40,14 +40,159 @@ //M*/ #include "precomp.hpp" -#include +#include "backend.hpp" + #include "opencv2/core/opengl.hpp" #include "opencv2/core/utils/logger.hpp" // in later times, use this file as a dispatcher to implementations like cvcap.cpp + +using namespace cv; +using namespace cv::highgui_backend; + +namespace cv { + +Mutex& getWindowMutex() +{ + static Mutex* g_window_mutex = new Mutex(); + return *g_window_mutex; +} + +namespace impl { + +typedef std::map WindowsMap_t; +static WindowsMap_t& getWindowsMap() +{ + static WindowsMap_t g_windowsMap; + return g_windowsMap; +} + +static std::shared_ptr findWindow_(const std::string& name) +{ + cv::AutoLock lock(cv::getWindowMutex()); + auto& windowsMap = getWindowsMap(); + auto i = windowsMap.find(name); + if (i != windowsMap.end()) + { + const auto& ui_base = i->second; + if (ui_base) + { + if (!ui_base->isActive()) + { + windowsMap.erase(i); + return std::shared_ptr(); + } + auto window = std::dynamic_pointer_cast(ui_base); + return window; + } + } + return std::shared_ptr(); +} + +static void cleanupTrackbarCallbacksWithData_(); // forward declaration + +static void cleanupClosedWindows_() +{ + cv::AutoLock lock(cv::getWindowMutex()); + auto& windowsMap = getWindowsMap(); + for (auto it = windowsMap.begin(); it != windowsMap.end();) + { + const auto& ui_base = it->second; + bool erase = (!ui_base || !ui_base->isActive()); + if (erase) + { + it = windowsMap.erase(it); + } + else + { + ++it; + } + } + + cleanupTrackbarCallbacksWithData_(); +} + +// Just to support deprecated API, to be removed +struct TrackbarCallbackWithData +{ + std::weak_ptr trackbar_; + int* data_; + TrackbarCallback callback_; + void* userdata_; + + TrackbarCallbackWithData(int* data, TrackbarCallback callback, void* userdata) + : data_(data) + , callback_(callback), userdata_(userdata) + { + // trackbar_ is initialized separatelly + } + + ~TrackbarCallbackWithData() + { + CV_LOG_DEBUG(NULL, "UI/Trackbar: Cleanup deprecated TrackbarCallbackWithData"); + } + + void onChange(int pos) + { + if (data_) + *data_ = pos; + if (callback_) + callback_(pos, userdata_); + } + + static void onChangeCallback(int pos, void* userdata) + { + TrackbarCallbackWithData* thiz = (TrackbarCallbackWithData*)userdata; + CV_Assert(thiz); + return thiz->onChange(pos); + } +}; + +typedef std::vector< std::shared_ptr > TrackbarCallbacksWithData_t; +static TrackbarCallbacksWithData_t& getTrackbarCallbacksWithData() +{ + static TrackbarCallbacksWithData_t g_trackbarCallbacksWithData; + return g_trackbarCallbacksWithData; +} + +static void cleanupTrackbarCallbacksWithData_() +{ + cv::AutoLock lock(cv::getWindowMutex()); + auto& callbacks = getTrackbarCallbacksWithData(); + for (auto it = callbacks.begin(); it != callbacks.end();) + { + const auto& cb = *it; + bool erase = (!cb || cb->trackbar_.expired()); + if (erase) + { + it = callbacks.erase(it); + } + else + { + ++it; + } + } +} + +}} // namespace cv::impl + +using namespace cv::impl; + CV_IMPL void cvSetWindowProperty(const char* name, int prop_id, double prop_value) { + CV_TRACE_FUNCTION(); + CV_Assert(name); + + { + auto window = findWindow_(name); + if (window) + { + /*bool res = */window->setProperty(prop_id, prop_value); + return; + } + } + switch(prop_id) { //change between fullscreen or not. @@ -109,8 +254,19 @@ CV_IMPL void cvSetWindowProperty(const char* name, int prop_id, double prop_valu /* return -1 if error */ CV_IMPL double cvGetWindowProperty(const char* name, int prop_id) { - if (!name) - return -1; + CV_TRACE_FUNCTION(); + CV_Assert(name); + + { + auto window = findWindow_(name); + if (window) + { + double v = window->getProperty(prop_id); + if (cvIsNaN(v)) + return -1; + return v; + } + } switch(prop_id) { @@ -209,9 +365,18 @@ CV_IMPL double cvGetWindowProperty(const char* name, int prop_id) cv::Rect cvGetWindowImageRect(const char* name) { + CV_TRACE_FUNCTION(); if (!name) return cv::Rect(-1, -1, -1, -1); + { + auto window = findWindow_(name); + if (window) + { + return window->getImageRect(); + } + } + #if defined (HAVE_QT) return cvGetWindowRect_QT(name); #elif defined(HAVE_WIN32UI) @@ -234,24 +399,90 @@ cv::Rect cv::getWindowImageRect(const String& winname) void cv::namedWindow( const String& winname, int flags ) { CV_TRACE_FUNCTION(); + CV_Assert(!winname.empty()); + + { + cv::AutoLock lock(cv::getWindowMutex()); + cleanupClosedWindows_(); + auto& windowsMap = getWindowsMap(); + auto i = windowsMap.find(winname); + if (i != windowsMap.end()) + { + auto ui_base = i->second; + if (ui_base) + { + auto window = std::dynamic_pointer_cast(ui_base); + if (!window) + { + CV_LOG_ERROR(NULL, "OpenCV/UI: Can't create window: '" << winname << "'"); + } + return; + } + } + auto backend = getCurrentUIBackend(); + if (backend) + { + auto window = backend->createWindow(winname, flags); + if (!window) + { + CV_LOG_ERROR(NULL, "OpenCV/UI: Can't create window: '" << winname << "'"); + return; + } + windowsMap.emplace(winname, window); + return; + } + } + cvNamedWindow( winname.c_str(), flags ); } void cv::destroyWindow( const String& winname ) { CV_TRACE_FUNCTION(); + + { + auto window = findWindow_(winname); + if (window) + { + window->destroy(); + cleanupClosedWindows_(); + return; + } + } + cvDestroyWindow( winname.c_str() ); } void cv::destroyAllWindows() { CV_TRACE_FUNCTION(); + + { + cv::AutoLock lock(cv::getWindowMutex()); + auto backend = getCurrentUIBackend(); + if (backend) + { + backend->destroyAllWindows(); + cleanupClosedWindows_(); + return; + } + } + cvDestroyAllWindows(); } void cv::resizeWindow( const String& winname, int width, int height ) { CV_TRACE_FUNCTION(); + + { + auto window = findWindow_(winname); + if (window) + { + return window->resize(width, height); + } + } + cvResizeWindow( winname.c_str(), width, height ); } @@ -264,6 +495,15 @@ void cv::resizeWindow(const String& winname, const cv::Size& size) void cv::moveWindow( const String& winname, int x, int y ) { CV_TRACE_FUNCTION(); + + { + auto window = findWindow_(winname); + if (window) + { + return window->move(x, y); + } + } + cvMoveWindow( winname.c_str(), x, y ); } @@ -282,6 +522,16 @@ double cv::getWindowProperty(const String& winname, int prop_id) int cv::waitKeyEx(int delay) { CV_TRACE_FUNCTION(); + + { + cv::AutoLock lock(cv::getWindowMutex()); + auto backend = getCurrentUIBackend(); + if (backend) + { + return backend->waitKeyEx(delay); + } + } + return cvWaitKey(delay); } @@ -308,6 +558,16 @@ int cv::waitKey(int delay) int cv::pollKey() { CV_TRACE_FUNCTION(); + + { + cv::AutoLock lock(cv::getWindowMutex()); + auto backend = getCurrentUIBackend(); + if (backend) + { + return backend->pollKey(); + } + } + // fallback. please implement a proper polling function return cvWaitKey(1); } @@ -318,6 +578,44 @@ int cv::createTrackbar(const String& trackbarName, const String& winName, void* userdata) { CV_TRACE_FUNCTION(); + + CV_LOG_IF_WARNING(NULL, value, "UI/Trackbar(" << trackbarName << "@" << winName << "): Using 'value' pointer is unsafe and deprecated. Use NULL as value pointer. " + "To fetch trackbar value setup callback."); + + { + cv::AutoLock lock(cv::getWindowMutex()); + auto window = findWindow_(winName); + if (window) + { + if (value) + { + auto cb = std::make_shared(value, callback, userdata); + auto trackbar = window->createTrackbar(trackbarName, count, TrackbarCallbackWithData::onChangeCallback, cb.get()); + if (!trackbar) + { + CV_LOG_ERROR(NULL, "OpenCV/UI: Can't create trackbar: '" << trackbarName << "'@'" << winName << "'"); + return 0; + } + cb->trackbar_ = trackbar; + getTrackbarCallbacksWithData().emplace_back(cb); + getWindowsMap().emplace(trackbar->getID(), trackbar); + trackbar->setPos(*value); + return 1; + } + else + { + auto trackbar = window->createTrackbar(trackbarName, count, callback, userdata); + if (!trackbar) + { + CV_LOG_ERROR(NULL, "OpenCV/UI: Can't create trackbar: '" << trackbarName << "'@'" << winName << "'"); + return 0; + } + getWindowsMap().emplace(trackbar->getID(), trackbar); + return 1; + } + } + } + return cvCreateTrackbar2(trackbarName.c_str(), winName.c_str(), value, count, callback, userdata); } @@ -325,30 +623,92 @@ int cv::createTrackbar(const String& trackbarName, const String& winName, void cv::setTrackbarPos( const String& trackbarName, const String& winName, int value ) { CV_TRACE_FUNCTION(); + + { + cv::AutoLock lock(cv::getWindowMutex()); + auto window = findWindow_(winName); + if (window) + { + auto trackbar = window->findTrackbar(trackbarName); + CV_Assert(trackbar); + return trackbar->setPos(value); + } + } + cvSetTrackbarPos(trackbarName.c_str(), winName.c_str(), value ); } void cv::setTrackbarMax(const String& trackbarName, const String& winName, int maxval) { CV_TRACE_FUNCTION(); + + { + cv::AutoLock lock(cv::getWindowMutex()); + auto window = findWindow_(winName); + if (window) + { + auto trackbar = window->findTrackbar(trackbarName); + CV_Assert(trackbar); + Range old_range = trackbar->getRange(); + Range range(std::min(old_range.start, maxval), maxval); + return trackbar->setRange(range); + } + } + cvSetTrackbarMax(trackbarName.c_str(), winName.c_str(), maxval); } void cv::setTrackbarMin(const String& trackbarName, const String& winName, int minval) { CV_TRACE_FUNCTION(); + + { + cv::AutoLock lock(cv::getWindowMutex()); + auto window = findWindow_(winName); + if (window) + { + auto trackbar = window->findTrackbar(trackbarName); + CV_Assert(trackbar); + Range old_range = trackbar->getRange(); + Range range(minval, std::max(minval, old_range.end)); + return trackbar->setRange(range); + } + } + cvSetTrackbarMin(trackbarName.c_str(), winName.c_str(), minval); } int cv::getTrackbarPos( const String& trackbarName, const String& winName ) { CV_TRACE_FUNCTION(); + + { + cv::AutoLock lock(cv::getWindowMutex()); + auto window = findWindow_(winName); + if (window) + { + auto trackbar = window->findTrackbar(trackbarName); + CV_Assert(trackbar); + return trackbar->getPos(); + } + } + return cvGetTrackbarPos(trackbarName.c_str(), winName.c_str()); } void cv::setMouseCallback( const String& windowName, MouseCallback onMouse, void* param) { CV_TRACE_FUNCTION(); + + { + cv::AutoLock lock(cv::getWindowMutex()); + auto window = findWindow_(windowName); + if (window) + { + return window->setMouseCallback(onMouse, param); + } + } + cvSetMouseCallback(windowName.c_str(), onMouse, param); } @@ -403,6 +763,39 @@ namespace void cv::imshow( const String& winname, InputArray _img ) { CV_TRACE_FUNCTION(); + + { + cv::AutoLock lock(cv::getWindowMutex()); + cleanupClosedWindows_(); + auto& windowsMap = getWindowsMap(); + auto i = windowsMap.find(winname); + if (i != windowsMap.end()) + { + auto ui_base = i->second; + if (ui_base) + { + auto window = std::dynamic_pointer_cast(ui_base); + if (!window) + { + CV_LOG_ERROR(NULL, "OpenCV/UI: invalid window name: '" << winname << "'"); + } + return window->imshow(_img); + } + } + auto backend = getCurrentUIBackend(); + if (backend) + { + auto window = backend->createWindow(winname, WINDOW_NORMAL); + if (!window) + { + CV_LOG_ERROR(NULL, "OpenCV/UI: Can't create window: '" << winname << "'"); + return; + } + windowsMap.emplace(winname, window); + return window->imshow(_img); + } + } + const Size size = _img.size(); #ifndef HAVE_OPENGL CV_Assert(size.width>0 && size.height>0); diff --git a/modules/highgui/src/window_QT.cpp b/modules/highgui/src/window_QT.cpp index 1600bd917f..60d7d69a59 100644 --- a/modules/highgui/src/window_QT.cpp +++ b/modules/highgui/src/window_QT.cpp @@ -1219,9 +1219,6 @@ void GuiReceiver::addSlider2(QString bar_name, QString window_name, void* value, if (t) //trackbar exists return; - if (!value) - CV_Error(CV_StsNullPtr, "NULL value pointer" ); - if (count <= 0) //count is the max value of the slider, so must be bigger than 0 CV_Error(CV_StsNullPtr, "Max value of the slider must be bigger than 0" ); @@ -1342,7 +1339,8 @@ void CvTrackbar::create(CvWindow* arg, QString name, int* value, int _count) slider->setMinimum(0); slider->setMaximum(_count); slider->setPageStep(5); - slider->setValue(*value); + if (dataSlider) + slider->setValue(*dataSlider); slider->setTickPosition(QSlider::TicksBelow); @@ -1409,7 +1407,8 @@ void CvTrackbar::update(int myvalue) { setLabel(myvalue); - *dataSlider = myvalue; + if (dataSlider) + *dataSlider = myvalue; if (callback) { callback(myvalue); diff --git a/modules/highgui/src/window_QT.h b/modules/highgui/src/window_QT.h index dbeacf2edf..398f3869f8 100644 --- a/modules/highgui/src/window_QT.h +++ b/modules/highgui/src/window_QT.h @@ -256,7 +256,7 @@ private: QPointer label; CvTrackbarCallback callback; CvTrackbarCallback2 callback2;//look like it is use by python binding - int* dataSlider; + int* dataSlider; // deprecated void* userdata; }; diff --git a/modules/highgui/src/window_gtk.cpp b/modules/highgui/src/window_gtk.cpp index 073b340443..9e011f4f18 100644 --- a/modules/highgui/src/window_gtk.cpp +++ b/modules/highgui/src/window_gtk.cpp @@ -40,8 +40,7 @@ //M*/ #include "precomp.hpp" - -#ifndef _WIN32 +#include "backend.hpp" #if defined (HAVE_GTK) @@ -104,9 +103,6 @@ struct _CvImageWidgetClass /** Allocate new image viewer widget */ GtkWidget* cvImageWidgetNew (int flags); -/** Set the image to display in the widget */ -void cvImageWidgetSetImage(CvImageWidget * widget, const CvArr *arr); - // standard GTK object macros #define CV_IMAGE_WIDGET(obj) G_TYPE_CHECK_INSTANCE_CAST (obj, cvImageWidget_get_type (), CvImageWidget) #define CV_IMAGE_WIDGET_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, cvImageWidget_get_type (), CvImageWidgetClass) @@ -122,7 +118,10 @@ static GtkWidgetClass * parent_class = NULL; // flag to help size initial window #define CV_WINDOW_NO_IMAGE 2 -void cvImageWidgetSetImage(CvImageWidget * widget, const CvArr *arr){ +/** Set the image to display in the widget */ +static +void cvImageWidgetSetImage(CvImageWidget * widget, const CvArr *arr) +{ CvMat * mat, stub; int origin=0; @@ -156,6 +155,7 @@ cvImageWidgetNew (int flags) CvImageWidget *image_widget; image_widget = CV_IMAGE_WIDGET( gtk_widget_new (cvImageWidget_get_type (), NULL) ); + CV_Assert(image_widget && "GTK widget creation is failed. Ensure that there is no GTK2/GTK3 libraries conflict"); image_widget->original_image = 0; image_widget->scaled_image = 0; image_widget->flags = flags | CV_WINDOW_NO_IMAGE; @@ -522,12 +522,13 @@ struct CvUIBase { struct CvTrackbar : CvUIBase { - CvTrackbar(const char* trackbar_name) : + CvTrackbar(const std::string& trackbar_name) : CvUIBase(CV_TRACKBAR_MAGIC_VAL), widget(NULL), name(trackbar_name), parent(NULL), data(NULL), pos(0), maxval(0), minval(0), - notify(NULL), notify2(NULL), userdata(NULL) + notify(NULL), notify2(NULL), // deprecated + onChangeCallback(NULL), userdata(NULL) { // nothing } @@ -538,20 +539,21 @@ struct CvTrackbar : CvUIBase GtkWidget* widget; std::string name; - CvWindow* parent; + CvWindow* parent; // TODO weak_ptr int* data; int pos; int maxval; int minval; - CvTrackbarCallback notify; - CvTrackbarCallback2 notify2; + CvTrackbarCallback notify; // deprecated + CvTrackbarCallback2 notify2; // deprecated + TrackbarCallback onChangeCallback; void* userdata; }; struct CvWindow : CvUIBase { - CvWindow(const char* window_name) : + CvWindow(const std::string& window_name) : CvUIBase(CV_WINDOW_MAGIC_VAL), widget(NULL), frame(NULL), paned(NULL), name(window_name), last_key(0), flags(0), status(0), @@ -560,9 +562,10 @@ struct CvWindow : CvUIBase ,useGl(false), glDrawCallback(NULL), glDrawData(NULL) #endif { - // nothing + CV_LOG_INFO(NULL, "OpenCV/UI: creating GTK window: " << window_name); } ~CvWindow(); + void destroy(); GtkWidget* widget; GtkWidget* frame; @@ -576,7 +579,7 @@ struct CvWindow : CvUIBase CvMouseCallback on_mouse; void* on_mouse_param; - std::vector< Ptr > trackbars; + std::vector< std::shared_ptr > trackbars; #ifdef HAVE_OPENGL bool useGl; @@ -600,14 +603,14 @@ GCond* cond_have_key = NULL; GThread* window_thread = NULL; #endif -static cv::Mutex& getWindowMutex() -{ - static cv::Mutex* g_window_mutex = new cv::Mutex(); - return *g_window_mutex; -} - static int last_key = -1; -static std::vector< Ptr > g_windows; + +static +std::vector< std::shared_ptr >& getGTKWindows() +{ + static std::vector< std::shared_ptr > g_windows; + return g_windows; +} CV_IMPL int cvInitSystem( int argc, char** argv ) { @@ -700,19 +703,32 @@ gpointer icvWindowThreadLoop(gpointer /*data*/) #define CV_LOCK_MUTEX() cv::AutoLock lock(getWindowMutex()) -static CvWindow* icvFindWindowByName( const char* name ) +static +std::shared_ptr icvFindWindowByName(const std::string& name) { + auto& g_windows = getGTKWindows(); for(size_t i = 0; i < g_windows.size(); ++i) { - CvWindow* window = g_windows[i].get(); + auto window = g_windows[i]; + if (!window) + continue; if (window->name == name) return window; } - return NULL; + return std::shared_ptr(); } +static inline +std::shared_ptr icvFindWindowByName(const char* name) +{ + CV_Assert(name); + return icvFindWindowByName(std::string(name)); +} + + static CvWindow* icvWindowByWidget( GtkWidget* widget ) { + auto& g_windows = getGTKWindows(); for (size_t i = 0; i < g_windows.size(); ++i) { CvWindow* window = g_windows[i].get(); @@ -722,20 +738,29 @@ static CvWindow* icvWindowByWidget( GtkWidget* widget ) return NULL; } +static Rect getImageRect_(const std::shared_ptr& window); + CvRect cvGetWindowRect_GTK(const char* name) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(name); + const auto window = icvFindWindowByName(name); if (!window) CV_Error( CV_StsNullPtr, "NULL window" ); + return cvRect(getImageRect_(window)); +} + +static Rect getImageRect_(const std::shared_ptr& window) +{ + CV_Assert(window); + gint wx, wy; #ifdef HAVE_OPENGL if (window->useGl) { gtk_widget_translate_coordinates(window->widget, gtk_widget_get_toplevel(window->widget), 0, 0, &wx, &wy); - return cvRect(wx, wy, window->widget->allocation.width, window->widget->allocation.height); + return Rect(wx, wy, window->widget->allocation.width, window->widget->allocation.height); } #endif @@ -743,23 +768,23 @@ CvRect cvGetWindowRect_GTK(const char* name) gtk_widget_translate_coordinates(&image_widget->widget, gtk_widget_get_toplevel(&image_widget->widget), 0, 0, &wx, &wy); if (image_widget->scaled_image) { #if defined (GTK_VERSION3) - return cvRect(wx, wy, MIN(image_widget->scaled_image->cols, gtk_widget_get_allocated_width(window->widget)), + return Rect(wx, wy, MIN(image_widget->scaled_image->cols, gtk_widget_get_allocated_width(window->widget)), MIN(image_widget->scaled_image->rows, gtk_widget_get_allocated_height(window->widget))); #else - return cvRect(wx, wy, MIN(image_widget->scaled_image->cols, window->widget->allocation.width), + return Rect(wx, wy, MIN(image_widget->scaled_image->cols, window->widget->allocation.width), MIN(image_widget->scaled_image->rows, window->widget->allocation.height)); #endif //GTK_VERSION3 } else if (image_widget->original_image) { #if defined (GTK_VERSION3) - return cvRect(wx, wy, MIN(image_widget->original_image->cols, gtk_widget_get_allocated_width(window->widget)), + return Rect(wx, wy, MIN(image_widget->original_image->cols, gtk_widget_get_allocated_width(window->widget)), MIN(image_widget->original_image->rows, gtk_widget_get_allocated_height(window->widget))); #else - return cvRect(wx, wy, MIN(image_widget->original_image->cols, window->widget->allocation.width), + return Rect(wx, wy, MIN(image_widget->original_image->cols, window->widget->allocation.width), MIN(image_widget->original_image->rows, window->widget->allocation.height)); #endif //GTK_VERSION3 } - return cvRect(-1, -1, -1, -1); + return Rect(-1, -1, -1, -1); } double cvGetModeWindow_GTK(const char* name)//YV @@ -767,7 +792,7 @@ double cvGetModeWindow_GTK(const char* name)//YV CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(name); + const auto window = icvFindWindowByName(name); if (!window) CV_Error( CV_StsNullPtr, "NULL window" ); @@ -775,42 +800,52 @@ double cvGetModeWindow_GTK(const char* name)//YV return result; } - +static bool setModeWindow_(const std::shared_ptr& window, int mode); void cvSetModeWindow_GTK( const char* name, double prop_value)//Yannick Verdie { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(name); - if( !window ) + const auto window = icvFindWindowByName(name); + if (!window) CV_Error( CV_StsNullPtr, "NULL window" ); - if(window->flags & CV_WINDOW_AUTOSIZE)//if the flag CV_WINDOW_AUTOSIZE is set - return; + setModeWindow_(window, (int)prop_value); +} + +static bool setModeWindow_(const std::shared_ptr& window, int mode) +{ + if (window->flags & CV_WINDOW_AUTOSIZE) //if the flag CV_WINDOW_AUTOSIZE is set + return false; //so easy to do fullscreen here, Linux rocks ! - if (window->status==CV_WINDOW_FULLSCREEN && prop_value==CV_WINDOW_NORMAL) + if (window->status == mode) + return true; + + if (window->status==CV_WINDOW_FULLSCREEN && mode==CV_WINDOW_NORMAL) { gtk_window_unfullscreen(GTK_WINDOW(window->frame)); window->status=CV_WINDOW_NORMAL; - return; + return true; } - if (window->status==CV_WINDOW_NORMAL && prop_value==CV_WINDOW_FULLSCREEN) + if (window->status==CV_WINDOW_NORMAL && mode==CV_WINDOW_FULLSCREEN) { gtk_window_fullscreen(GTK_WINDOW(window->frame)); window->status=CV_WINDOW_FULLSCREEN; - return; + return true; } + + return false; } void cv::setWindowTitle(const String& winname, const String& title) { CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(winname.c_str()); + auto window = icvFindWindowByName(winname.c_str()); if (!window) { @@ -828,7 +863,7 @@ double cvGetPropWindowAutoSize_GTK(const char* name) CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(name); + const auto window = icvFindWindowByName(name); if (!window) return -1; // keep silence here @@ -836,16 +871,22 @@ double cvGetPropWindowAutoSize_GTK(const char* name) return result; } +static double getRatioWindow_(const std::shared_ptr& window); double cvGetRatioWindow_GTK(const char* name) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(name); + const auto window = icvFindWindowByName(name); if (!window) return -1; // keep silence here + return getRatioWindow_(window); +} + +static double getRatioWindow_(const std::shared_ptr& window) +{ #if defined (GTK_VERSION3) double result = static_cast( gtk_widget_get_allocated_width(window->widget)) / gtk_widget_get_allocated_height(window->widget); @@ -862,7 +903,7 @@ double cvGetOpenGlProp_GTK(const char* name) CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(name); + const auto window = icvFindWindowByName(name); if (!window) return -1; // keep silence here @@ -1048,6 +1089,7 @@ static gboolean cvImageWidget_expose(GtkWidget* widget, GdkEventExpose* event, g } #endif //GTK_VERSION3 +static std::shared_ptr namedWindow_(const std::string& name, int flags); CV_IMPL int cvNamedWindow( const char* name, int flags ) { cvInitSystem(name ? 1 : 0,(char**)&name); @@ -1060,8 +1102,16 @@ CV_IMPL int cvNamedWindow( const char* name, int flags ) { return 1; } + auto window = namedWindow_(name, flags); + return window ? 1 : 0; +} - Ptr window = makePtr(name); +static std::shared_ptr namedWindow_(const std::string& name, int flags) +{ + cvInitSystem(0, NULL); + + auto window_ptr = std::make_shared(name); + CvWindow* window = window_ptr.get(); window->flags = flags; window->status = CV_WINDOW_NORMAL;//YV @@ -1116,9 +1166,12 @@ CV_IMPL int cvNamedWindow( const char* name, int flags ) #endif //GTK_VERSION3_4 gtk_widget_show( window->frame ); - gtk_window_set_title( GTK_WINDOW(window->frame), name ); + gtk_window_set_title(GTK_WINDOW(window->frame), name.c_str()); - g_windows.push_back(window); + { + AutoLock lock(getWindowMutex()); + getGTKWindows().push_back(window_ptr); + } bool b_nautosize = ((flags & CV_WINDOW_AUTOSIZE) == 0); gtk_window_set_resizable( GTK_WINDOW(window->frame), b_nautosize ); @@ -1137,7 +1190,7 @@ CV_IMPL int cvNamedWindow( const char* name, int flags ) cvSetOpenGlContext(name); #endif - return 1; + return window_ptr; } @@ -1203,13 +1256,21 @@ CV_IMPL void cvSetOpenGlDrawCallback(const char* name, CvOpenGlDrawCallback call CvWindow::~CvWindow() { + if (frame) + destroy(); +} + +inline void CvWindow::destroy() +{ + CV_LOG_INFO(NULL, "OpenCV/UI: destroying GTK window: " << name); gtk_widget_destroy(frame); + frame = nullptr; } static void checkLastWindow() { // if last window... - if (g_windows.empty()) + if (getGTKWindows().empty()) { #ifdef HAVE_GTHREAD if( thread_started ) @@ -1236,11 +1297,13 @@ static void checkLastWindow() } } -static void icvDeleteWindow( CvWindow* window ) +static +void icvDeleteWindow_( CvWindow* window ) { + AutoLock lock(getWindowMutex()); + auto& g_windows = getGTKWindows(); bool found = false; - for (std::vector< Ptr >::iterator i = g_windows.begin(); - i != g_windows.end(); ++i) + for (auto i = g_windows.begin(); i != g_windows.end(); ++i) { if (i->get() == window) { @@ -1249,8 +1312,7 @@ static void icvDeleteWindow( CvWindow* window ) break; } } - CV_Assert(found && "Can't destroy non-registered window"); - + CV_LOG_IF_WARNING(NULL, !found, "OpenCV/GTK: Can't destroy non-registered window"); checkLastWindow(); } @@ -1259,10 +1321,10 @@ CV_IMPL void cvDestroyWindow( const char* name ) CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); + auto& g_windows = getGTKWindows(); bool found = false; - for (std::vector< Ptr >::iterator i = g_windows.begin(); - i != g_windows.end(); ++i) + for (auto i = g_windows.begin(); i != g_windows.end(); ++i) { if (i->get()->name == name) { @@ -1271,7 +1333,7 @@ CV_IMPL void cvDestroyWindow( const char* name ) break; } } - CV_Assert(found && "Can't destroy non-registered window"); + CV_LOG_IF_ERROR(NULL, !found, "OpenCV/GTK: Can't destroy non-registered window: '" << name << "'"); checkLastWindow(); } @@ -1282,7 +1344,7 @@ cvDestroyAllWindows( void ) { CV_LOCK_MUTEX(); - g_windows.clear(); + getGTKWindows().clear(); checkLastWindow(); } @@ -1305,7 +1367,7 @@ cvShowImage( const char* name, const CvArr* arr ) CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(name); + auto window = icvFindWindowByName(name); if(!window) { cvNamedWindow(name, 1); @@ -1328,16 +1390,24 @@ cvShowImage( const char* name, const CvArr* arr ) } } +static void resizeWindow_(const std::shared_ptr& window, int width, int height); CV_IMPL void cvResizeWindow(const char* name, int width, int height ) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(name); + auto window = icvFindWindowByName(name); if(!window) return; + return resizeWindow_(window, width, height); +} + +static +void resizeWindow_(const std::shared_ptr& window, int width, int height) +{ + CV_Assert(window); CvImageWidget* image_widget = CV_IMAGE_WIDGET( window->widget ); //if(image_widget->flags & CV_WINDOW_AUTOSIZE) //EXIT; @@ -1357,26 +1427,30 @@ CV_IMPL void cvMoveWindow( const char* name, int x, int y ) CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(name); + const auto window = icvFindWindowByName(name); if(!window) return; gtk_window_move( GTK_WINDOW(window->frame), x, y ); } - -static CvTrackbar* -icvFindTrackbarByName( const CvWindow* window, const char* name ) +static +std::shared_ptr icvFindTrackbarByName(const std::shared_ptr& window, const std::string& name) { - for (size_t i = 0; i < window->trackbars.size(); ++i) + CV_Assert(window); + auto& trackbars = window->trackbars; + for(size_t i = 0; i < trackbars.size(); ++i) { - CvTrackbar* trackbar = window->trackbars[i].get(); + auto trackbar = trackbars[i]; + if (!trackbar) + continue; if (trackbar->name == name) return trackbar; } - return NULL; + return std::shared_ptr(); } + static int icvCreateTrackbar( const char* trackbar_name, const char* window_name, int* val, int count, CvTrackbarCallback on_notify, @@ -1390,16 +1464,16 @@ icvCreateTrackbar( const char* trackbar_name, const char* window_name, CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(window_name); + const auto window = icvFindWindowByName(window_name); if(!window) return 0; - CvTrackbar* trackbar = icvFindTrackbarByName(window, trackbar_name); - if (!trackbar) + auto trackbar_ = icvFindTrackbarByName(window, trackbar_name); + if (!trackbar_) { - Ptr trackbar_ = makePtr(trackbar_name); - trackbar = trackbar_.get(); - trackbar->parent = window; + trackbar_ = std::make_shared(trackbar_name); + CvTrackbar* trackbar = trackbar_.get(); + trackbar->parent = window.get(); window->trackbars.push_back(trackbar_); GtkWidget* hscale_box = gtk_hbox_new( FALSE, 10 ); @@ -1418,6 +1492,8 @@ icvCreateTrackbar( const char* trackbar_name, const char* window_name, gtk_widget_show( hscale_box ); } + CvTrackbar* trackbar = trackbar_.get(); CV_DbgAssert(trackbar); + if( val ) { int value = *val; @@ -1444,7 +1520,6 @@ icvCreateTrackbar( const char* trackbar_name, const char* window_name, return 1; } - CV_IMPL int cvCreateTrackbar( const char* trackbar_name, const char* window_name, int* val, int count, CvTrackbarCallback on_notify ) @@ -1453,7 +1528,6 @@ cvCreateTrackbar( const char* trackbar_name, const char* window_name, on_notify, 0, 0); } - CV_IMPL int cvCreateTrackbar2( const char* trackbar_name, const char* window_name, int* val, int count, CvTrackbarCallback2 on_notify2, @@ -1463,6 +1537,52 @@ cvCreateTrackbar2( const char* trackbar_name, const char* window_name, 0, on_notify2, userdata); } +static +std::shared_ptr createTrackbar_( + const std::shared_ptr& window, const std::string& name, + int count, + TrackbarCallback onChange, void* userdata +) +{ + CV_Assert(window); + CV_Assert(!name.empty()); + + if (count <= 0) + CV_Error(Error::StsOutOfRange, "Bad trackbar maximal value"); + + auto trackbar_ = std::make_shared(name); + CvTrackbar* trackbar = trackbar_.get(); + trackbar->parent = window.get(); + window->trackbars.push_back(trackbar_); + + GtkWidget* hscale_box = gtk_hbox_new( FALSE, 10 ); + GtkWidget* hscale_label = gtk_label_new(name.c_str()); + GtkWidget* hscale = gtk_hscale_new_with_range( 0, count, 1 ); + gtk_scale_set_digits( GTK_SCALE(hscale), 0 ); + //gtk_scale_set_value_pos( hscale, GTK_POS_TOP ); + gtk_scale_set_draw_value( GTK_SCALE(hscale), TRUE ); + + trackbar->widget = hscale; + gtk_box_pack_start( GTK_BOX(hscale_box), hscale_label, FALSE, FALSE, 5 ); + gtk_widget_show( hscale_label ); + gtk_box_pack_start( GTK_BOX(hscale_box), hscale, TRUE, TRUE, 5 ); + gtk_widget_show( hscale ); + gtk_box_pack_start( GTK_BOX(window->paned), hscale_box, FALSE, FALSE, 5 ); + gtk_widget_show( hscale_box ); + + trackbar->maxval = count; + trackbar->onChangeCallback = onChange; + trackbar->userdata = userdata; + g_signal_connect(trackbar->widget, "value-changed", + G_CALLBACK(icvOnTrackbar), trackbar); + + // queue a widget resize to trigger a window resize to + // compensate for the addition of trackbars + gtk_widget_queue_resize(GTK_WIDGET(window->widget)); + + return trackbar_; +} + CV_IMPL void cvSetMouseCallback( const char* window_name, CvMouseCallback on_mouse, void* param ) @@ -1471,7 +1591,7 @@ cvSetMouseCallback( const char* window_name, CvMouseCallback on_mouse, void* par CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(window_name); + const auto window = icvFindWindowByName(window_name); if (!window) return; @@ -1487,18 +1607,18 @@ CV_IMPL int cvGetTrackbarPos( const char* trackbar_name, const char* window_name CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(window_name); + const auto window = icvFindWindowByName(window_name); if (!window) return -1; - CvTrackbar* trackbar = icvFindTrackbarByName(window,trackbar_name); + const auto trackbar = icvFindTrackbarByName(window,trackbar_name); if (!trackbar) return -1; return trackbar->pos; } - +static void setTrackbarPos_(const std::shared_ptr& trackbar, int pos); CV_IMPL void cvSetTrackbarPos( const char* trackbar_name, const char* window_name, int pos ) { CV_Assert(window_name && "NULL window name"); @@ -1506,24 +1626,27 @@ CV_IMPL void cvSetTrackbarPos( const char* trackbar_name, const char* window_nam CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(window_name); + const auto window = icvFindWindowByName(window_name); if(!window) return; - CvTrackbar* trackbar = icvFindTrackbarByName(window,trackbar_name); - if( trackbar ) - { - if( pos < trackbar->minval ) - pos = trackbar->minval; - - if( pos > trackbar->maxval ) - pos = trackbar->maxval; - } - else + const auto trackbar = icvFindTrackbarByName(window, trackbar_name); + if (!trackbar) { CV_Error( CV_StsNullPtr, "No trackbar found" ); } + return setTrackbarPos_(trackbar, pos); +} + +static void setTrackbarPos_(const std::shared_ptr& trackbar, int pos) +{ + CV_Assert(trackbar); + CV_CheckLE(trackbar->minval, trackbar->maxval, ""); + + pos = std::max(pos, trackbar->minval); + pos = std::min(pos, trackbar->maxval); + gtk_range_set_value( GTK_RANGE(trackbar->widget), pos ); } @@ -1535,11 +1658,11 @@ CV_IMPL void cvSetTrackbarMax(const char* trackbar_name, const char* window_name CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(window_name); + const auto window = icvFindWindowByName(window_name); if(!window) return; - CvTrackbar* trackbar = icvFindTrackbarByName(window,trackbar_name); + const auto trackbar = icvFindTrackbarByName(window,trackbar_name); if(!trackbar) return; @@ -1556,11 +1679,11 @@ CV_IMPL void cvSetTrackbarMin(const char* trackbar_name, const char* window_name CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(window_name); + const auto window = icvFindWindowByName(window_name); if(!window) return; - CvTrackbar* trackbar = icvFindTrackbarByName(window,trackbar_name); + const auto trackbar = icvFindTrackbarByName(window,trackbar_name); if(!trackbar) return; @@ -1576,7 +1699,7 @@ CV_IMPL void* cvGetWindowHandle( const char* window_name ) CV_LOCK_MUTEX(); - CvWindow* window = icvFindWindowByName(window_name); + const auto window = icvFindWindowByName(window_name); if(!window) return NULL; @@ -1747,6 +1870,10 @@ static void icvOnTrackbar( GtkWidget* widget, gpointer user_data ) trackbar->widget == widget ) { trackbar->pos = pos; + if (trackbar->onChangeCallback) + trackbar->onChangeCallback(pos, trackbar->userdata); + + // deprecated if( trackbar->data ) *trackbar->data = pos; if( trackbar->notify2 ) @@ -1762,7 +1889,14 @@ static gboolean icvOnClose( GtkWidget* widget, GdkEvent* /*event*/, gpointer use if( window->signature == CV_WINDOW_MAGIC_VAL && window->frame == widget ) { - icvDeleteWindow(window); + try + { + icvDeleteWindow_(window); + } + catch (...) + { + CV_LOG_WARNING(NULL, "OpenCV/GTK: unexpected C++ exception in icvDeleteWindow_"); + } } return TRUE; } @@ -1916,7 +2050,7 @@ CV_IMPL int cvWaitKey( int delay ) expired = !g_cond_timed_wait(cond_have_key, last_key_mutex, &timer); } else{ - if (g_windows.empty()) + if (getGTKWindows().empty()) { CV_LOG_WARNING(NULL, "cv::waitKey() is called without timeout and missing active windows. Ignoring"); } @@ -1928,7 +2062,8 @@ CV_IMPL int cvWaitKey( int delay ) } my_last_key = last_key; g_mutex_unlock(last_key_mutex); - if(expired || g_windows.empty()){ + if (expired || getGTKWindows().empty()) + { return -1; } return my_last_key; @@ -1941,7 +2076,7 @@ CV_IMPL int cvWaitKey( int delay ) if( delay > 0 ) timer = g_timeout_add( delay, icvAlarm, &expired ); last_key = -1; - while( gtk_main_iteration_do(TRUE) && last_key < 0 && !expired && (delay > 0 || !g_windows.empty())) + while( gtk_main_iteration_do(TRUE) && last_key < 0 && !expired && (delay > 0 || !getGTKWindows().empty())) ; if( delay > 0 && !expired ) @@ -1950,8 +2085,335 @@ CV_IMPL int cvWaitKey( int delay ) return last_key; } +namespace cv { namespace impl { + +using namespace cv::highgui_backend; + +class GTKTrackbar; + +class GTKWindow + : public UIWindow + , public std::enable_shared_from_this +{ +protected: + const std::string name_; + std::weak_ptr window_; + std::map > trackbars_; +public: + GTKWindow(const std::string& name, const std::shared_ptr& window) + : name_(name) + , window_(window) + { + // nothing + } + + ~GTKWindow() CV_OVERRIDE + { + if (!window_.expired()) + destroy(); + CV_LOG_DEBUG(NULL, "OpenCV/UI/GTK: GTKWindow(" << name_ << ") is disposed"); + } + + const std::string& getID() const CV_OVERRIDE { return name_; } + + bool isActive() const CV_OVERRIDE { return !window_.expired(); } + + void destroy() CV_OVERRIDE + { + cv::AutoLock lock(getWindowMutex()); + if (!window_.expired()) + { + auto window = window_.lock(); + if (window) + window->destroy(); + window_.reset(); + } + } + + void imshow(InputArray image) CV_OVERRIDE + { + auto window = window_.lock(); + CV_Assert(window); + CvImageWidget* image_widget = CV_IMAGE_WIDGET(window->widget); + CV_Assert(image_widget); + Mat img = image.getMat(); + CvMat c_img = cvMat(img); // TODO Drop C-API + cvImageWidgetSetImage(image_widget, &c_img); + } + + double getProperty(int prop) const CV_OVERRIDE + { + auto window = window_.lock(); + CV_Assert(window); + // see cvGetWindowProperty + switch (prop) + { + case CV_WND_PROP_FULLSCREEN: + return (double)window->status; + + case CV_WND_PROP_AUTOSIZE: + return (window->flags & CV_WINDOW_AUTOSIZE) ? 1.0 : 0.0; + + case CV_WND_PROP_ASPECTRATIO: + return getRatioWindow_(window); + +#ifdef HAVE_OPENGL + case CV_WND_PROP_OPENGL: + return window->useGl ? 1.0 : 0.0; +#endif + + default: + break; + } + return std::numeric_limits::quiet_NaN(); + } + + bool setProperty(int prop, double value) CV_OVERRIDE + { + auto window = window_.lock(); + CV_Assert(window); + // see cvSetWindowProperty + switch (prop) + { + case CV_WND_PROP_FULLSCREEN: + if (value != CV_WINDOW_NORMAL && value != CV_WINDOW_FULLSCREEN) // bad arg + break; + setModeWindow_(window, value); + return true; + + default: + break; + } + return false; + } + + void resize(int width, int height) CV_OVERRIDE + { + auto window = window_.lock(); + CV_Assert(window); + resizeWindow_(window, width, height); + } + + void move(int x, int y) CV_OVERRIDE + { + auto window = window_.lock(); + CV_Assert(window); + gtk_window_move(GTK_WINDOW(window->frame), x, y); + } + + Rect getImageRect() const CV_OVERRIDE + { + auto window = window_.lock(); + CV_Assert(window); + return getImageRect_(window); + } + + void setTitle(const std::string& title) CV_OVERRIDE + { + auto window = window_.lock(); + CV_Assert(window); + gtk_window_set_title(GTK_WINDOW(window->frame), title.c_str()); + } + + void setMouseCallback(MouseCallback onMouse, void* userdata /*= 0*/) CV_OVERRIDE + { + auto window = window_.lock(); + CV_Assert(window); + window->on_mouse = onMouse; + window->on_mouse_param = userdata; + } + + std::shared_ptr createTrackbar( + const std::string& name, + int count, + TrackbarCallback onChange /*= 0*/, + void* userdata /*= 0*/ + ) CV_OVERRIDE + { + auto window = window_.lock(); + CV_Assert(window); + CV_LOG_INFO(NULL, "OpenCV/UI: Creating GTK trackbar at '" << name_ << "': '" << name << "'"); + auto trackbar = createTrackbar_(window, name, count, onChange, userdata); + auto ui_trackbar = std::make_shared(name, trackbar, shared_from_this()); + { + cv::AutoLock lock(getWindowMutex()); + trackbars_.emplace(name, ui_trackbar); + } + return std::static_pointer_cast(ui_trackbar); + } + + std::shared_ptr findTrackbar(const std::string& name) CV_OVERRIDE + { + cv::AutoLock lock(getWindowMutex()); + auto i = trackbars_.find(name); + if (i != trackbars_.end()) + { + return std::static_pointer_cast(i->second); + } + return std::shared_ptr(); + } +}; // GTKWindow + + +class GTKTrackbar : public UITrackbar +{ +protected: + /*const*/ std::string name_; + std::weak_ptr trackbar_; + std::weak_ptr parent_; + std::map > trackbars_; +public: + GTKTrackbar(const std::string& name, const std::shared_ptr& trackbar, const std::shared_ptr& parent) + : trackbar_(trackbar) + , parent_(parent) + { + name_ = std::string("<") + name + ">@" + parent->getID(); + } + + ~GTKTrackbar() CV_OVERRIDE + { + if (!trackbar_.expired()) + destroy(); + CV_LOG_DEBUG(NULL, "OpenCV/UI/GTK: GTKTrackbar(" << name_ << ") is disposed"); + } + + const std::string& getID() const CV_OVERRIDE { return name_; } + + bool isActive() const CV_OVERRIDE { return !trackbar_.expired(); } + + void destroy() CV_OVERRIDE + { + // nothing (destroyed with parent window, dedicated trackbar removal is not supported) + } + + int getPos() const CV_OVERRIDE + { + auto trackbar = trackbar_.lock(); + CV_Assert(trackbar); + return trackbar->pos; + } + void setPos(int pos) CV_OVERRIDE + { + auto trackbar = trackbar_.lock(); + CV_Assert(trackbar); + return setTrackbarPos_(trackbar, pos); + } + + cv::Range getRange() const CV_OVERRIDE + { + auto trackbar = trackbar_.lock(); + CV_Assert(trackbar); + return cv::Range(trackbar->minval, trackbar->maxval); + } + + void setRange(const cv::Range& range) CV_OVERRIDE + { + auto trackbar = trackbar_.lock(); + CV_Assert(trackbar); + CV_CheckLE(range.start, range.end, "Invalid trackbar range"); + gtk_range_set_range(GTK_RANGE(trackbar->widget), range.start, range.end); + } +}; // GTKTrackbar + + +class GTKBackendUI : public UIBackend +{ +public: + ~GTKBackendUI() CV_OVERRIDE + { + destroyAllWindows(); + } + + void destroyAllWindows() CV_OVERRIDE + { + cvDestroyAllWindows(); + } + + // namedWindow + virtual std::shared_ptr createWindow( + const std::string& winname, + int flags + ) CV_OVERRIDE + { + CV_LOG_INFO(NULL, "OpenCV/UI: Creating GTK window: " << winname << " (" << flags << ")"); + auto window = namedWindow_(winname, flags); + auto ui_window = std::make_shared(winname, window); + return ui_window; + } + + int waitKeyEx(int delay) CV_OVERRIDE + { + return cvWaitKey(delay); + } + int pollKey() CV_OVERRIDE + { + return cvWaitKey(1); // TODO + } +}; // GTKBackendUI + +static +std::shared_ptr& getInstance() +{ + static std::shared_ptr g_instance = std::make_shared(); + return g_instance; +} + +} // namespace impl + +#ifndef BUILD_PLUGIN +namespace highgui_backend { + +std::shared_ptr createUIBackendGTK() +{ + return impl::getInstance(); +} + +} // namespace highgui_backend +#endif + +} // namespace + +#ifdef BUILD_PLUGIN + +#define ABI_VERSION 0 +#define API_VERSION 0 +#include "plugin_api.hpp" + +static +CvResult cv_getInstance(CV_OUT CvPluginUIBackend* handle) CV_NOEXCEPT +{ + try + { + if (!handle) + return CV_ERROR_FAIL; + *handle = cv::impl::getInstance().get(); + return CV_ERROR_OK; + } + catch (...) + { + return CV_ERROR_FAIL; + } +} + +static const OpenCV_UI_Plugin_API plugin_api = +{ + { + sizeof(OpenCV_UI_Plugin_API), ABI_VERSION, API_VERSION, + CV_VERSION_MAJOR, CV_VERSION_MINOR, CV_VERSION_REVISION, CV_VERSION_STATUS, + "GTK" CVAUX_STR(GTK_MAJOR_VERSION) " OpenCV UI plugin" + }, + { + /* 1*/cv_getInstance + } +}; + +const OpenCV_UI_Plugin_API* CV_API_CALL opencv_ui_plugin_init_v0(int requested_abi_version, int requested_api_version, void* /*reserved=NULL*/) CV_NOEXCEPT +{ + if (requested_abi_version == ABI_VERSION && requested_api_version <= API_VERSION) + return &plugin_api; + return NULL; +} + +#endif // BUILD_PLUGIN #endif // HAVE_GTK -#endif // _WIN32 - -/* End of file. */ diff --git a/modules/highgui/test/test_gui.cpp b/modules/highgui/test/test_gui.cpp index c973771e98..6bf634b500 100644 --- a/modules/highgui/test/test_gui.cpp +++ b/modules/highgui/test/test_gui.cpp @@ -47,13 +47,18 @@ namespace opencv_test { namespace { inline void verify_size(const std::string &nm, const cv::Mat &img) { EXPECT_NO_THROW(imshow(nm, img)); - EXPECT_EQ(-1, waitKey(500)); + EXPECT_EQ(-1, waitKey(200)); Rect rc; EXPECT_NO_THROW(rc = getWindowImageRect(nm)); EXPECT_EQ(rc.size(), img.size()); } -#if !defined HAVE_GTK && !defined HAVE_QT && !defined HAVE_WIN32UI && !defined HAVE_COCOA +#if (!defined(ENABLE_PLUGINS) \ + && !defined HAVE_GTK \ + && !defined HAVE_QT \ + && !defined HAVE_WIN32UI \ + && !defined HAVE_COCOA \ + ) TEST(Highgui_GUI, DISABLED_regression) #else TEST(Highgui_GUI, regression) @@ -126,11 +131,15 @@ static void Foo(int, void* counter) } } -#if !defined HAVE_GTK && !defined HAVE_QT && !defined HAVE_WIN32UI -// && !defined HAVE_COCOA - TODO: fails on Mac? -TEST(Highgui_GUI, DISABLED_trackbar) +#if (!defined(ENABLE_PLUGINS) \ + && !defined HAVE_GTK \ + && !defined HAVE_QT \ + && !defined HAVE_WIN32UI \ + ) \ + || defined(__APPLE__) // test fails on Mac (cocoa) +TEST(Highgui_GUI, DISABLED_trackbar_unsafe) #else -TEST(Highgui_GUI, trackbar) +TEST(Highgui_GUI, trackbar_unsafe) #endif { int value = 50; @@ -142,9 +151,52 @@ TEST(Highgui_GUI, trackbar) ASSERT_NO_THROW(namedWindow(window_name)); EXPECT_EQ((int)1, createTrackbar(trackbar_name, window_name, &value, 100, Foo, &callback_count)); EXPECT_EQ(value, getTrackbarPos(trackbar_name, window_name)); - EXPECT_EQ(0, callback_count); + EXPECT_GE(callback_count, 0); + EXPECT_LE(callback_count, 1); + int callback_count_base = callback_count; EXPECT_NO_THROW(setTrackbarPos(trackbar_name, window_name, 90)); - EXPECT_EQ(1, callback_count); + EXPECT_EQ(callback_count_base + 1, callback_count); + EXPECT_EQ(90, value); + EXPECT_EQ(90, getTrackbarPos(trackbar_name, window_name)); + EXPECT_NO_THROW(destroyAllWindows()); +} + +static +void testTrackbarCallback(int pos, void* param) +{ + CV_Assert(param); + int* status = (int*)param; + status[0] = pos; + status[1]++; +} + +#if (!defined(ENABLE_PLUGINS) \ + && !defined HAVE_GTK \ + && !defined HAVE_QT \ + && !defined HAVE_WIN32UI \ + ) \ + || defined(__APPLE__) // test fails on Mac (cocoa) +TEST(Highgui_GUI, DISABLED_trackbar) +#else +TEST(Highgui_GUI, trackbar) +#endif +{ + int status[2] = {-1, 0}; // pos, counter + const std::string window_name("trackbar_test_window"); + const std::string trackbar_name("trackbar"); + + EXPECT_NO_THROW(destroyAllWindows()); + ASSERT_NO_THROW(namedWindow(window_name)); + EXPECT_EQ((int)1, createTrackbar(trackbar_name, window_name, NULL, 100, testTrackbarCallback, status)); + EXPECT_EQ(0, getTrackbarPos(trackbar_name, window_name)); + int callback_count = status[1]; + EXPECT_GE(callback_count, 0); + EXPECT_LE(callback_count, 1); + int callback_count_base = callback_count; + EXPECT_NO_THROW(setTrackbarPos(trackbar_name, window_name, 90)); + callback_count = status[1]; + EXPECT_EQ(callback_count_base + 1, callback_count); + int value = status[0]; EXPECT_EQ(90, value); EXPECT_EQ(90, getTrackbarPos(trackbar_name, window_name)); EXPECT_NO_THROW(destroyAllWindows());