/*M/////////////////////////////////////////////////////////////////////////////////////// // // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. // // By downloading, copying, installing or using the software you agree to this license. // If you do not agree to this license, do not download, install, // copy or use the software. // // // Intel License Agreement // For Open Source Computer Vision Library // // Copyright (C) 2000, Intel Corporation, all rights reserved. // Third party copyrights are property of their respective owners. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // * Redistribution's of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // * Redistribution's in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // * The name of Intel Corporation may not be used to endorse or promote products // derived from this software without specific prior written permission. // // This software is provided by the copyright holders and contributors "as is" and // any express or implied warranties, including, but not limited to, the implied // warranties of merchantability and fitness for a particular purpose are disclaimed. // In no event shall the Intel Corporation or contributors be liable for any direct, // indirect, incidental, special, exemplary, or consequential damages // (including, but not limited to, procurement of substitute goods or services; // loss of use, data, or profits; or business interruption) however caused // and on any theory of liability, whether in contract, strict liability, // or tort (including negligence or otherwise) arising in any way out of // the use of this software, even if advised of the possibility of such damage. // //M*/ #include "precomp.hpp" #include "backend.hpp" #if defined (HAVE_GTK) #include #if (GTK_MAJOR_VERSION == 3) && defined(HAVE_OPENGL) #undef HAVE_OPENGL // no support with GTK3 #endif #if defined(HAVE_OPENGL) && !defined(HAVE_GTKGLEXT) #undef HAVE_OPENGL // gtkglext is required #endif #include #include #include #if (GTK_MAJOR_VERSION == 3) #define GTK_VERSION3 1 #endif //GTK_MAJOR_VERSION >= 3 #if (GTK_MAJOR_VERSION > 3 || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 4)) #define GTK_VERSION3_4 1 #endif #ifdef HAVE_OPENGL #include #include #include #endif #include #include "opencv2/imgproc.hpp" using namespace cv; #ifndef BIT_ALLIN #define BIT_ALLIN(x,y) ( ((x)&(y)) == (y) ) #endif #ifndef BIT_MAP #define BIT_MAP(x,y,z) ( ((x)&(y)) ? (z) : 0 ) #endif // TODO Fix the initial window size when flags=0. Right now the initial window is by default // 320x240 size. A better default would be actual size of the image. Problem // is determining desired window size with trackbars while still allowing resizing. // // Gnome Totem source may be of use here, see bacon_video_widget_set_scale_ratio // in totem/src/backend/bacon-video-widget-xine.c //////////////////////////////////////////////////////////// // CvImageWidget GTK Widget Public API //////////////////////////////////////////////////////////// typedef struct _CvImageWidget CvImageWidget; typedef struct _CvImageWidgetClass CvImageWidgetClass; struct _CvImageWidget { GtkWidget widget; CvMat * original_image; CvMat * scaled_image; int flags; }; struct _CvImageWidgetClass { GtkWidgetClass parent_class; }; /** Allocate new image viewer widget */ GtkWidget* cvImageWidgetNew (int flags); // 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) #define CV_IS_IMAGE_WIDGET(obj) G_TYPE_CHECK_INSTANCE_TYPE (obj, cvImageWidget_get_type ()) ///////////////////////////////////////////////////////////////////////////// // Private API //////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// GType cvImageWidget_get_type (void); static GtkWidgetClass * parent_class = NULL; // flag to help size initial window #define CV_WINDOW_NO_IMAGE 2 /** Set the image to display in the widget */ static void cvImageWidgetSetImage(CvImageWidget * widget, const CvArr *arr) { CvMat * mat, stub; int origin=0; //printf("cvImageWidgetSetImage\n"); if( CV_IS_IMAGE_HDR( arr )) origin = ((IplImage*)arr)->origin; mat = cvGetMat(arr, &stub); if(widget->original_image && !CV_ARE_SIZES_EQ(mat, widget->original_image)){ cvReleaseMat( &widget->original_image ); } if(!widget->original_image){ widget->original_image = cvCreateMat( mat->rows, mat->cols, CV_8UC3 ); gtk_widget_queue_resize( GTK_WIDGET( widget ) ); } CV_Assert(origin == 0); convertToShow(cv::cvarrToMat(arr), widget->original_image); if(widget->scaled_image){ cvResize( widget->original_image, widget->scaled_image, CV_INTER_AREA ); } // window does not refresh without this gtk_widget_queue_draw( GTK_WIDGET(widget) ); } GtkWidget* 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; return GTK_WIDGET (image_widget); } static void cvImageWidget_realize (GtkWidget *widget) { GdkWindowAttr attributes; gint attributes_mask; #if defined(GTK_VERSION3) GtkAllocation allocation; gtk_widget_get_allocation(widget, &allocation); #endif //GTK_VERSION3 //printf("cvImageWidget_realize\n"); g_return_if_fail (widget != NULL); g_return_if_fail (CV_IS_IMAGE_WIDGET (widget)); gtk_widget_set_realized(widget, TRUE); #if defined(GTK_VERSION3) attributes.x = allocation.x; attributes.y = allocation.y; attributes.width = allocation.width; attributes.height = allocation.height; #else attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; #endif //GTK_VERSION3 attributes.wclass = GDK_INPUT_OUTPUT; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK; attributes.visual = gtk_widget_get_visual (widget); #if defined(GTK_VERSION3) attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; gtk_widget_set_window( widget, gdk_window_new( gtk_widget_get_parent_window(widget), &attributes, attributes_mask ) ); gtk_widget_set_style( widget, gtk_style_attach( gtk_widget_get_style(widget), gtk_widget_get_window(widget) ) ); gdk_window_set_user_data ( gtk_widget_get_window(widget), widget ); gtk_style_set_background ( gtk_widget_get_style(widget), gtk_widget_get_window(widget), GTK_STATE_ACTIVE ); #else // The following lines are included to prevent breaking // compatibility with older Gtk2 (window = gdk_window_new (widget->parent->window, &attributes, attributes_mask); widget->style = gtk_style_attach (widget->style, widget->window); gdk_window_set_user_data (widget->window, widget); gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE); #endif // GTK_VERSION3 } static CvSize cvImageWidget_calc_size( int im_width, int im_height, int max_width, int max_height ){ float aspect = (float)im_width/(float)im_height; float max_aspect = (float)max_width/(float)max_height; if(aspect > max_aspect){ return cvSize( max_width, cvRound(max_width/aspect) ); } return cvSize( cvRound(max_height*aspect), max_height ); } #if defined (GTK_VERSION3) static void cvImageWidget_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width) { g_return_if_fail (widget != NULL); g_return_if_fail (CV_IS_IMAGE_WIDGET (widget)); CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget ); if(image_widget->original_image != NULL) { *minimal_width = (image_widget->flags & CV_WINDOW_AUTOSIZE) != CV_WINDOW_AUTOSIZE ? gdk_window_get_width(gtk_widget_get_window(widget)) : image_widget->original_image->cols; } else { *minimal_width = 320; } if(image_widget->scaled_image != NULL) { *natural_width = *minimal_width < image_widget->scaled_image->cols ? image_widget->scaled_image->cols : *minimal_width; } else { *natural_width = *minimal_width; } } static void cvImageWidget_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height) { g_return_if_fail (widget != NULL); g_return_if_fail (CV_IS_IMAGE_WIDGET (widget)); CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget ); if(image_widget->original_image != NULL) { *minimal_height = (image_widget->flags & CV_WINDOW_AUTOSIZE) != CV_WINDOW_AUTOSIZE ? gdk_window_get_height(gtk_widget_get_window(widget)) : image_widget->original_image->rows; } else { *minimal_height = 240; } if(image_widget->scaled_image != NULL) { *natural_height = *minimal_height < image_widget->scaled_image->rows ? image_widget->scaled_image->rows : *minimal_height; } else { *natural_height = *minimal_height; } } #else static void cvImageWidget_size_request (GtkWidget *widget, GtkRequisition *requisition) { CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget ); //printf("cvImageWidget_size_request "); // the case the first time cvShowImage called or when AUTOSIZE if( image_widget->original_image && ((image_widget->flags & CV_WINDOW_AUTOSIZE) || (image_widget->flags & CV_WINDOW_NO_IMAGE))) { //printf("original "); requisition->width = image_widget->original_image->cols; requisition->height = image_widget->original_image->rows; } // default case else if(image_widget->scaled_image){ //printf("scaled "); requisition->width = image_widget->scaled_image->cols; requisition->height = image_widget->scaled_image->rows; } // the case before cvShowImage called else{ //printf("default "); requisition->width = 320; requisition->height = 240; } //printf("%d %d\n",requisition->width, requisition->height); } #endif //GTK_VERSION3 static void cvImageWidget_set_size(GtkWidget * widget, int max_width, int max_height){ CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget ); //printf("cvImageWidget_set_size %d %d\n", max_width, max_height); // don't allow to set the size if(image_widget->flags & CV_WINDOW_AUTOSIZE) return; if(!image_widget->original_image) return; CvSize scaled_image_size = cvImageWidget_calc_size( image_widget->original_image->cols, image_widget->original_image->rows, max_width, max_height ); if( image_widget->scaled_image && ( image_widget->scaled_image->cols != scaled_image_size.width || image_widget->scaled_image->rows != scaled_image_size.height )) { cvReleaseMat( &image_widget->scaled_image ); } if( !image_widget->scaled_image ){ image_widget->scaled_image = cvCreateMat( scaled_image_size.height, scaled_image_size.width, CV_8UC3 ); } CV_Assert(image_widget->scaled_image); } static void cvImageWidget_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { CvImageWidget *image_widget; //printf("cvImageWidget_size_allocate\n"); g_return_if_fail (widget != NULL); g_return_if_fail (CV_IS_IMAGE_WIDGET (widget)); g_return_if_fail (allocation != NULL); #if defined (GTK_VERSION3) gtk_widget_set_allocation(widget, allocation); #else widget->allocation = *allocation; #endif //GTK_VERSION3 image_widget = CV_IMAGE_WIDGET (widget); if( (image_widget->flags & CV_WINDOW_AUTOSIZE)==0 && image_widget->original_image ){ // (re) allocated scaled image if( image_widget->flags & CV_WINDOW_NO_IMAGE ){ cvImageWidget_set_size( widget, image_widget->original_image->cols, image_widget->original_image->rows); } else{ cvImageWidget_set_size( widget, allocation->width, allocation->height ); } cvResize( image_widget->original_image, image_widget->scaled_image, CV_INTER_AREA ); } if (gtk_widget_get_realized (widget)) { image_widget = CV_IMAGE_WIDGET (widget); if( image_widget->original_image && ((image_widget->flags & CV_WINDOW_AUTOSIZE) || (image_widget->flags & CV_WINDOW_NO_IMAGE)) ) { #if defined (GTK_VERSION3) allocation->width = image_widget->original_image->cols; allocation->height = image_widget->original_image->rows; gtk_widget_set_allocation(widget, allocation); #else widget->allocation.width = image_widget->original_image->cols; widget->allocation.height = image_widget->original_image->rows; #endif //GTK_VERSION3 gdk_window_move_resize( gtk_widget_get_window(widget), allocation->x, allocation->y, image_widget->original_image->cols, image_widget->original_image->rows ); if(image_widget->flags & CV_WINDOW_NO_IMAGE){ image_widget->flags &= ~CV_WINDOW_NO_IMAGE; gtk_widget_queue_resize( GTK_WIDGET(widget) ); } } else{ gdk_window_move_resize (gtk_widget_get_window(widget), allocation->x, allocation->y, allocation->width, allocation->height ); } } } #if defined (GTK_VERSION3) static void cvImageWidget_destroy (GtkWidget *object) #else static void cvImageWidget_destroy (GtkObject *object) #endif //GTK_VERSION3 { CvImageWidget *image_widget; g_return_if_fail (object != NULL); g_return_if_fail (CV_IS_IMAGE_WIDGET (object)); image_widget = CV_IMAGE_WIDGET (object); cvReleaseMat( &image_widget->scaled_image ); cvReleaseMat( &image_widget->original_image ); #if defined (GTK_VERSION3) if (GTK_WIDGET_CLASS (parent_class)->destroy) (* GTK_WIDGET_CLASS (parent_class)->destroy) (object); #else if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); #endif //GTK_VERSION3 } static void cvImageWidget_class_init (gpointer g_class, gpointer /*class_data*/) { CvImageWidgetClass* klass = (CvImageWidgetClass*)g_class; #if defined (GTK_VERSION3) GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); #else GtkObjectClass *object_class; GtkWidgetClass *widget_class; object_class = (GtkObjectClass*) klass; widget_class = (GtkWidgetClass*) klass; #endif //GTK_VERSION3 parent_class = GTK_WIDGET_CLASS( g_type_class_peek (gtk_widget_get_type ()) ); #if defined (GTK_VERSION3) widget_class->destroy = cvImageWidget_destroy; widget_class->get_preferred_width = cvImageWidget_get_preferred_width; widget_class->get_preferred_height = cvImageWidget_get_preferred_height; #else object_class->destroy = cvImageWidget_destroy; widget_class->size_request = cvImageWidget_size_request; #endif //GTK_VERSION3 widget_class->realize = cvImageWidget_realize; widget_class->size_allocate = cvImageWidget_size_allocate; widget_class->button_press_event = NULL; widget_class->button_release_event = NULL; widget_class->motion_notify_event = NULL; } static void cvImageWidget_init(GTypeInstance* instance, gpointer /*g_class*/) { CvImageWidget* image_widget = (CvImageWidget*)instance; image_widget->original_image=0; image_widget->scaled_image=0; image_widget->flags=0; } GType cvImageWidget_get_type (void){ static GType image_type = 0; if (!image_type) { image_type = g_type_register_static_simple( GTK_TYPE_WIDGET, (gchar*) "CvImageWidget", sizeof(CvImageWidgetClass), cvImageWidget_class_init, sizeof(CvImageWidget), cvImageWidget_init, (GTypeFlags)0 ); } return image_type; } ///////////////////////////////////////////////////////////////////////////// // End CvImageWidget ///////////////////////////////////////////////////////////////////////////// struct CvWindow; struct CvUIBase { CvUIBase(int signature_) : signature(signature_) { } int signature; }; struct CvTrackbar : CvUIBase { 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), // deprecated onChangeCallback(NULL), userdata(NULL) { // nothing } ~CvTrackbar() { // destroyed by parent window } GtkWidget* widget; std::string name; CvWindow* parent; // TODO weak_ptr int* data; int pos; int maxval; int minval; CvTrackbarCallback notify; // deprecated CvTrackbarCallback2 notify2; // deprecated TrackbarCallback onChangeCallback; void* userdata; }; struct CvWindow : CvUIBase { 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), on_mouse(NULL), on_mouse_param(NULL) #ifdef HAVE_OPENGL ,useGl(false), glDrawCallback(NULL), glDrawData(NULL) #endif { CV_LOG_INFO(NULL, "OpenCV/UI: creating GTK window: " << window_name); } ~CvWindow(); void destroy(); GtkWidget* widget; GtkWidget* frame; GtkWidget* paned; std::string name; int last_key; int flags; int status;//0 normal, 1 fullscreen (YV) CvMouseCallback on_mouse; void* on_mouse_param; std::vector< std::shared_ptr > trackbars; #ifdef HAVE_OPENGL bool useGl; CvOpenGlDrawCallback glDrawCallback; void* glDrawData; #endif }; static gboolean icvOnClose( GtkWidget* widget, GdkEvent* event, gpointer user_data ); static gboolean icvOnKeyPress( GtkWidget* widget, GdkEventKey* event, gpointer user_data ); static void icvOnTrackbar( GtkWidget* widget, gpointer user_data ); static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_data ); #ifdef HAVE_GTHREAD int thread_started=0; static gpointer icvWindowThreadLoop(gpointer data); GMutex* last_key_mutex = NULL; GCond* cond_have_key = NULL; GThread* window_thread = NULL; #endif static int last_key = -1; static std::vector< std::shared_ptr >& getGTKWindows() { static std::vector< std::shared_ptr > g_windows; return g_windows; } CV_IMPL int cvInitSystem( int argc, char** argv ) { static int wasInitialized = 0; static bool hasError = false; // check initialization status if( !wasInitialized ) { if (!gtk_init_check(&argc, &argv)) { hasError = true; wasInitialized = true; CV_Error(Error::StsError, "Can't initialize GTK backend"); } setlocale(LC_NUMERIC,"C"); #ifdef HAVE_OPENGL if (!gtk_gl_init_check(&argc, &argv)) { hasError = true; wasInitialized = true; CV_Error(Error::StsError, "Can't initialize GTK-OpenGL backend"); } #endif wasInitialized = 1; } if (hasError) CV_Error(Error::StsError, "GTK backend is not available"); return 0; } CV_IMPL int cvStartWindowThread(){ #ifdef HAVE_GTHREAD cvInitSystem(0,NULL); if (!thread_started) { (void)getWindowMutex(); // force mutex initialization // protects the 'last key pressed' variable last_key_mutex = g_mutex_new(); // conditional that indicates a key has been pressed cond_have_key = g_cond_new(); window_thread = g_thread_new("OpenCV window update", icvWindowThreadLoop, NULL); } thread_started = window_thread!=NULL; return thread_started; #else return 0; #endif } #ifdef HAVE_GTHREAD gpointer icvWindowThreadLoop(gpointer /*data*/) { while(1){ { cv::AutoLock lock(getWindowMutex()); gtk_main_iteration_do(FALSE); } // little sleep g_usleep(500); g_thread_yield(); } return NULL; } #endif #define CV_LOCK_MUTEX() cv::AutoLock lock(getWindowMutex()) static std::shared_ptr icvFindWindowByName(const std::string& name) { auto& g_windows = getGTKWindows(); for(size_t i = 0; i < g_windows.size(); ++i) { auto window = g_windows[i]; if (!window) continue; if (window->name == name) return window; } return std::shared_ptr(); } static inline std::shared_ptr icvFindWindowByName(const char* name) { CV_Assert(name); return icvFindWindowByName(std::string(name)); } static CvWindow* icvWindowByWidget( GtkWidget* widget ) { auto& g_windows = getGTKWindows(); for (size_t i = 0; i < g_windows.size(); ++i) { CvWindow* window = g_windows[i].get(); if (window->widget == widget || window->frame == widget || window->paned == widget) return window; } return NULL; } static Rect getImageRect_(const std::shared_ptr& window); CvRect cvGetWindowRect_GTK(const char* name) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(name); if (!window) CV_Error( CV_StsNullPtr, "NULL window" ); return cvRect(getImageRect_(window)); } static Rect getImageRect_(const std::shared_ptr& window) { CV_Assert(window); gint wx, wy; #ifdef HAVE_OPENGL if (window->useGl) { gtk_widget_translate_coordinates(window->widget, gtk_widget_get_toplevel(window->widget), 0, 0, &wx, &wy); return Rect(wx, wy, window->widget->allocation.width, window->widget->allocation.height); } #endif CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget ); 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 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 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 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 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 Rect(-1, -1, -1, -1); } double cvGetModeWindow_GTK(const char* name)//YV { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(name); if (!window) CV_Error( CV_StsNullPtr, "NULL window" ); double result = window->status; return result; } static bool setModeWindow_(const std::shared_ptr& window, int mode); void cvSetModeWindow_GTK( const char* name, double prop_value)//Yannick Verdie { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(name); if (!window) CV_Error( CV_StsNullPtr, "NULL window" ); setModeWindow_(window, (int)prop_value); } static bool setModeWindow_(const std::shared_ptr& window, int mode) { if (window->flags & CV_WINDOW_AUTOSIZE) //if the flag CV_WINDOW_AUTOSIZE is set return false; //so easy to do fullscreen here, Linux rocks ! if (window->status == 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 true; } if (window->status==CV_WINDOW_NORMAL && mode==CV_WINDOW_FULLSCREEN) { gtk_window_fullscreen(GTK_WINDOW(window->frame)); window->status=CV_WINDOW_FULLSCREEN; return true; } return false; } void setWindowTitle_GTK(const String& winname, const String& title) { CV_LOCK_MUTEX(); auto window = icvFindWindowByName(winname.c_str()); if (!window) { namedWindow(winname); window = icvFindWindowByName(winname.c_str()); CV_Assert(window); } gtk_window_set_title(GTK_WINDOW(window->frame), title.c_str()); } double cvGetPropWindowAutoSize_GTK(const char* name) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(name); if (!window) return -1; // keep silence here double result = window->flags & CV_WINDOW_AUTOSIZE; return result; } static double getRatioWindow_(const std::shared_ptr& window); double cvGetRatioWindow_GTK(const char* name) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(name); if (!window) return -1; // keep silence here return getRatioWindow_(window); } static double getRatioWindow_(const std::shared_ptr& window) { #if defined (GTK_VERSION3) double result = static_cast( gtk_widget_get_allocated_width(window->widget)) / gtk_widget_get_allocated_height(window->widget); #else double result = static_cast(window->widget->allocation.width) / window->widget->allocation.height; #endif // GTK_VERSION3 return result; } double cvGetOpenGlProp_GTK(const char* name) { #ifdef HAVE_OPENGL CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(name); if (!window) return -1; // keep silence here double result = window->useGl; return result; #else (void)name; return -1; #endif } // OpenGL support #ifdef HAVE_OPENGL namespace { void createGlContext(CvWindow* window) { GdkGLConfig* glconfig; // Try double-buffered visual glconfig = gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE)); if (!glconfig) CV_Error( CV_OpenGlApiCallError, "Can't Create A GL Device Context" ); // Set OpenGL-capability to the widget if (!gtk_widget_set_gl_capability(window->widget, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE)) CV_Error( CV_OpenGlApiCallError, "Can't Create A GL Device Context" ); window->useGl = true; } void drawGl(CvWindow* window) { GdkGLContext* glcontext = gtk_widget_get_gl_context(window->widget); GdkGLDrawable* gldrawable = gtk_widget_get_gl_drawable(window->widget); if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext)) CV_Error( CV_OpenGlApiCallError, "Can't Activate The GL Rendering Context" ); glViewport(0, 0, window->widget->allocation.width, window->widget->allocation.height); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (window->glDrawCallback) window->glDrawCallback(window->glDrawData); if (gdk_gl_drawable_is_double_buffered (gldrawable)) gdk_gl_drawable_swap_buffers(gldrawable); else glFlush(); gdk_gl_drawable_gl_end(gldrawable); } } #endif // HAVE_OPENGL #if defined (GTK_VERSION3) static gboolean cvImageWidget_draw(GtkWidget* widget, cairo_t *cr, gpointer data) { #ifdef HAVE_OPENGL CvWindow* window = (CvWindow*)data; if (window->useGl) { drawGl(window); return TRUE; } #else (void)data; #endif CvImageWidget *image_widget = NULL; GdkPixbuf *pixbuf = NULL; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (CV_IS_IMAGE_WIDGET (widget), FALSE); image_widget = CV_IMAGE_WIDGET (widget); if( image_widget->scaled_image ){ // center image in available region #if defined (GTK_VERSION3) int x0 = (gtk_widget_get_allocated_width(widget) - image_widget->scaled_image->cols)/2; int y0 = (gtk_widget_get_allocated_height(widget) - image_widget->scaled_image->rows)/2; #else int x0 = (widget->allocation.width - image_widget->scaled_image->cols)/2; int y0 = (widget->allocation.height - image_widget->scaled_image->rows)/2; #endif //GTK_VERSION3 #if defined (GTK_VERSION3) pixbuf = gdk_pixbuf_new_from_data(image_widget->scaled_image->data.ptr, GDK_COLORSPACE_RGB, false, 8, MIN(image_widget->scaled_image->cols, gtk_widget_get_allocated_width(widget)), MIN(image_widget->scaled_image->rows, gtk_widget_get_allocated_height(widget)), image_widget->scaled_image->step, NULL, NULL); #else pixbuf = gdk_pixbuf_new_from_data(image_widget->scaled_image->data.ptr, GDK_COLORSPACE_RGB, false, 8, MIN(image_widget->scaled_image->cols, widget->allocation.width), MIN(image_widget->scaled_image->rows, widget->allocation.height), image_widget->scaled_image->step, NULL, NULL); #endif //GTK_VERSION3 gdk_cairo_set_source_pixbuf(cr, pixbuf, x0, y0); } else if( image_widget->original_image ){ #if defined (GTK_VERSION3) pixbuf = gdk_pixbuf_new_from_data(image_widget->original_image->data.ptr, GDK_COLORSPACE_RGB, false, 8, MIN(image_widget->original_image->cols, gtk_widget_get_allocated_width(widget)), MIN(image_widget->original_image->rows, gtk_widget_get_allocated_height(widget)), image_widget->original_image->step, NULL, NULL); #else pixbuf = gdk_pixbuf_new_from_data(image_widget->original_image->data.ptr, GDK_COLORSPACE_RGB, false, 8, MIN(image_widget->original_image->cols, widget->allocation.width), MIN(image_widget->original_image->rows, widget->allocation.height), image_widget->original_image->step, NULL, NULL); #endif //GTK_VERSION3 gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); } cairo_paint(cr); if(pixbuf) g_object_unref(pixbuf); return TRUE; } #else static gboolean cvImageWidget_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data) { #ifdef HAVE_OPENGL CvWindow* window = (CvWindow*)data; if (window->useGl) { drawGl(window); return TRUE; } #else (void)data; #endif CvImageWidget *image_widget = NULL; cairo_t *cr = NULL; GdkPixbuf *pixbuf = NULL; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (CV_IS_IMAGE_WIDGET (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); if (event->count > 0) return FALSE; cr = gdk_cairo_create(widget->window); image_widget = CV_IMAGE_WIDGET (widget); if( image_widget->scaled_image ){ // center image in available region int x0 = (widget->allocation.width - image_widget->scaled_image->cols)/2; int y0 = (widget->allocation.height - image_widget->scaled_image->rows)/2; pixbuf = gdk_pixbuf_new_from_data(image_widget->scaled_image->data.ptr, GDK_COLORSPACE_RGB, false, 8, MIN(image_widget->scaled_image->cols, widget->allocation.width), MIN(image_widget->scaled_image->rows, widget->allocation.height), image_widget->scaled_image->step, NULL, NULL); gdk_cairo_set_source_pixbuf(cr, pixbuf, x0, y0); } else if( image_widget->original_image ){ pixbuf = gdk_pixbuf_new_from_data(image_widget->original_image->data.ptr, GDK_COLORSPACE_RGB, false, 8, MIN(image_widget->original_image->cols, widget->allocation.width), MIN(image_widget->original_image->rows, widget->allocation.height), image_widget->original_image->step, NULL, NULL); gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); } cairo_paint(cr); if(pixbuf) g_object_unref(pixbuf); cairo_destroy(cr); return TRUE; } #endif //GTK_VERSION3 static std::shared_ptr namedWindow_(const std::string& name, int flags); CV_IMPL int cvNamedWindow( const char* name, int flags ) { cvInitSystem(name ? 1 : 0,(char**)&name); CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); // Check the name in the storage if (icvFindWindowByName(name)) { return 1; } auto window = namedWindow_(name, flags); return window ? 1 : 0; } static std::shared_ptr namedWindow_(const std::string& name, int flags) { cvInitSystem(0, NULL); auto window_ptr = std::make_shared(name); CvWindow* window = window_ptr.get(); window->flags = flags; window->status = CV_WINDOW_NORMAL;//YV window->frame = gtk_window_new( GTK_WINDOW_TOPLEVEL ); window->paned = gtk_vbox_new( FALSE, 0 ); window->widget = cvImageWidgetNew( flags ); gtk_box_pack_end( GTK_BOX(window->paned), window->widget, TRUE, TRUE, 0 ); gtk_widget_show( window->widget ); gtk_container_add( GTK_CONTAINER(window->frame), window->paned ); gtk_widget_show( window->paned ); #ifndef HAVE_OPENGL if (flags & CV_WINDOW_OPENGL) CV_Error( CV_OpenGlNotSupported, "Library was built without OpenGL support" ); #else if (flags & CV_WINDOW_OPENGL) createGlContext(window); window->glDrawCallback = 0; window->glDrawData = 0; #endif // // configure event handlers // TODO -- move this to CvImageWidget ? g_signal_connect( window->frame, "key-press-event", G_CALLBACK(icvOnKeyPress), window ); g_signal_connect( window->widget, "button-press-event", G_CALLBACK(icvOnMouse), window ); g_signal_connect( window->widget, "button-release-event", G_CALLBACK(icvOnMouse), window ); g_signal_connect( window->widget, "motion-notify-event", G_CALLBACK(icvOnMouse), window ); g_signal_connect( window->widget, "scroll-event", G_CALLBACK(icvOnMouse), window ); g_signal_connect( window->frame, "delete-event", G_CALLBACK(icvOnClose), window ); #if defined(GTK_VERSION3) g_signal_connect( window->widget, "draw", G_CALLBACK(cvImageWidget_draw), window ); #else g_signal_connect( window->widget, "expose-event", G_CALLBACK(cvImageWidget_expose), window ); #endif //GTK_VERSION3 #if defined(GTK_VERSION3_4) gtk_widget_add_events (window->widget, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK) ; #else gtk_widget_add_events (window->widget, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK) ; #endif //GTK_VERSION3_4 gtk_widget_show( window->frame ); gtk_window_set_title(GTK_WINDOW(window->frame), name.c_str()); { 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 ); // allow window to be resized if( b_nautosize ){ GdkGeometry geometry; geometry.min_width = 50; geometry.min_height = 50; gtk_window_set_geometry_hints( GTK_WINDOW( window->frame ), GTK_WIDGET( window->widget ), &geometry, (GdkWindowHints) (GDK_HINT_MIN_SIZE)); } #ifdef HAVE_OPENGL if (window->useGl) cvSetOpenGlContext(name.c_str()); #endif return window_ptr; } #ifdef HAVE_OPENGL CV_IMPL void cvSetOpenGlContext(const char* name) { GdkGLContext* glcontext; GdkGLDrawable* gldrawable; CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); auto window = icvFindWindowByName(name); if (!window) CV_Error( CV_StsNullPtr, "NULL window" ); if (!window->useGl) CV_Error( CV_OpenGlNotSupported, "Window doesn't support OpenGL" ); glcontext = gtk_widget_get_gl_context(window->widget); gldrawable = gtk_widget_get_gl_drawable(window->widget); if (!gdk_gl_drawable_make_current(gldrawable, glcontext)) CV_Error( CV_OpenGlApiCallError, "Can't Activate The GL Rendering Context" ); } CV_IMPL void cvUpdateWindow(const char* name) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); auto window = icvFindWindowByName(name); if (!window) return; // window does not refresh without this gtk_widget_queue_draw( GTK_WIDGET(window->widget) ); } CV_IMPL void cvSetOpenGlDrawCallback(const char* name, CvOpenGlDrawCallback callback, void* userdata) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); auto window = icvFindWindowByName(name); if( !window ) return; if (!window->useGl) CV_Error( CV_OpenGlNotSupported, "Window was created without OpenGL context" ); window->glDrawCallback = callback; window->glDrawData = userdata; } #endif // HAVE_OPENGL 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 (getGTKWindows().empty()) { #ifdef HAVE_GTHREAD if( thread_started ) { // send key press signal to jump out of any waiting cvWaitKey's g_cond_broadcast( cond_have_key ); } else { #endif // Some GTK+ modules (like the Unity module) use GDBusConnection, // which has a habit of postponing cleanup by performing it via // idle sources added to the main loop. Since this was the last window, // we can assume that no event processing is going to happen in the // nearest future, so we should force that cleanup (by handling all pending // events) while we still have the chance. // This is not needed if thread_started is true, because the background // thread will process events continuously. while( gtk_events_pending() ) gtk_main_iteration(); #ifdef HAVE_GTHREAD } #endif } } static void icvDeleteWindow_( CvWindow* window ) { AutoLock lock(getWindowMutex()); auto& g_windows = getGTKWindows(); bool found = false; for (auto i = g_windows.begin(); i != g_windows.end(); ++i) { if (i->get() == window) { g_windows.erase(i); found = true; break; } } CV_LOG_IF_WARNING(NULL, !found, "OpenCV/GTK: Can't destroy non-registered window"); checkLastWindow(); } CV_IMPL void cvDestroyWindow( const char* name ) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); auto& g_windows = getGTKWindows(); bool found = false; for (auto i = g_windows.begin(); i != g_windows.end(); ++i) { if (i->get()->name == name) { g_windows.erase(i); found = true; break; } } CV_LOG_IF_ERROR(NULL, !found, "OpenCV/GTK: Can't destroy non-registered window: '" << name << "'"); checkLastWindow(); } CV_IMPL void cvDestroyAllWindows( void ) { CV_LOCK_MUTEX(); getGTKWindows().clear(); checkLastWindow(); } // CvSize icvCalcOptimalWindowSize( CvWindow * window, CvSize new_image_size){ // CvSize window_size; // GtkWidget * toplevel = gtk_widget_get_toplevel( window->frame ); // gdk_drawable_get_size( GDK_DRAWABLE(toplevel->window), // &window_size.width, &window_size.height ); // window_size.width = window_size.width + new_image_size.width - window->widget->allocation.width; // window_size.height = window_size.height + new_image_size.height - window->widget->allocation.height; // return window_size; // } CV_IMPL void cvShowImage( const char* name, const CvArr* arr ) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); auto window = icvFindWindowByName(name); if(!window) { cvNamedWindow(name, 1); window = icvFindWindowByName(name); } CV_Assert(window); if (arr) { #ifdef HAVE_OPENGL if (window->useGl) { cv::imshow(name, cv::cvarrToMat(arr)); return; } #endif CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget ); cvImageWidgetSetImage( image_widget, arr ); } } static void resizeWindow_(const std::shared_ptr& window, int width, int height); CV_IMPL void cvResizeWindow(const char* name, int width, int height ) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); auto window = icvFindWindowByName(name); if(!window) return; return resizeWindow_(window, width, height); } static void resizeWindow_(const std::shared_ptr& window, int width, int height) { CV_Assert(window); CvImageWidget* image_widget = CV_IMAGE_WIDGET( window->widget ); //if(image_widget->flags & CV_WINDOW_AUTOSIZE) //EXIT; gtk_window_set_resizable( GTK_WINDOW(window->frame), 1 ); gtk_window_resize( GTK_WINDOW(window->frame), width, height ); // disable initial resize since presumably user wants to keep // this window size image_widget->flags &= ~CV_WINDOW_NO_IMAGE; } CV_IMPL void cvMoveWindow( const char* name, int x, int y ) { CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(name); if(!window) return; gtk_window_move( GTK_WINDOW(window->frame), x, y ); } static std::shared_ptr icvFindTrackbarByName(const std::shared_ptr& window, const std::string& name) { CV_Assert(window); auto& trackbars = window->trackbars; for(size_t i = 0; i < trackbars.size(); ++i) { auto trackbar = trackbars[i]; if (!trackbar) continue; if (trackbar->name == name) return trackbar; } return std::shared_ptr(); } static int icvCreateTrackbar( const char* trackbar_name, const char* window_name, int* val, int count, CvTrackbarCallback on_notify, CvTrackbarCallback2 on_notify2, void* userdata ) { CV_Assert(window_name && "NULL window name"); CV_Assert(trackbar_name && "NULL trackbar name"); if( count <= 0 ) CV_Error( CV_StsOutOfRange, "Bad trackbar maximal value" ); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(window_name); if(!window) return 0; auto trackbar_ = icvFindTrackbarByName(window, trackbar_name); if (!trackbar_) { trackbar_ = std::make_shared(trackbar_name); CvTrackbar* trackbar = trackbar_.get(); trackbar->parent = window.get(); window->trackbars.push_back(trackbar_); GtkWidget* hscale_box = gtk_hbox_new( FALSE, 10 ); GtkWidget* hscale_label = gtk_label_new( trackbar_name ); 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 ); } CvTrackbar* trackbar = trackbar_.get(); CV_DbgAssert(trackbar); if( val ) { int value = *val; if( value < 0 ) value = 0; if( value > count ) value = count; gtk_range_set_value( GTK_RANGE(trackbar->widget), value ); trackbar->pos = value; trackbar->data = val; } trackbar->maxval = count; trackbar->notify = on_notify; trackbar->notify2 = on_notify2; 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 1; } CV_IMPL int cvCreateTrackbar( const char* trackbar_name, const char* window_name, int* val, int count, CvTrackbarCallback on_notify ) { return icvCreateTrackbar(trackbar_name, window_name, val, count, on_notify, 0, 0); } CV_IMPL int cvCreateTrackbar2( const char* trackbar_name, const char* window_name, int* val, int count, CvTrackbarCallback2 on_notify2, void* userdata ) { return icvCreateTrackbar(trackbar_name, window_name, val, count, 0, on_notify2, userdata); } static std::shared_ptr createTrackbar_( const std::shared_ptr& window, const std::string& name, int count, TrackbarCallback onChange, void* userdata ) { CV_Assert(window); CV_Assert(!name.empty()); if (count <= 0) CV_Error(Error::StsOutOfRange, "Bad trackbar maximal value"); auto trackbar_ = std::make_shared(name); CvTrackbar* trackbar = trackbar_.get(); trackbar->parent = window.get(); window->trackbars.push_back(trackbar_); GtkWidget* hscale_box = gtk_hbox_new( FALSE, 10 ); GtkWidget* hscale_label = gtk_label_new(name.c_str()); GtkWidget* hscale = gtk_hscale_new_with_range( 0, count, 1 ); gtk_scale_set_digits( GTK_SCALE(hscale), 0 ); //gtk_scale_set_value_pos( hscale, GTK_POS_TOP ); gtk_scale_set_draw_value( GTK_SCALE(hscale), TRUE ); trackbar->widget = hscale; gtk_box_pack_start( GTK_BOX(hscale_box), hscale_label, FALSE, FALSE, 5 ); gtk_widget_show( hscale_label ); gtk_box_pack_start( GTK_BOX(hscale_box), hscale, TRUE, TRUE, 5 ); gtk_widget_show( hscale ); gtk_box_pack_start( GTK_BOX(window->paned), hscale_box, FALSE, FALSE, 5 ); gtk_widget_show( hscale_box ); trackbar->maxval = count; trackbar->onChangeCallback = onChange; trackbar->userdata = userdata; g_signal_connect(trackbar->widget, "value-changed", G_CALLBACK(icvOnTrackbar), trackbar); // queue a widget resize to trigger a window resize to // compensate for the addition of trackbars gtk_widget_queue_resize(GTK_WIDGET(window->widget)); return trackbar_; } CV_IMPL void cvSetMouseCallback( const char* window_name, CvMouseCallback on_mouse, void* param ) { CV_Assert(window_name && "NULL window name"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(window_name); if (!window) return; window->on_mouse = on_mouse; window->on_mouse_param = param; } CV_IMPL int cvGetTrackbarPos( const char* trackbar_name, const char* window_name ) { CV_Assert(window_name && "NULL window name"); CV_Assert(trackbar_name && "NULL trackbar name"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(window_name); if (!window) return -1; const auto trackbar = icvFindTrackbarByName(window,trackbar_name); if (!trackbar) return -1; return trackbar->pos; } static void setTrackbarPos_(const std::shared_ptr& trackbar, int pos); CV_IMPL void cvSetTrackbarPos( const char* trackbar_name, const char* window_name, int pos ) { CV_Assert(window_name && "NULL window name"); CV_Assert(trackbar_name && "NULL trackbar name"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(window_name); if(!window) return; const auto trackbar = icvFindTrackbarByName(window, trackbar_name); if (!trackbar) { CV_Error( CV_StsNullPtr, "No trackbar found" ); } return setTrackbarPos_(trackbar, pos); } static void setTrackbarPos_(const std::shared_ptr& trackbar, int pos) { CV_Assert(trackbar); CV_CheckLE(trackbar->minval, trackbar->maxval, ""); pos = std::max(pos, trackbar->minval); pos = std::min(pos, trackbar->maxval); gtk_range_set_value( GTK_RANGE(trackbar->widget), pos ); } CV_IMPL void cvSetTrackbarMax(const char* trackbar_name, const char* window_name, int maxval) { CV_Assert(window_name && "NULL window name"); CV_Assert(trackbar_name && "NULL trackbar name"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(window_name); if(!window) return; const auto trackbar = icvFindTrackbarByName(window,trackbar_name); if(!trackbar) return; trackbar->maxval = maxval; if (trackbar->maxval >= trackbar->minval) gtk_range_set_range(GTK_RANGE(trackbar->widget), trackbar->minval, trackbar->maxval); } CV_IMPL void cvSetTrackbarMin(const char* trackbar_name, const char* window_name, int minval) { CV_Assert(window_name && "NULL window name"); CV_Assert(trackbar_name && "NULL trackbar name"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(window_name); if(!window) return; const auto trackbar = icvFindTrackbarByName(window,trackbar_name); if(!trackbar) return; trackbar->minval = minval; if (trackbar->maxval >= trackbar->minval) gtk_range_set_range(GTK_RANGE(trackbar->widget), trackbar->minval, trackbar->maxval); } CV_IMPL void* cvGetWindowHandle( const char* window_name ) { CV_Assert(window_name && "NULL window name"); CV_LOCK_MUTEX(); const auto window = icvFindWindowByName(window_name); if(!window) return NULL; return (void*)window->widget; } CV_IMPL const char* cvGetWindowName( void* window_handle ) { CV_Assert(window_handle && "NULL window handle"); CV_LOCK_MUTEX(); CvWindow* window = icvWindowByWidget( (GtkWidget*)window_handle ); if (window) return window->name.c_str(); return ""; // FIXME: NULL? } static GtkFileFilter* icvMakeGtkFilter(const char* name, const char* patterns, GtkFileFilter* images) { GtkFileFilter* filter = gtk_file_filter_new(); gtk_file_filter_set_name(filter, name); while(patterns[0]) { gtk_file_filter_add_pattern(filter, patterns); gtk_file_filter_add_pattern(images, patterns); patterns += strlen(patterns) + 1; } return filter; } static void icvShowSaveAsDialog(GtkWidget* widget, CvWindow* window) { if (!window || !widget) return; CvImageWidget* image_widget = CV_IMAGE_WIDGET(window->widget); if (!image_widget || !image_widget->original_image) return; GtkWidget* dialog = gtk_file_chooser_dialog_new("Save As...", GTK_WINDOW(widget), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); cv::String sname = gtk_window_get_title(GTK_WINDOW(window->frame)); sname = sname.substr(sname.find_last_of("\\/") + 1) + ".png"; gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), sname.c_str()); GtkFileFilter* filter_all = gtk_file_filter_new(); gtk_file_filter_set_name(filter_all, "All Files"); gtk_file_filter_add_pattern(filter_all, "*"); GtkFileFilter* filter_images = gtk_file_filter_new(); gtk_file_filter_set_name(filter_images, "All Images"); GtkFileFilter* file_filters[] = { icvMakeGtkFilter("Portable Network Graphics files (*.png)", "*.png\0", filter_images), icvMakeGtkFilter("JPEG files (*.jpeg;*.jpg;*.jpe)", "*.jpeg\0*.jpg\0*.jpe\0", filter_images), icvMakeGtkFilter("Windows bitmap (*.bmp;*.dib)", "*.bmp\0*.dib\0", filter_images), icvMakeGtkFilter("TIFF Files (*.tiff;*.tif)", "*.tiff\0*.tif\0", filter_images), icvMakeGtkFilter("JPEG-2000 files (*.jp2)", "*.jp2\0", filter_images), icvMakeGtkFilter("WebP files (*.webp)", "*.webp\0", filter_images), icvMakeGtkFilter("Portable image format (*.pbm;*.pgm;*.ppm;*.pxm;*.pnm)", "*.pbm\0*.pgm\0*.ppm\0*.pxm\0*.pnm\0", filter_images), icvMakeGtkFilter("OpenEXR Image files (*.exr)", "*.exr\0", filter_images), icvMakeGtkFilter("Radiance HDR (*.hdr;*.pic)", "*.hdr\0*.pic\0", filter_images), icvMakeGtkFilter("Sun raster files (*.sr;*.ras)", "*.sr\0*.ras\0", filter_images), filter_images, filter_all }; for (size_t idx = 0; idx < sizeof(file_filters)/sizeof(file_filters[0]); ++idx) gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), file_filters[idx]); // filter ownership is transferred to dialog gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter_images); cv::String filename; if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { char* fname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); filename = fname; g_free(fname); } gtk_widget_destroy(dialog); if (!filename.empty()) { cv::Mat bgr; cv::cvtColor(cv::cvarrToMat(image_widget->original_image), bgr, cv::COLOR_RGB2BGR); cv::imwrite(filename, bgr); } } #if defined (GTK_VERSION3) #define GDK_Escape GDK_KEY_Escape #define GDK_Return GDK_KEY_Return #define GDK_Linefeed GDK_KEY_Linefeed #define GDK_Tab GDK_KEY_Tab #define GDK_s GDK_KEY_s #define GDK_S GDK_KEY_S #endif //GTK_VERSION3 static gboolean icvOnKeyPress(GtkWidget* widget, GdkEventKey* event, gpointer user_data) { int code = 0; if ( BIT_ALLIN(event->state, GDK_CONTROL_MASK) && (event->keyval == GDK_s || event->keyval == GDK_S)) { try { icvShowSaveAsDialog(widget, (CvWindow*)user_data); } catch(...) { // suppress all exceptions here } } switch( event->keyval ) { case GDK_Escape: code = 27; break; case GDK_Return: case GDK_Linefeed: code = 13; break; case GDK_Tab: code = '\t'; break; default: code = event->keyval; } code |= event->state << 16; #ifdef HAVE_GTHREAD if(thread_started) { g_mutex_lock(last_key_mutex); last_key = code; // signal any waiting threads g_cond_broadcast(cond_have_key); g_mutex_unlock(last_key_mutex); } else #endif { last_key = code; } return FALSE; } static void icvOnTrackbar( GtkWidget* widget, gpointer user_data ) { int pos = cvRound( gtk_range_get_value(GTK_RANGE(widget))); CvTrackbar* trackbar = (CvTrackbar*)user_data; if( trackbar && trackbar->signature == CV_TRACKBAR_MAGIC_VAL && trackbar->widget == widget ) { trackbar->pos = pos; if (trackbar->onChangeCallback) trackbar->onChangeCallback(pos, trackbar->userdata); // deprecated if( trackbar->data ) *trackbar->data = pos; if( trackbar->notify2 ) trackbar->notify2(pos, trackbar->userdata); else if( trackbar->notify ) trackbar->notify(pos); } } static gboolean icvOnClose( GtkWidget* widget, GdkEvent* /*event*/, gpointer user_data ) { CvWindow* window = (CvWindow*)user_data; if( window->signature == CV_WINDOW_MAGIC_VAL && window->frame == widget ) { try { icvDeleteWindow_(window); } catch (...) { CV_LOG_WARNING(NULL, "OpenCV/GTK: unexpected C++ exception in icvDeleteWindow_"); } } return TRUE; } static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_data ) { // TODO move this logic to CvImageWidget // TODO add try-catch wrappers into all callbacks CvWindow* window = (CvWindow*)user_data; if (!window || !widget || window->signature != CV_WINDOW_MAGIC_VAL || window->widget != widget || !window->on_mouse) return FALSE; CvPoint2D32f pt32f = {-1., -1.}; CvPoint pt = {-1,-1}; int cv_event = -1, state = 0, flags = 0; CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget ); if( event->type == GDK_MOTION_NOTIFY ) { GdkEventMotion* event_motion = (GdkEventMotion*)event; cv_event = CV_EVENT_MOUSEMOVE; pt32f.x = cvFloor(event_motion->x); pt32f.y = cvFloor(event_motion->y); state = event_motion->state; } else if( event->type == GDK_BUTTON_PRESS || event->type == GDK_BUTTON_RELEASE || event->type == GDK_2BUTTON_PRESS ) { GdkEventButton* event_button = (GdkEventButton*)event; pt32f.x = cvFloor(event_button->x); pt32f.y = cvFloor(event_button->y); if( event_button->type == GDK_BUTTON_PRESS ) { cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDOWN : event_button->button == 2 ? CV_EVENT_MBUTTONDOWN : event_button->button == 3 ? CV_EVENT_RBUTTONDOWN : 0; } else if( event_button->type == GDK_BUTTON_RELEASE ) { cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONUP : event_button->button == 2 ? CV_EVENT_MBUTTONUP : event_button->button == 3 ? CV_EVENT_RBUTTONUP : 0; } else if( event_button->type == GDK_2BUTTON_PRESS ) { cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDBLCLK : event_button->button == 2 ? CV_EVENT_MBUTTONDBLCLK : event_button->button == 3 ? CV_EVENT_RBUTTONDBLCLK : 0; } state = event_button->state; } else if( event->type == GDK_SCROLL ) { GdkEventScroll* event_scroll = (GdkEventScroll*)event; pt32f.x = cvFloor(event_scroll->x); pt32f.y = cvFloor(event_scroll->y); #if defined(GTK_VERSION3_4) // NOTE: in current implementation doesn't possible to put into callback function delta_x and delta_y separately double delta = (event->scroll.delta_x + event->scroll.delta_y); cv_event = (event->scroll.delta_x==0) ? CV_EVENT_MOUSEWHEEL : CV_EVENT_MOUSEHWHEEL; #else cv_event = CV_EVENT_MOUSEWHEEL; #endif //GTK_VERSION3_4 state = event->scroll.state; switch(event->scroll.direction) { #if defined(GTK_VERSION3_4) case GDK_SCROLL_SMOOTH: flags |= (((int)delta << 16)); break; #endif //GTK_VERSION3_4 case GDK_SCROLL_LEFT: cv_event = CV_EVENT_MOUSEHWHEEL; /* FALLTHRU */ case GDK_SCROLL_UP: flags |= ~0xffff; break; case GDK_SCROLL_RIGHT: cv_event = CV_EVENT_MOUSEHWHEEL; /* FALLTHRU */ case GDK_SCROLL_DOWN: flags |= (((int)1 << 16)); break; default: ; }; } if( cv_event >= 0 ) { // scale point if image is scaled if( (image_widget->flags & CV_WINDOW_AUTOSIZE)==0 && image_widget->original_image && image_widget->scaled_image ) { // image origin is not necessarily at (0,0) #if defined (GTK_VERSION3) int x0 = (gtk_widget_get_allocated_width(widget) - image_widget->scaled_image->cols)/2; int y0 = (gtk_widget_get_allocated_height(widget) - image_widget->scaled_image->rows)/2; #else int x0 = (widget->allocation.width - image_widget->scaled_image->cols)/2; int y0 = (widget->allocation.height - image_widget->scaled_image->rows)/2; #endif //GTK_VERSION3 pt.x = cvFloor( ((pt32f.x-x0)*image_widget->original_image->cols)/ image_widget->scaled_image->cols ); pt.y = cvFloor( ((pt32f.y-y0)*image_widget->original_image->rows)/ image_widget->scaled_image->rows ); } else { pt = cvPointFrom32f( pt32f ); } if (!image_widget->original_image/*OpenGL*/ || ( (unsigned)pt.x < (unsigned)(image_widget->original_image->width) && (unsigned)pt.y < (unsigned)(image_widget->original_image->height) )) { // handle non-keyboard (mouse) modifiers first flags |= BIT_MAP(state, GDK_BUTTON1_MASK, CV_EVENT_FLAG_LBUTTON) | BIT_MAP(state, GDK_BUTTON2_MASK, CV_EVENT_FLAG_MBUTTON) | BIT_MAP(state, GDK_BUTTON3_MASK, CV_EVENT_FLAG_RBUTTON); // keyboard modifiers state &= gtk_accelerator_get_default_mod_mask(); flags |= BIT_MAP(state, GDK_SHIFT_MASK, CV_EVENT_FLAG_SHIFTKEY) | BIT_MAP(state, GDK_CONTROL_MASK, CV_EVENT_FLAG_CTRLKEY) | BIT_MAP(state, GDK_MOD1_MASK, CV_EVENT_FLAG_ALTKEY) | BIT_MAP(state, GDK_MOD2_MASK, CV_EVENT_FLAG_ALTKEY); window->on_mouse( cv_event, pt.x, pt.y, flags, window->on_mouse_param ); } } return FALSE; } static gboolean icvAlarm( gpointer user_data ) { *(int*)user_data = 1; return FALSE; } CV_IMPL int cvWaitKey( int delay ) { #ifdef HAVE_GTHREAD if (thread_started && g_thread_self() != window_thread) { gboolean expired = true; int my_last_key; g_mutex_lock(last_key_mutex); // wait for signal or timeout if delay > 0 if(delay>0){ GTimeVal timer; g_get_current_time(&timer); g_time_val_add(&timer, delay*1000); expired = !g_cond_timed_wait(cond_have_key, last_key_mutex, &timer); } else{ if (getGTKWindows().empty()) { CV_LOG_WARNING(NULL, "cv::waitKey() is called without timeout and missing active windows. Ignoring"); } else { g_cond_wait(cond_have_key, last_key_mutex); expired=false; } } my_last_key = last_key; g_mutex_unlock(last_key_mutex); if (expired || getGTKWindows().empty()) { return -1; } return my_last_key; } else #endif { int expired = 0; guint timer = 0; 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 || !getGTKWindows().empty())) ; if( delay > 0 && !expired ) g_source_remove(timer); } return last_key; } namespace cv { namespace impl { using namespace cv::highgui_backend; class GTKTrackbar; class GTKWindow : public UIWindow , public std::enable_shared_from_this { protected: const std::string name_; std::weak_ptr window_; std::map > trackbars_; public: GTKWindow(const std::string& name, const std::shared_ptr& window) : name_(name) , window_(window) { // nothing } ~GTKWindow() CV_OVERRIDE { if (!window_.expired()) destroy(); CV_LOG_DEBUG(NULL, "OpenCV/UI/GTK: GTKWindow(" << name_ << ") is disposed"); } const std::string& getID() const CV_OVERRIDE { return name_; } bool isActive() const CV_OVERRIDE { return !window_.expired(); } void destroy() CV_OVERRIDE { cv::AutoLock lock(getWindowMutex()); if (!window_.expired()) { auto window = window_.lock(); if (window) window->destroy(); window_.reset(); } } void imshow(InputArray image) CV_OVERRIDE { auto window = window_.lock(); CV_Assert(window); CvImageWidget* image_widget = CV_IMAGE_WIDGET(window->widget); CV_Assert(image_widget); Mat img = image.getMat(); CvMat c_img = cvMat(img); // TODO Drop C-API cvImageWidgetSetImage(image_widget, &c_img); } double getProperty(int prop) const CV_OVERRIDE { auto window = window_.lock(); CV_Assert(window); // see cvGetWindowProperty switch (prop) { case CV_WND_PROP_FULLSCREEN: return (double)window->status; case CV_WND_PROP_AUTOSIZE: return (window->flags & CV_WINDOW_AUTOSIZE) ? 1.0 : 0.0; case CV_WND_PROP_ASPECTRATIO: return getRatioWindow_(window); #ifdef HAVE_OPENGL case CV_WND_PROP_OPENGL: return window->useGl ? 1.0 : 0.0; #endif default: break; } return std::numeric_limits::quiet_NaN(); } bool setProperty(int prop, double value) CV_OVERRIDE { auto window = window_.lock(); CV_Assert(window); // see cvSetWindowProperty switch (prop) { case CV_WND_PROP_FULLSCREEN: if (value != CV_WINDOW_NORMAL && value != CV_WINDOW_FULLSCREEN) // bad arg break; setModeWindow_(window, value); return true; default: break; } return false; } void resize(int width, int height) CV_OVERRIDE { auto window = window_.lock(); CV_Assert(window); resizeWindow_(window, width, height); } void move(int x, int y) CV_OVERRIDE { auto window = window_.lock(); CV_Assert(window); gtk_window_move(GTK_WINDOW(window->frame), x, y); } Rect getImageRect() const CV_OVERRIDE { auto window = window_.lock(); CV_Assert(window); return getImageRect_(window); } void setTitle(const std::string& title) CV_OVERRIDE { auto window = window_.lock(); CV_Assert(window); gtk_window_set_title(GTK_WINDOW(window->frame), title.c_str()); } void setMouseCallback(MouseCallback onMouse, void* userdata /*= 0*/) CV_OVERRIDE { auto window = window_.lock(); CV_Assert(window); window->on_mouse = onMouse; window->on_mouse_param = userdata; } std::shared_ptr createTrackbar( const std::string& name, int count, TrackbarCallback onChange /*= 0*/, void* userdata /*= 0*/ ) CV_OVERRIDE { auto window = window_.lock(); CV_Assert(window); CV_LOG_INFO(NULL, "OpenCV/UI: Creating GTK trackbar at '" << name_ << "': '" << name << "'"); auto trackbar = createTrackbar_(window, name, count, onChange, userdata); auto ui_trackbar = std::make_shared(name, trackbar, shared_from_this()); { cv::AutoLock lock(getWindowMutex()); trackbars_.emplace(name, ui_trackbar); } return std::static_pointer_cast(ui_trackbar); } std::shared_ptr findTrackbar(const std::string& name) CV_OVERRIDE { cv::AutoLock lock(getWindowMutex()); auto i = trackbars_.find(name); if (i != trackbars_.end()) { return std::static_pointer_cast(i->second); } return std::shared_ptr(); } }; // GTKWindow class GTKTrackbar : public UITrackbar { protected: /*const*/ std::string name_; std::weak_ptr trackbar_; std::weak_ptr parent_; std::map > trackbars_; public: GTKTrackbar(const std::string& name, const std::shared_ptr& trackbar, const std::shared_ptr& parent) : trackbar_(trackbar) , parent_(parent) { name_ = std::string("<") + name + ">@" + parent->getID(); } ~GTKTrackbar() CV_OVERRIDE { if (!trackbar_.expired()) destroy(); CV_LOG_DEBUG(NULL, "OpenCV/UI/GTK: GTKTrackbar(" << name_ << ") is disposed"); } const std::string& getID() const CV_OVERRIDE { return name_; } bool isActive() const CV_OVERRIDE { return !trackbar_.expired(); } void destroy() CV_OVERRIDE { // nothing (destroyed with parent window, dedicated trackbar removal is not supported) } int getPos() const CV_OVERRIDE { auto trackbar = trackbar_.lock(); CV_Assert(trackbar); return trackbar->pos; } void setPos(int pos) CV_OVERRIDE { auto trackbar = trackbar_.lock(); CV_Assert(trackbar); return setTrackbarPos_(trackbar, pos); } cv::Range getRange() const CV_OVERRIDE { auto trackbar = trackbar_.lock(); CV_Assert(trackbar); return cv::Range(trackbar->minval, trackbar->maxval); } void setRange(const cv::Range& range) CV_OVERRIDE { auto trackbar = trackbar_.lock(); CV_Assert(trackbar); CV_CheckLE(range.start, range.end, "Invalid trackbar range"); gtk_range_set_range(GTK_RANGE(trackbar->widget), range.start, range.end); } }; // GTKTrackbar class GTKBackendUI : public UIBackend { public: GTKBackendUI() { // NB: avoid static initialization order fiasco (void)getGTKWindows(); } ~GTKBackendUI() CV_OVERRIDE { destroyAllWindows(); } void destroyAllWindows() CV_OVERRIDE { cvDestroyAllWindows(); } // namedWindow virtual std::shared_ptr createWindow( const std::string& winname, int flags ) CV_OVERRIDE { CV_LOG_INFO(NULL, "OpenCV/UI: Creating GTK window: " << winname << " (" << flags << ")"); auto window = namedWindow_(winname, flags); auto ui_window = std::make_shared(winname, window); return ui_window; } int waitKeyEx(int delay) CV_OVERRIDE { return cvWaitKey(delay); } int pollKey() CV_OVERRIDE { return cvWaitKey(1); // TODO } }; // GTKBackendUI static std::shared_ptr& getInstance() { static std::shared_ptr g_instance = std::make_shared(); return g_instance; } } // namespace impl #ifndef BUILD_PLUGIN namespace highgui_backend { std::shared_ptr createUIBackendGTK() { return impl::getInstance(); } } // namespace highgui_backend #endif } // namespace #ifdef BUILD_PLUGIN #define ABI_VERSION 0 #define API_VERSION 0 #include "plugin_api.hpp" static CvResult cv_getInstance(CV_OUT CvPluginUIBackend* handle) CV_NOEXCEPT { try { if (!handle) return CV_ERROR_FAIL; *handle = cv::impl::getInstance().get(); return CV_ERROR_OK; } catch (...) { return CV_ERROR_FAIL; } } static const OpenCV_UI_Plugin_API plugin_api = { { sizeof(OpenCV_UI_Plugin_API), ABI_VERSION, API_VERSION, CV_VERSION_MAJOR, CV_VERSION_MINOR, CV_VERSION_REVISION, CV_VERSION_STATUS, "GTK" CVAUX_STR(GTK_MAJOR_VERSION) " OpenCV UI plugin" }, { /* 1*/cv_getInstance } }; const OpenCV_UI_Plugin_API* CV_API_CALL opencv_ui_plugin_init_v0(int requested_abi_version, int requested_api_version, void* /*reserved=NULL*/) CV_NOEXCEPT { if (requested_abi_version == ABI_VERSION && requested_api_version <= API_VERSION) return &plugin_api; return NULL; } #endif // BUILD_PLUGIN #endif // HAVE_GTK