highgui: backends and plugins
authorAlexander Alekhin <alexander.a.alekhin@gmail.com>
Sat, 1 May 2021 13:13:58 +0000 (13:13 +0000)
committerAlexander Alekhin <alexander.alekhin@intel.com>
Mon, 24 May 2021 16:12:02 +0000 (16:12 +0000)
28 files changed:
cmake/OpenCVFindLibsGUI.cmake
cmake/OpenCVMinDepVersions.cmake
cmake/OpenCVModule.cmake
cmake/templates/cvconfig.h.in
modules/CMakeLists.txt
modules/core/include/opencv2/core/utils/plugin_loader.private.hpp
modules/highgui/CMakeLists.txt
modules/highgui/cmake/detect_gtk.cmake [new file with mode: 0644]
modules/highgui/cmake/init.cmake [new file with mode: 0644]
modules/highgui/cmake/plugin.cmake [new file with mode: 0644]
modules/highgui/misc/plugins/build_plugins.sh [new file with mode: 0755]
modules/highgui/misc/plugins/plugin_gtk/CMakeLists.txt [new file with mode: 0644]
modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk2 [new file with mode: 0644]
modules/highgui/misc/plugins/plugin_gtk/Dockerfile-ubuntu-gtk3 [new file with mode: 0644]
modules/highgui/misc/plugins/plugin_gtk/build.sh [new file with mode: 0755]
modules/highgui/src/backend.cpp [new file with mode: 0644]
modules/highgui/src/backend.hpp [new file with mode: 0644]
modules/highgui/src/factory.hpp [new file with mode: 0644]
modules/highgui/src/plugin_api.hpp [new file with mode: 0644]
modules/highgui/src/plugin_wrapper.impl.hpp [new file with mode: 0644]
modules/highgui/src/precomp.hpp
modules/highgui/src/registry.hpp [new file with mode: 0644]
modules/highgui/src/registry.impl.hpp [new file with mode: 0644]
modules/highgui/src/window.cpp
modules/highgui/src/window_QT.cpp
modules/highgui/src/window_QT.h
modules/highgui/src/window_gtk.cpp
modules/highgui/test/test_gui.cpp

index e3593d4dc9b34cb42f62cf52217b1354fcc68a6f..8030e8b0c0fca4a4ef9004bdf04d1e566a282c88 100644 (file)
@@ -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)
index ce0c0ba8165c600937d004e98157ef478555031e..db225e2ab5b4261330786957eda65faf204da8d8 100644 (file)
@@ -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)
index 0e783dfec68ed8685842d2e9692ffc3cd6e99958..7c48aad9c29529f72ca043a198bed70c6eb6bf3e 100644 (file)
@@ -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)
index c0f073604bc806d0c712f61503e04a97291fa53d..e79e1ec0a1bc93d7e00732ef3bcd58acb38f6bd8 100644 (file)
@@ -28,9 +28,6 @@
 /* Clp support */
 #cmakedefine HAVE_CLP
 
-/* Cocoa API */
-#cmakedefine HAVE_COCOA
-
 /* NVIDIA CUDA Runtime API*/
 #cmakedefine HAVE_CUDA
 
 /* 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
 
 /* 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
 
index 6a8004036b280f4482b6b75a7a20070d8368c620..f2389d0c1b6347428ec812b3f1b6afacf26de909 100644 (file)
@@ -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)
index bc3ae4d08a7a4d0ec9b179521fbf91ba2309357f..d6390fc74a48a69a1692dcf50db372e5b121150f 100644 (file)
@@ -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
 }
 
index 7a546616a4238aa35ebc2eaf40254a567e815ca6..9b68b4672e0419f208263d556523b905e490b276 100644 (file)
@@ -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 (file)
index 0000000..7c5939e
--- /dev/null
@@ -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 (file)
index 0000000..1a115f2
--- /dev/null
@@ -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 (file)
index 0000000..6e0ddd2
--- /dev/null
@@ -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 (executable)
index 0000000..a27f4a0
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/bash
+
+set -e
+
+if [ -z $1 ] ; then
+    echo "$0 <destination directory>"
+    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 (file)
index 0000000..ad86905
--- /dev/null
@@ -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 (file)
index 0000000..81836cb
--- /dev/null
@@ -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 (file)
index 0000000..2c6625a
--- /dev/null
@@ -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 (executable)
index 0000000..5804869
--- /dev/null
@@ -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 (file)
index 0000000..4c0de05
--- /dev/null
@@ -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 <opencv2/core/utils/configuration.private.hpp>
+#include <opencv2/core/utils/logger.defines.hpp>
+#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 <opencv2/core/utils/logger.hpp>
+
+
+#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<UIBackend> 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<UIBackend> 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<UIBackend>();
+}
+
+static inline
+std::shared_ptr<UIBackend> createDefaultUIBackend()
+{
+    CV_LOG_DEBUG(NULL, "UI: Initializing backend...");
+    return createUIBackend();
+}
+
+std::shared_ptr<UIBackend>& getCurrentUIBackend()
+{
+    static std::shared_ptr<UIBackend> g_currentUIBackend = createDefaultUIBackend();
+    return g_currentUIBackend;
+}
+
+void setUIBackend(const std::shared_ptr<UIBackend>& 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<UIBackend> 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 (file)
index 0000000..14c88b2
--- /dev/null
@@ -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 <memory>
+#include <map>
+
+namespace cv { namespace highgui_backend {
+
+class CV_EXPORTS UIWindowBase
+{
+public:
+    typedef std::shared_ptr<UIWindowBase> Ptr;
+    typedef std::weak_ptr<UIWindowBase> 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<UITrackbar> createTrackbar(
+        const std::string& name,
+        int count,
+        TrackbarCallback onChange /*= 0*/,
+        void* userdata /*= 0*/
+    ) = 0;
+
+    virtual std::shared_ptr<UITrackbar> 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<UIWindow> createWindow(
+        const std::string& winname,
+        int flags
+    ) = 0;
+
+    virtual int waitKeyEx(int delay /*= 0*/) = 0;
+    virtual int pollKey() = 0;
+};
+
+std::shared_ptr<UIBackend>& getCurrentUIBackend();
+void setUIBackend(const std::shared_ptr<UIBackend>& api);
+bool setUIBackend(const std::string& backendName);
+
+#ifndef BUILD_PLUGIN
+
+#ifdef HAVE_GTK
+std::shared_ptr<UIBackend> createUIBackendGTK();
+#endif
+
+#if 0  // TODO: defined HAVE_QT
+std::shared_ptr<UIBackend> 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 (file)
index 0000000..c40358b
--- /dev/null
@@ -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<cv::highgui_backend::UIBackend> create() const = 0;
+};
+
+
+class StaticBackendFactory CV_FINAL: public IUIBackendFactory
+{
+protected:
+    std::function<std::shared_ptr<cv::highgui_backend::UIBackend>(void)> create_fn_;
+
+public:
+    StaticBackendFactory(std::function<std::shared_ptr<cv::highgui_backend::UIBackend>(void)>&& create_fn)
+        : create_fn_(create_fn)
+    {
+        // nothing
+    }
+
+    ~StaticBackendFactory() CV_OVERRIDE {}
+
+    std::shared_ptr<cv::highgui_backend::UIBackend> create() const CV_OVERRIDE
+    {
+        return create_fn_();
+    }
+};
+
+//
+// PluginUIBackendFactory is implemented in plugin_wrapper
+//
+
+std::shared_ptr<IUIBackendFactory> 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 (file)
index 0000000..fb57b75
--- /dev/null
@@ -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 <opencv2/core/cvdef.h>
+#include <opencv2/core/llapi/llapi.h>
+
+#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 (file)
index 0000000..e68f73c
--- /dev/null
@@ -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<PluginUIBackend>
+{
+protected:
+    void initPluginAPI()
+    {
+        const char* init_name = "opencv_ui_plugin_init_v0";
+        FN_opencv_ui_plugin_init_t fn_init = reinterpret_cast<FN_opencv_ui_plugin_init_t>(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<cv::plugin::impl::DynamicLib> lib_;
+    const OpenCV_UI_Plugin_API* plugin_api_;
+
+    PluginUIBackend(const std::shared_ptr<cv::plugin::impl::DynamicLib>& lib)
+        : lib_(lib)
+        , plugin_api_(NULL)
+    {
+        initPluginAPI();
+    }
+
+    std::shared_ptr<cv::highgui_backend::UIBackend> 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<cv::highgui_backend::UIBackend>(instancePtr, [](cv::highgui_backend::UIBackend*){});  // empty deleter
+            }
+        }
+        return std::shared_ptr<cv::highgui_backend::UIBackend>();
+    }
+};
+
+
+class PluginUIBackendFactory CV_FINAL: public IUIBackendFactory
+{
+public:
+    std::string baseName_;
+    std::shared_ptr<PluginUIBackend> backend;
+    bool initialized;
+public:
+    PluginUIBackendFactory(const std::string& baseName)
+        : baseName_(baseName)
+        , initialized(false)
+    {
+        // nothing, plugins are loaded on demand
+    }
+
+    std::shared_ptr<cv::highgui_backend::UIBackend> create() const CV_OVERRIDE
+    {
+        if (!initialized)
+        {
+            const_cast<PluginUIBackendFactory*>(this)->initBackend();
+        }
+        if (backend)
+            return backend->create();
+        return std::shared_ptr<cv::highgui_backend::UIBackend>();
+    }
+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<FileSystemPath_t> 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<FileSystemPath_t> paths;
+    // TODO OPENCV_PLUGIN_PATH
+    const std::vector<std::string> paths_ = getConfigurationParameterPaths("OPENCV_CORE_PLUGIN_PATH", std::vector<std::string>());
+    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<FileSystemPath_t> 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<std::string> 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<cv::plugin::impl::DynamicLib>(plugin);
+        if (!lib->isLoaded())
+        {
+            continue;
+        }
+        try
+        {
+            auto pluginBackend = std::make_shared<PluginUIBackend>(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<IUIBackendFactory> createPluginUIBackendFactory(const std::string& baseName)
+{
+#if OPENCV_HAVE_FILESYSTEM_SUPPORT && defined(ENABLE_PLUGINS)
+    return std::make_shared<impl::PluginUIBackendFactory>(baseName);
+#else
+    CV_UNUSED(baseName);
+    return std::shared_ptr<IUIBackendFactory>();
+#endif
+}
+
+}}  // namespace
index a5b176c9dd9188eaad5d506b30867c987e491fc6..275cc556ae53c23d67237c478f3efdebfca8320b 100644 (file)
 #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 (file)
index 0000000..77c1234
--- /dev/null
@@ -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-<index*10> - default builtin priority
+                      // 0 - disabled (OPENCV_UI_PRIORITY_<name> = 0)
+                      // >10000 - prioritized list (OPENCV_UI_PRIORITY_LIST)
+    std::string name;
+    std::shared_ptr<IUIBackendFactory> backendFactory;
+};
+
+const std::vector<BackendInfo>& 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 (file)
index 0000000..a2e4dbe
--- /dev/null
@@ -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<cv::highgui_backend::StaticBackendFactory>([=] () -> std::shared_ptr<cv::highgui_backend::UIBackend> { return createBackendAPI(); }) \
+},
+
+static
+std::vector<BackendInfo>& getBuiltinBackendsInfo()
+{
+    static std::vector<BackendInfo> 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<BackendInfo> 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<std::string> tokenize_string(const std::string& input, char token)
+    {
+        std::vector<std::string> 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<std::string> 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<BackendInfo>& getEnabledBackends() const { return enabledBackends; }
+};
+
+
+const std::vector<BackendInfo>& getBackendsInfo()
+{
+    return cv::highgui_backend::UIBackendRegistry::getInstance().getEnabledBackends();
+}
+
+}} // namespace
index d2cf1e1e4826cc76d1ac4fe1040b0555b4192164..782480805bb9feb02c260c7646fee293275b41b0 100644 (file)
 //M*/
 
 #include "precomp.hpp"
-#include <map>
+#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<std::string, highgui_backend::UIWindowBase::Ptr> WindowsMap_t;
+static WindowsMap_t& getWindowsMap()
+{
+    static WindowsMap_t g_windowsMap;
+    return g_windowsMap;
+}
+
+static std::shared_ptr<UIWindow> 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<UIWindow>();
+            }
+            auto window = std::dynamic_pointer_cast<UIWindow>(ui_base);
+            return window;
+        }
+    }
+    return std::shared_ptr<UIWindow>();
+}
+
+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<UITrackbar> 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<TrackbarCallbackWithData> > 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<UIWindow>(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<TrackbarCallbackWithData>(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<UIWindow>(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);
index 1600bd917f2b92c0bf7235886831cb6a92526921..60d7d69a597927ddc3b4233ba9bbe94dc1ac4ac5 100644 (file)
@@ -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);
index dbeacf2edff65e30596099fdc86142fccc4b7a1f..398f3869f88ad5db90e3159a9feb34d8043a94e4 100644 (file)
@@ -256,7 +256,7 @@ private:
     QPointer<QPushButton > label;
     CvTrackbarCallback callback;
     CvTrackbarCallback2 callback2;//look like it is use by python binding
-    int* dataSlider;
+    int* dataSlider;  // deprecated
     void* userdata;
 };
 
index 073b3404432a7b6da3fee93c1f076f990cffa711..9e011f4f188bf9750836053073be4ff2bc841782 100644 (file)
@@ -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<CvTrackbar> > trackbars;
+    std::vector< std::shared_ptr<CvTrackbar> > trackbars;
 
 #ifdef HAVE_OPENGL
     bool useGl;
@@ -600,15 +603,15 @@ GCond*                               cond_have_key = NULL;
 GThread*                          window_thread = NULL;
 #endif
 
-static cv::Mutex& getWindowMutex()
+static int             last_key = -1;
+
+static
+std::vector< std::shared_ptr<CvWindow> >& getGTKWindows()
 {
-    static cv::Mutex* g_window_mutex = new cv::Mutex();
-    return *g_window_mutex;
+    static std::vector< std::shared_ptr<CvWindow> > g_windows;
+    return g_windows;
 }
 
-static int             last_key = -1;
-static std::vector< Ptr<CvWindow> > g_windows;
-
 CV_IMPL int cvInitSystem( int argc, char** argv )
 {
     static int wasInitialized = 0;
@@ -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<CvWindow> 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<CvWindow>();
 }
 
+static inline
+std::shared_ptr<CvWindow> 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<CvWindow>& 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<CvWindow>& 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<CvWindow>& 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<CvWindow>& 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<CvWindow>& 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<CvWindow>& window)
+{
 #if defined (GTK_VERSION3)
     double result = static_cast<double>(
         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<CvWindow> 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<CvWindow> window = makePtr<CvWindow>(name);
+static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags)
+{
+    cvInitSystem(0, NULL);
+
+    auto window_ptr = std::make_shared<CvWindow>(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<CvWindow> >::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<CvWindow> >::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<CvWindow>& 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<CvWindow>& 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<CvTrackbar> icvFindTrackbarByName(const std::shared_ptr<CvWindow>& 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<CvTrackbar>();
 }
 
+
 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<CvTrackbar> trackbar_ = makePtr<CvTrackbar>(trackbar_name);
-        trackbar = trackbar_.get();
-        trackbar->parent = window;
+        trackbar_ = std::make_shared<CvTrackbar>(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<CvTrackbar> createTrackbar_(
+    const std::shared_ptr<CvWindow>& 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<CvTrackbar>(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<CvTrackbar>& 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<CvTrackbar>& 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 {
 
-#endif  // HAVE_GTK
-#endif  // _WIN32
+using namespace cv::highgui_backend;
+
+class GTKTrackbar;
+
+class GTKWindow
+        : public UIWindow
+        , public std::enable_shared_from_this<GTKWindow>
+{
+protected:
+    const std::string name_;
+    std::weak_ptr<CvWindow> window_;
+    std::map<std::string, std::shared_ptr<GTKTrackbar> > trackbars_;
+public:
+    GTKWindow(const std::string& name, const std::shared_ptr<CvWindow>& 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;
 
-/* End of file. */
+        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<double>::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<UITrackbar> 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<GTKTrackbar>(name, trackbar, shared_from_this());
+        {
+            cv::AutoLock lock(getWindowMutex());
+            trackbars_.emplace(name, ui_trackbar);
+        }
+        return std::static_pointer_cast<UITrackbar>(ui_trackbar);
+    }
+
+    std::shared_ptr<UITrackbar> 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<UITrackbar>(i->second);
+        }
+        return std::shared_ptr<UITrackbar>();
+    }
+};  // GTKWindow
+
+
+class GTKTrackbar : public UITrackbar
+{
+protected:
+    /*const*/ std::string name_;
+    std::weak_ptr<CvTrackbar> trackbar_;
+    std::weak_ptr<GTKWindow> parent_;
+    std::map<std::string, std::shared_ptr<GTKTrackbar> > trackbars_;
+public:
+    GTKTrackbar(const std::string& name, const std::shared_ptr<CvTrackbar>& trackbar, const std::shared_ptr<GTKWindow>& 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<UIWindow> 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<GTKWindow>(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<GTKBackendUI>& getInstance()
+{
+    static std::shared_ptr<GTKBackendUI> g_instance = std::make_shared<GTKBackendUI>();
+    return g_instance;
+}
+
+} // namespace impl
+
+#ifndef BUILD_PLUGIN
+namespace highgui_backend {
+
+std::shared_ptr<UIBackend> 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
index c973771e987bd5aa15cdf36f32a216c1a2c26d45..6bf634b5001d3ca0c9a33ae22c1e59ab3b569787 100644 (file)
@@ -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(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));
-    EXPECT_EQ(1, callback_count);
+    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());