1/*M///////////////////////////////////////////////////////////////////////////////////////
2//
3// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
4//
5// By downloading, copying, installing or using the software you agree to this license.
6// If you do not agree to this license, do not download, install,
7// copy or use the software.
8//
9//
10// Intel License Agreement
11// For Open Source Computer Vision Library
12//
13// Copyright (C) 2000, Intel Corporation, all rights reserved.
14// Third party copyrights are property of their respective owners.
15//
16// Redistribution and use in source and binary forms, with or without modification,
17// are permitted provided that the following conditions are met:
18//
19// * Redistribution's of source code must retain the above copyright notice,
20// this list of conditions and the following disclaimer.
21//
22// * Redistribution's in binary form must reproduce the above copyright notice,
23// this list of conditions and the following disclaimer in the documentation
24// and/or other materials provided with the distribution.
25//
26// * The name of Intel Corporation may not be used to endorse or promote products
27// derived from this software without specific prior written permission.
28//
29// This software is provided by the copyright holders and contributors "as is" and
30// any express or implied warranties, including, but not limited to, the implied
31// warranties of merchantability and fitness for a particular purpose are disclaimed.
32// In no event shall the Intel Corporation or contributors be liable for any direct,
33// indirect, incidental, special, exemplary, or consequential damages
34// (including, but not limited to, procurement of substitute goods or services;
35// loss of use, data, or profits; or business interruption) however caused
36// and on any theory of liability, whether in contract, strict liability,
37// or tort (including negligence or otherwise) arising in any way out of
38// the use of this software, even if advised of the possibility of such damage.
39//
40//M*/
41
42#include "precomp.hpp"
43#include "backend.hpp"
44
45#if defined (HAVE_GTK)
46
47#include <gtk/gtk.h>
48
49#if (GTK_MAJOR_VERSION == 3) && defined(HAVE_OPENGL)
50 #undef HAVE_OPENGL // no support with GTK3
51#endif
52#if defined(HAVE_OPENGL) && !defined(HAVE_GTKGLEXT)
53 #undef HAVE_OPENGL // gtkglext is required
54#endif
55
56#include <gdk/gdkkeysyms.h>
57#include <gdk-pixbuf/gdk-pixbuf.h>
58#include <stdio.h>
59
60#if (GTK_MAJOR_VERSION == 2)
61 #define GTK_VERSION2 1
62#endif //GTK_MAJOR_VERSION >= 2.0
63#if (GTK_MAJOR_VERSION == 3)
64 #define GTK_VERSION3 1
65#endif //GTK_MAJOR_VERSION >= 3
66#if (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 4)
67 #define GTK_VERSION3_4 1
68#endif
69
70#ifdef HAVE_OPENGL
71 #include <gtk/gtkgl.h>
72 #include <GL/gl.h>
73 #include <GL/glu.h>
74#endif
75
76#include <opencv2/core/utils/logger.hpp>
77#include "opencv2/imgproc.hpp"
78
79using namespace cv;
80
81#ifndef BIT_ALLIN
82 #define BIT_ALLIN(x,y) ( ((x)&(y)) == (y) )
83#endif
84#ifndef BIT_MAP
85 #define BIT_MAP(x,y,z) ( ((x)&(y)) ? (z) : 0 )
86#endif
87
88// TODO Fix the initial window size when flags=0. Right now the initial window is by default
89// 320x240 size. A better default would be actual size of the image. Problem
90// is determining desired window size with trackbars while still allowing resizing.
91//
92// Gnome Totem source may be of use here, see bacon_video_widget_set_scale_ratio
93// in totem/src/backend/bacon-video-widget-xine.c
94
95////////////////////////////////////////////////////////////
96// CvImageWidget GTK Widget Public API
97////////////////////////////////////////////////////////////
98typedef struct _CvImageWidget CvImageWidget;
99typedef struct _CvImageWidgetClass CvImageWidgetClass;
100
101struct _CvImageWidget {
102 GtkWidget widget;
103 CvMat * original_image;
104 CvMat * scaled_image;
105 int flags;
106};
107
108struct _CvImageWidgetClass
109{
110 GtkWidgetClass parent_class;
111};
112
113
114/** Allocate new image viewer widget */
115GtkWidget* cvImageWidgetNew (int flags);
116
117// standard GTK object macros
118#define CV_IMAGE_WIDGET(obj) G_TYPE_CHECK_INSTANCE_CAST (obj, cvImageWidget_get_type (), CvImageWidget)
119#define CV_IMAGE_WIDGET_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, cvImageWidget_get_type (), CvImageWidgetClass)
120#define CV_IS_IMAGE_WIDGET(obj) G_TYPE_CHECK_INSTANCE_TYPE (obj, cvImageWidget_get_type ())
121
122/////////////////////////////////////////////////////////////////////////////
123// Private API ////////////////////////////////////////////////////////
124/////////////////////////////////////////////////////////////////////////////
125GType cvImageWidget_get_type (void);
126
127static GtkWidgetClass * parent_class = NULL;
128
129// flag to help size initial window
130#define CV_WINDOW_NO_IMAGE 2
131
132/** Set the image to display in the widget */
133static
134void cvImageWidgetSetImage(CvImageWidget * widget, const CvArr *arr)
135{
136 CvMat * mat, stub;
137 int origin=0;
138
139 //printf("cvImageWidgetSetImage\n");
140
141 if( CV_IS_IMAGE_HDR( arr ))
142 origin = ((IplImage*)arr)->origin;
143
144 mat = cvGetMat(arr, header: &stub);
145
146 if(widget->original_image && !CV_ARE_SIZES_EQ(mat, widget->original_image)){
147 cvReleaseMat( mat: &widget->original_image );
148 }
149 if(!widget->original_image){
150 widget->original_image = cvCreateMat( rows: mat->rows, cols: mat->cols, CV_8UC3 );
151 gtk_widget_queue_resize( GTK_WIDGET( widget ) );
152 }
153 CV_Assert(origin == 0);
154 convertToShow(src: cv::cvarrToMat(arr), arr: widget->original_image);
155 if(widget->scaled_image){
156 cvResize( src: widget->original_image, dst: widget->scaled_image, interpolation: CV_INTER_AREA );
157 }
158
159 // window does not refresh without this
160 gtk_widget_queue_draw( GTK_WIDGET(widget) );
161}
162
163GtkWidget*
164cvImageWidgetNew (int flags)
165{
166 CvImageWidget *image_widget;
167
168 image_widget = CV_IMAGE_WIDGET( gtk_widget_new (cvImageWidget_get_type (), NULL) );
169 CV_Assert(image_widget && "GTK widget creation is failed. Ensure that there is no GTK2/GTK3 libraries conflict");
170 image_widget->original_image = 0;
171 image_widget->scaled_image = 0;
172 image_widget->flags = flags | CV_WINDOW_NO_IMAGE;
173
174 return GTK_WIDGET (image_widget);
175}
176
177static void
178cvImageWidget_realize (GtkWidget *widget)
179{
180 GdkWindowAttr attributes;
181 gint attributes_mask;
182
183#if defined(GTK_VERSION3)
184 GtkAllocation allocation;
185 gtk_widget_get_allocation(widget, allocation: &allocation);
186#endif //GTK_VERSION3
187
188 //printf("cvImageWidget_realize\n");
189 g_return_if_fail (widget != NULL);
190 g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
191
192 gtk_widget_set_realized(widget, TRUE);
193
194#if defined(GTK_VERSION3)
195 attributes.x = allocation.x;
196 attributes.y = allocation.y;
197 attributes.width = allocation.width;
198 attributes.height = allocation.height;
199#elif defined(GTK_VERSION2)
200 attributes.x = widget->allocation.x;
201 attributes.y = widget->allocation.y;
202 attributes.width = widget->allocation.width;
203 attributes.height = widget->allocation.height;
204#endif //GTK_VERSION3
205
206 attributes.wclass = GDK_INPUT_OUTPUT;
207 attributes.window_type = GDK_WINDOW_CHILD;
208 attributes.event_mask = gtk_widget_get_events (widget) |
209 GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
210 GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK;
211 attributes.visual = gtk_widget_get_visual (widget);
212
213#if defined(GTK_VERSION3)
214 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
215 gtk_widget_set_window(
216 widget,
217 window: gdk_window_new(
218 parent: gtk_widget_get_parent_window(widget),
219 attributes: &attributes,
220 attributes_mask
221 )
222 );
223
224 gtk_widget_set_style(
225 widget,
226 style: gtk_style_attach(
227 style: gtk_widget_get_style(widget),
228 window: gtk_widget_get_window(widget)
229 )
230 );
231
232 gdk_window_set_user_data (
233 window: gtk_widget_get_window(widget),
234 user_data: widget
235 );
236
237 gtk_style_set_background (
238 style: gtk_widget_get_style(widget),
239 window: gtk_widget_get_window(widget),
240 state_type: GTK_STATE_ACTIVE
241 );
242 #elif defined(GTK_VERSION2)
243 // The following lines are included to prevent breaking
244 // compatibility with older Gtk2 (<gtk+-2.18) libraries.
245 attributes.colormap = gtk_widget_get_colormap (widget);
246 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
247 widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
248
249 widget->style = gtk_style_attach (widget->style, widget->window);
250 gdk_window_set_user_data (widget->window, widget);
251
252 gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
253#endif // GTK_VERSION3
254}
255
256static CvSize cvImageWidget_calc_size( int im_width, int im_height, int max_width, int max_height ){
257 float aspect = (float)im_width/(float)im_height;
258 float max_aspect = (float)max_width/(float)max_height;
259 if(aspect > max_aspect){
260 return cvSize( width: max_width, height: cvRound(value: max_width/aspect) );
261 }
262 return cvSize( width: cvRound(value: max_height*aspect), height: max_height );
263}
264
265#if defined (GTK_VERSION3)
266static void
267cvImageWidget_get_preferred_width (GtkWidget *widget, gint *minimal_width, gint *natural_width)
268{
269 g_return_if_fail (widget != NULL);
270 g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
271 CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
272
273 if(image_widget->original_image != NULL) {
274 *minimal_width = (image_widget->flags & CV_WINDOW_AUTOSIZE) != CV_WINDOW_AUTOSIZE ?
275 gdk_window_get_width(window: gtk_widget_get_window(widget)) : image_widget->original_image->cols;
276 }
277 else {
278 *minimal_width = 320;
279 }
280
281 if(image_widget->scaled_image != NULL) {
282 *natural_width = *minimal_width < image_widget->scaled_image->cols ?
283 image_widget->scaled_image->cols : *minimal_width;
284 }
285 else {
286 *natural_width = *minimal_width;
287 }
288}
289
290static void
291cvImageWidget_get_preferred_height (GtkWidget *widget, gint *minimal_height, gint *natural_height)
292{
293 g_return_if_fail (widget != NULL);
294 g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
295 CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
296
297 if(image_widget->original_image != NULL) {
298 *minimal_height = (image_widget->flags & CV_WINDOW_AUTOSIZE) != CV_WINDOW_AUTOSIZE ?
299 gdk_window_get_height(window: gtk_widget_get_window(widget)) : image_widget->original_image->rows;
300 }
301 else {
302 *minimal_height = 240;
303 }
304
305 if(image_widget->scaled_image != NULL) {
306 *natural_height = *minimal_height < image_widget->scaled_image->rows ?
307 image_widget->scaled_image->rows : *minimal_height;
308 }
309 else {
310 *natural_height = *minimal_height;
311 }
312}
313#endif //GTK_VERSION3
314
315#if defined (GTK_VERSION2)
316static void
317cvImageWidget_size_request (GtkWidget *widget,
318 GtkRequisition *requisition)
319{
320 CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
321
322 //printf("cvImageWidget_size_request ");
323 // the case the first time cvShowImage called or when AUTOSIZE
324 if( image_widget->original_image &&
325 ((image_widget->flags & CV_WINDOW_AUTOSIZE) ||
326 (image_widget->flags & CV_WINDOW_NO_IMAGE)))
327 {
328 //printf("original ");
329 requisition->width = image_widget->original_image->cols;
330 requisition->height = image_widget->original_image->rows;
331 }
332 // default case
333 else if(image_widget->scaled_image){
334 //printf("scaled ");
335 requisition->width = image_widget->scaled_image->cols;
336 requisition->height = image_widget->scaled_image->rows;
337 }
338 // the case before cvShowImage called
339 else{
340 //printf("default ");
341 requisition->width = 320;
342 requisition->height = 240;
343 }
344 //printf("%d %d\n",requisition->width, requisition->height);
345}
346#endif //GTK_VERSION2
347
348static void cvImageWidget_set_size(GtkWidget * widget, int max_width, int max_height){
349 CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
350
351 //printf("cvImageWidget_set_size %d %d\n", max_width, max_height);
352
353 // don't allow to set the size
354 if(image_widget->flags & CV_WINDOW_AUTOSIZE) return;
355 if(!image_widget->original_image) return;
356
357 CvSize scaled_image_size = cvImageWidget_calc_size( im_width: image_widget->original_image->cols,
358 im_height: image_widget->original_image->rows, max_width, max_height );
359
360 if( image_widget->scaled_image &&
361 ( image_widget->scaled_image->cols != scaled_image_size.width ||
362 image_widget->scaled_image->rows != scaled_image_size.height ))
363 {
364 cvReleaseMat( mat: &image_widget->scaled_image );
365 }
366 if( !image_widget->scaled_image ){
367 image_widget->scaled_image = cvCreateMat( rows: scaled_image_size.height, cols: scaled_image_size.width, CV_8UC3 );
368
369
370 }
371 CV_Assert(image_widget->scaled_image);
372}
373
374static void
375cvImageWidget_size_allocate (GtkWidget *widget,
376 GtkAllocation *allocation)
377{
378 CvImageWidget *image_widget;
379
380 //printf("cvImageWidget_size_allocate\n");
381 g_return_if_fail (widget != NULL);
382 g_return_if_fail (CV_IS_IMAGE_WIDGET (widget));
383 g_return_if_fail (allocation != NULL);
384
385#if defined (GTK_VERSION3)
386 gtk_widget_set_allocation(widget, allocation);
387#elif defined (GTK_VERSION2)
388 widget->allocation = *allocation;
389#endif //GTK_VERSION3
390 image_widget = CV_IMAGE_WIDGET (widget);
391
392
393 if( (image_widget->flags & CV_WINDOW_AUTOSIZE)==0 && image_widget->original_image ){
394 // (re) allocated scaled image
395 if( image_widget->flags & CV_WINDOW_NO_IMAGE ){
396 cvImageWidget_set_size( widget, max_width: image_widget->original_image->cols,
397 max_height: image_widget->original_image->rows);
398 }
399 else{
400 cvImageWidget_set_size( widget, max_width: allocation->width, max_height: allocation->height );
401 }
402 cvResize( src: image_widget->original_image, dst: image_widget->scaled_image, interpolation: CV_INTER_AREA );
403 }
404
405 if (gtk_widget_get_realized (widget))
406 {
407 image_widget = CV_IMAGE_WIDGET (widget);
408
409 if( image_widget->original_image &&
410 ((image_widget->flags & CV_WINDOW_AUTOSIZE) ||
411 (image_widget->flags & CV_WINDOW_NO_IMAGE)) )
412 {
413#if defined (GTK_VERSION3)
414 allocation->width = image_widget->original_image->cols;
415 allocation->height = image_widget->original_image->rows;
416 gtk_widget_set_allocation(widget, allocation);
417#elif defined (GTK_VERSION2)
418 widget->allocation.width = image_widget->original_image->cols;
419 widget->allocation.height = image_widget->original_image->rows;
420#endif //GTK_VERSION3
421 gdk_window_move_resize( window: gtk_widget_get_window(widget),
422 x: allocation->x, y: allocation->y,
423 width: image_widget->original_image->cols, height: image_widget->original_image->rows );
424 if(image_widget->flags & CV_WINDOW_NO_IMAGE){
425 image_widget->flags &= ~CV_WINDOW_NO_IMAGE;
426 gtk_widget_queue_resize( GTK_WIDGET(widget) );
427 }
428 }
429 else{
430 gdk_window_move_resize (window: gtk_widget_get_window(widget),
431 x: allocation->x, y: allocation->y,
432 width: allocation->width, height: allocation->height );
433 }
434 }
435}
436
437#if defined (GTK_VERSION3)
438static void
439cvImageWidget_destroy (GtkWidget *object)
440#else
441static void
442cvImageWidget_destroy (GtkObject *object)
443#endif //GTK_VERSION3
444{
445 CvImageWidget *image_widget;
446
447 g_return_if_fail (object != NULL);
448 g_return_if_fail (CV_IS_IMAGE_WIDGET (object));
449
450 image_widget = CV_IMAGE_WIDGET (object);
451
452 cvReleaseMat( mat: &image_widget->scaled_image );
453 cvReleaseMat( mat: &image_widget->original_image );
454
455#if defined (GTK_VERSION3)
456 if (GTK_WIDGET_CLASS (parent_class)->destroy)
457 (* GTK_WIDGET_CLASS (parent_class)->destroy) (object);
458#else
459 if (GTK_OBJECT_CLASS (parent_class)->destroy)
460 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
461#endif //GTK_VERSION3
462}
463
464static void cvImageWidget_class_init (gpointer g_class, gpointer /*class_data*/)
465{
466 CvImageWidgetClass* klass = (CvImageWidgetClass*)g_class;
467#if defined (GTK_VERSION3)
468 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
469#endif //GTK_VERSION3
470#if defined (GTK_VERSION2)
471 GtkObjectClass *object_class = (GtkObjectClass*) klass;
472 GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
473#endif //GTK_VERSION2
474
475 parent_class = GTK_WIDGET_CLASS( g_type_class_peek (gtk_widget_get_type ()) );
476
477#if defined (GTK_VERSION3)
478 widget_class->destroy = cvImageWidget_destroy;
479 widget_class->get_preferred_width = cvImageWidget_get_preferred_width;
480 widget_class->get_preferred_height = cvImageWidget_get_preferred_height;
481#endif //GTK_VERSION3
482#if defined (GTK_VERSION2)
483 object_class->destroy = cvImageWidget_destroy;
484 widget_class->size_request = cvImageWidget_size_request;
485#endif //GTK_VERSION2
486
487 widget_class->realize = cvImageWidget_realize;
488 widget_class->size_allocate = cvImageWidget_size_allocate;
489 widget_class->button_press_event = NULL;
490 widget_class->button_release_event = NULL;
491 widget_class->motion_notify_event = NULL;
492}
493
494static void
495cvImageWidget_init(GTypeInstance* instance, gpointer /*g_class*/)
496{
497 CvImageWidget* image_widget = (CvImageWidget*)instance;
498 image_widget->original_image=0;
499 image_widget->scaled_image=0;
500 image_widget->flags=0;
501}
502
503GType cvImageWidget_get_type (void){
504 static GType image_type = 0;
505
506 if (!image_type)
507 {
508 image_type = g_type_register_static_simple(
509 GTK_TYPE_WIDGET,
510 type_name: (gchar*) "CvImageWidget",
511 class_size: sizeof(CvImageWidgetClass),
512 class_init: cvImageWidget_class_init,
513 instance_size: sizeof(CvImageWidget),
514 instance_init: cvImageWidget_init,
515 flags: (GTypeFlags)0
516 );
517 }
518
519 return image_type;
520}
521/////////////////////////////////////////////////////////////////////////////
522// End CvImageWidget
523/////////////////////////////////////////////////////////////////////////////
524
525
526struct CvWindow;
527
528struct CvUIBase {
529 CvUIBase(int signature_) : signature(signature_) { }
530
531 int signature;
532};
533
534struct CvTrackbar : CvUIBase
535{
536 CvTrackbar(const std::string& trackbar_name) :
537 CvUIBase(CV_TRACKBAR_MAGIC_VAL),
538 widget(NULL), name(trackbar_name),
539 parent(NULL), data(NULL),
540 pos(0), maxval(0), minval(0),
541 notify(NULL), notify2(NULL), // deprecated
542 onChangeCallback(NULL), userdata(NULL)
543 {
544 // nothing
545 }
546 ~CvTrackbar()
547 {
548 // destroyed by parent window
549 }
550
551 GtkWidget* widget;
552 std::string name;
553 CvWindow* parent; // TODO weak_ptr
554 int* data;
555 int pos;
556 int maxval;
557 int minval;
558 CvTrackbarCallback notify; // deprecated
559 CvTrackbarCallback2 notify2; // deprecated
560 TrackbarCallback onChangeCallback;
561 void* userdata;
562};
563
564
565struct CvWindow : CvUIBase
566{
567 CvWindow(const std::string& window_name) :
568 CvUIBase(CV_WINDOW_MAGIC_VAL),
569 widget(NULL), frame(NULL), paned(NULL), name(window_name),
570 last_key(0), flags(0), status(0),
571 on_mouse(NULL), on_mouse_param(NULL)
572#ifdef HAVE_OPENGL
573 ,useGl(false), glDrawCallback(NULL), glDrawData(NULL)
574#endif
575 {
576 CV_LOG_INFO(NULL, "OpenCV/UI: creating GTK window: " << window_name);
577 }
578 ~CvWindow();
579 void destroy();
580
581 GtkWidget* widget;
582 GtkWidget* frame;
583 GtkWidget* paned;
584 std::string name;
585
586 int last_key;
587 int flags;
588 int status;//0 normal, 1 fullscreen (YV)
589
590 CvMouseCallback on_mouse;
591 void* on_mouse_param;
592
593 std::vector< std::shared_ptr<CvTrackbar> > trackbars;
594
595#ifdef HAVE_OPENGL
596 bool useGl;
597
598 CvOpenGlDrawCallback glDrawCallback;
599 void* glDrawData;
600#endif
601};
602
603
604static gboolean icvOnClose( GtkWidget* widget, GdkEvent* event, gpointer user_data );
605static gboolean icvOnKeyPress( GtkWidget* widget, GdkEventKey* event, gpointer user_data );
606static void icvOnTrackbar( GtkWidget* widget, gpointer user_data );
607static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_data );
608
609#ifdef HAVE_GTHREAD
610int thread_started=0;
611static gpointer icvWindowThreadLoop(gpointer data);
612GMutex* last_key_mutex = NULL;
613GCond* cond_have_key = NULL;
614GThread* window_thread = NULL;
615#endif
616
617static int last_key = -1;
618
619static
620std::vector< std::shared_ptr<CvWindow> >& getGTKWindows()
621{
622 static std::vector< std::shared_ptr<CvWindow> > g_windows;
623 return g_windows;
624}
625
626CV_IMPL int cvInitSystem( int argc, char** argv )
627{
628 static int wasInitialized = 0;
629 static bool hasError = false;
630
631 // check initialization status
632 if( !wasInitialized )
633 {
634 if (!gtk_init_check(argc: &argc, argv: &argv))
635 {
636 hasError = true;
637 wasInitialized = true;
638 CV_Error(Error::StsError, "Can't initialize GTK backend");
639 }
640
641 setlocale(LC_NUMERIC,locale: "C");
642
643 #ifdef HAVE_OPENGL
644 if (!gtk_gl_init_check(&argc, &argv))
645 {
646 hasError = true;
647 wasInitialized = true;
648 CV_Error(Error::StsError, "Can't initialize GTK-OpenGL backend");
649 }
650 #endif
651
652 wasInitialized = 1;
653 }
654 if (hasError)
655 CV_Error(Error::StsError, "GTK backend is not available");
656
657 return 0;
658}
659
660CV_IMPL int cvStartWindowThread(){
661#ifdef HAVE_GTHREAD
662 cvInitSystem(argc: 0,NULL);
663 if (!thread_started)
664 {
665 (void)getWindowMutex(); // force mutex initialization
666
667 // protects the 'last key pressed' variable
668 last_key_mutex = g_mutex_new();
669
670 // conditional that indicates a key has been pressed
671 cond_have_key = g_cond_new();
672
673 window_thread = g_thread_new(name: "OpenCV window update", func: icvWindowThreadLoop, NULL);
674 }
675 thread_started = window_thread!=NULL;
676 return thread_started;
677#else
678 return 0;
679#endif
680}
681
682#ifdef HAVE_GTHREAD
683gpointer icvWindowThreadLoop(gpointer /*data*/)
684{
685 while(1){
686 {
687 cv::AutoLock lock(getWindowMutex());
688 gtk_main_iteration_do(FALSE);
689 }
690
691 // little sleep
692 g_usleep(microseconds: 500);
693
694 g_thread_yield();
695 }
696 return NULL;
697}
698
699#endif
700
701#define CV_LOCK_MUTEX() cv::AutoLock lock(getWindowMutex())
702
703static
704std::shared_ptr<CvWindow> icvFindWindowByName(const std::string& name)
705{
706 auto& g_windows = getGTKWindows();
707 for(size_t i = 0; i < g_windows.size(); ++i)
708 {
709 auto window = g_windows[i];
710 if (!window)
711 continue;
712 if (window->name == name)
713 return window;
714 }
715 return std::shared_ptr<CvWindow>();
716}
717
718static inline
719std::shared_ptr<CvWindow> icvFindWindowByName(const char* name)
720{
721 CV_Assert(name);
722 return icvFindWindowByName(name: std::string(name));
723}
724
725
726static CvWindow* icvWindowByWidget( GtkWidget* widget )
727{
728 auto& g_windows = getGTKWindows();
729 for (size_t i = 0; i < g_windows.size(); ++i)
730 {
731 CvWindow* window = g_windows[i].get();
732 if (window->widget == widget || window->frame == widget || window->paned == widget)
733 return window;
734 }
735 return NULL;
736}
737
738static Rect getImageRect_(const std::shared_ptr<CvWindow>& window);
739
740CvRect cvGetWindowRect_GTK(const char* name)
741{
742 CV_Assert(name && "NULL name string");
743
744 CV_LOCK_MUTEX();
745 const auto window = icvFindWindowByName(name);
746 if (!window)
747 CV_Error( CV_StsNullPtr, "NULL window" );
748
749 return cvRect(rc: getImageRect_(window));
750}
751
752#if defined(GTK_VERSION2)
753 #define gtk_widget_get_allocated_width(widget) (widget->allocation.width)
754 #define gtk_widget_get_allocated_height(widget) (widget->allocation.height)
755#endif
756
757static Rect getImageRect_(const std::shared_ptr<CvWindow>& window)
758{
759 CV_Assert(window);
760
761 gint wx, wy;
762#ifdef HAVE_OPENGL
763 if (window->useGl) {
764 gtk_widget_translate_coordinates(window->widget, gtk_widget_get_toplevel(window->widget), 0, 0, &wx, &wy);
765 return Rect(wx, wy, gtk_widget_get_allocated_width(window->widget), gtk_widget_get_allocated_height(window->widget));
766 }
767#endif
768
769 CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget );
770 gtk_widget_translate_coordinates(src_widget: &image_widget->widget, dest_widget: gtk_widget_get_toplevel(widget: &image_widget->widget), src_x: 0, src_y: 0, dest_x: &wx, dest_y: &wy);
771 if (image_widget->scaled_image) {
772 return Rect(wx, wy, MIN(image_widget->scaled_image->cols, gtk_widget_get_allocated_width(window->widget)),
773 MIN(image_widget->scaled_image->rows, gtk_widget_get_allocated_height(window->widget)));
774 } else if (image_widget->original_image) {
775 return Rect(wx, wy, MIN(image_widget->original_image->cols, gtk_widget_get_allocated_width(window->widget)),
776 MIN(image_widget->original_image->rows, gtk_widget_get_allocated_height(window->widget)));
777 }
778
779 return Rect(-1, -1, -1, -1);
780}
781
782double cvGetModeWindow_GTK(const char* name)//YV
783{
784 CV_Assert(name && "NULL name string");
785
786 CV_LOCK_MUTEX();
787 const auto window = icvFindWindowByName(name);
788 if (!window)
789 CV_Error( CV_StsNullPtr, "NULL window" );
790
791 double result = window->status;
792 return result;
793}
794
795static bool setModeWindow_(const std::shared_ptr<CvWindow>& window, int mode);
796void cvSetModeWindow_GTK( const char* name, double prop_value)//Yannick Verdie
797{
798 CV_Assert(name && "NULL name string");
799
800 CV_LOCK_MUTEX();
801
802 const auto window = icvFindWindowByName(name);
803 if (!window)
804 CV_Error( CV_StsNullPtr, "NULL window" );
805
806 setModeWindow_(window, mode: (int)prop_value);
807}
808
809static bool setModeWindow_(const std::shared_ptr<CvWindow>& window, int mode)
810{
811 if (window->flags & CV_WINDOW_AUTOSIZE) //if the flag CV_WINDOW_AUTOSIZE is set
812 return false;
813
814 //so easy to do fullscreen here, Linux rocks !
815
816 if (window->status == mode)
817 return true;
818
819 if (window->status==CV_WINDOW_FULLSCREEN && mode==CV_WINDOW_NORMAL)
820 {
821 gtk_window_unfullscreen(GTK_WINDOW(window->frame));
822 window->status=CV_WINDOW_NORMAL;
823 return true;
824 }
825
826 if (window->status==CV_WINDOW_NORMAL && mode==CV_WINDOW_FULLSCREEN)
827 {
828 gtk_window_fullscreen(GTK_WINDOW(window->frame));
829 window->status=CV_WINDOW_FULLSCREEN;
830 return true;
831 }
832
833 return false;
834}
835
836void setWindowTitle_GTK(const String& winname, const String& title)
837{
838 CV_LOCK_MUTEX();
839
840 auto window = icvFindWindowByName(name: winname.c_str());
841
842 if (!window)
843 {
844 namedWindow(winname);
845 window = icvFindWindowByName(name: winname.c_str());
846 CV_Assert(window);
847 }
848
849 gtk_window_set_title(GTK_WINDOW(window->frame), title: title.c_str());
850}
851
852double cvGetPropWindowAutoSize_GTK(const char* name)
853{
854 CV_Assert(name && "NULL name string");
855
856 CV_LOCK_MUTEX();
857
858 const auto window = icvFindWindowByName(name);
859 if (!window)
860 return -1; // keep silence here
861
862 double result = window->flags & CV_WINDOW_AUTOSIZE;
863 return result;
864}
865
866static double getRatioWindow_(const std::shared_ptr<CvWindow>& window);
867double cvGetRatioWindow_GTK(const char* name)
868{
869 CV_Assert(name && "NULL name string");
870
871 CV_LOCK_MUTEX();
872
873 const auto window = icvFindWindowByName(name);
874 if (!window)
875 return -1; // keep silence here
876
877 return getRatioWindow_(window);
878}
879
880static double getRatioWindow_(const std::shared_ptr<CvWindow>& window)
881{
882 double result = static_cast<double>(
883 gtk_widget_get_allocated_width(widget: window->widget)) / gtk_widget_get_allocated_height(widget: window->widget);
884 return result;
885}
886
887double cvGetOpenGlProp_GTK(const char* name)
888{
889#ifdef HAVE_OPENGL
890 CV_Assert(name && "NULL name string");
891
892 CV_LOCK_MUTEX();
893
894 const auto window = icvFindWindowByName(name);
895 if (!window)
896 return -1; // keep silence here
897
898 double result = window->useGl;
899 return result;
900#else
901 (void)name;
902 return -1;
903#endif
904}
905
906
907// OpenGL support
908
909#ifdef HAVE_OPENGL
910
911namespace
912{
913 void createGlContext(CvWindow* window)
914 {
915 GdkGLConfig* glconfig;
916
917 // Try double-buffered visual
918 glconfig = gdk_gl_config_new_by_mode((GdkGLConfigMode)(GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE));
919 if (!glconfig)
920 CV_Error( CV_OpenGlApiCallError, "Can't Create A GL Device Context" );
921
922 // Set OpenGL-capability to the widget
923 if (!gtk_widget_set_gl_capability(window->widget, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE))
924 CV_Error( CV_OpenGlApiCallError, "Can't Create A GL Device Context" );
925
926 window->useGl = true;
927 }
928
929 void drawGl(CvWindow* window)
930 {
931 GdkGLContext* glcontext = gtk_widget_get_gl_context(window->widget);
932 GdkGLDrawable* gldrawable = gtk_widget_get_gl_drawable(window->widget);
933
934 if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
935 CV_Error( CV_OpenGlApiCallError, "Can't Activate The GL Rendering Context" );
936
937 glViewport(0, 0, gtk_widget_get_allocated_width(window->widget), gtk_widget_get_allocated_height(window->widget));
938
939 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
940
941 if (window->glDrawCallback)
942 window->glDrawCallback(window->glDrawData);
943
944 if (gdk_gl_drawable_is_double_buffered (gldrawable))
945 gdk_gl_drawable_swap_buffers(gldrawable);
946 else
947 glFlush();
948
949 gdk_gl_drawable_gl_end(gldrawable);
950 }
951}
952
953#endif // HAVE_OPENGL
954
955#if defined (GTK_VERSION2)
956static gboolean cvImageWidget_expose(GtkWidget* widget, GdkEventExpose* event, gpointer data)
957#elif defined (GTK_VERSION3)
958static gboolean cvImageWidget_draw(GtkWidget* widget, cairo_t *cr, gpointer data)
959#endif
960{
961#ifdef HAVE_OPENGL
962 CvWindow* window = (CvWindow*)data;
963
964 if (window->useGl)
965 {
966 drawGl(window);
967 return TRUE;
968 }
969#else
970 (void)data;
971#endif
972#if defined (GTK_VERSION2)
973 (void)event;
974#endif
975
976 CvImageWidget *image_widget = NULL;
977 GdkPixbuf *pixbuf = NULL;
978
979 g_return_val_if_fail (widget != NULL, FALSE);
980 g_return_val_if_fail (CV_IS_IMAGE_WIDGET (widget), FALSE);
981
982 image_widget = CV_IMAGE_WIDGET (widget);
983#if defined (GTK_VERSION2)
984 cairo_t *cr = gdk_cairo_create(widget->window);
985#endif
986
987 if( image_widget->scaled_image ){
988 // center image in available region
989 int x0 = (gtk_widget_get_allocated_width(widget) - image_widget->scaled_image->cols)/2;
990 int y0 = (gtk_widget_get_allocated_height(widget) - image_widget->scaled_image->rows)/2;
991
992 pixbuf = gdk_pixbuf_new_from_data(data: image_widget->scaled_image->data.ptr, colorspace: GDK_COLORSPACE_RGB, has_alpha: false,
993 bits_per_sample: 8, MIN(image_widget->scaled_image->cols, gtk_widget_get_allocated_width(widget)),
994 MIN(image_widget->scaled_image->rows, gtk_widget_get_allocated_height(widget)),
995 rowstride: image_widget->scaled_image->step, NULL, NULL);
996
997 gdk_cairo_set_source_pixbuf(cr, pixbuf, pixbuf_x: x0, pixbuf_y: y0);
998 }
999 else if( image_widget->original_image ){
1000 pixbuf = gdk_pixbuf_new_from_data(data: image_widget->original_image->data.ptr, colorspace: GDK_COLORSPACE_RGB, has_alpha: false,
1001 bits_per_sample: 8, MIN(image_widget->original_image->cols, gtk_widget_get_allocated_width(widget)),
1002 MIN(image_widget->original_image->rows, gtk_widget_get_allocated_height(widget)),
1003 rowstride: image_widget->original_image->step, NULL, NULL);
1004 gdk_cairo_set_source_pixbuf(cr, pixbuf, pixbuf_x: 0, pixbuf_y: 0);
1005 }
1006
1007 cairo_paint(cr);
1008 if(pixbuf)
1009 g_object_unref(object: pixbuf);
1010#if defined (GTK_VERSION2)
1011 cairo_destroy(cr);
1012#endif
1013 return TRUE;
1014}
1015
1016static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags);
1017CV_IMPL int cvNamedWindow( const char* name, int flags )
1018{
1019 cvInitSystem(argc: name ? 1 : 0,argv: (char**)&name);
1020 CV_Assert(name && "NULL name string");
1021
1022 CV_LOCK_MUTEX();
1023
1024 // Check the name in the storage
1025 if (icvFindWindowByName(name))
1026 {
1027 return 1;
1028 }
1029 auto window = namedWindow_(name, flags);
1030 return window ? 1 : 0;
1031}
1032
1033static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags)
1034{
1035 cvInitSystem(argc: 0, NULL);
1036
1037 auto window_ptr = std::make_shared<CvWindow>(args: name);
1038 CvWindow* window = window_ptr.get();
1039 window->flags = flags;
1040 window->status = CV_WINDOW_NORMAL;//YV
1041
1042 window->frame = gtk_window_new( type: GTK_WINDOW_TOPLEVEL );
1043
1044 window->paned = gtk_vbox_new( FALSE, spacing: 0 );
1045 window->widget = cvImageWidgetNew( flags );
1046 gtk_box_pack_end( GTK_BOX(window->paned), child: window->widget, TRUE, TRUE, padding: 0 );
1047 gtk_widget_show( widget: window->widget );
1048 gtk_container_add( GTK_CONTAINER(window->frame), widget: window->paned );
1049 gtk_widget_show( widget: window->paned );
1050
1051#ifndef HAVE_OPENGL
1052 if (flags & CV_WINDOW_OPENGL)
1053 CV_Error( CV_OpenGlNotSupported, "Library was built without OpenGL support" );
1054#else
1055 if (flags & CV_WINDOW_OPENGL)
1056 createGlContext(window);
1057
1058 window->glDrawCallback = 0;
1059 window->glDrawData = 0;
1060#endif
1061
1062 //
1063 // configure event handlers
1064 // TODO -- move this to CvImageWidget ?
1065 g_signal_connect( window->frame, "key-press-event",
1066 G_CALLBACK(icvOnKeyPress), window );
1067 g_signal_connect( window->widget, "button-press-event",
1068 G_CALLBACK(icvOnMouse), window );
1069 g_signal_connect( window->widget, "button-release-event",
1070 G_CALLBACK(icvOnMouse), window );
1071 g_signal_connect( window->widget, "motion-notify-event",
1072 G_CALLBACK(icvOnMouse), window );
1073 g_signal_connect( window->widget, "scroll-event",
1074 G_CALLBACK(icvOnMouse), window );
1075 g_signal_connect( window->frame, "delete-event",
1076 G_CALLBACK(icvOnClose), window );
1077#if defined(GTK_VERSION3)
1078 g_signal_connect( window->widget, "draw",
1079 G_CALLBACK(cvImageWidget_draw), window );
1080#else
1081 g_signal_connect( window->widget, "expose-event",
1082 G_CALLBACK(cvImageWidget_expose), window );
1083#endif //GTK_VERSION3
1084
1085
1086#if defined(GTK_VERSION3_4)
1087 gtk_widget_add_events (widget: window->widget, events: GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK) ;
1088#else
1089 gtk_widget_add_events (window->widget, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK) ;
1090#endif //GTK_VERSION3_4
1091
1092 gtk_widget_show( widget: window->frame );
1093 gtk_window_set_title(GTK_WINDOW(window->frame), title: name.c_str());
1094
1095 {
1096 AutoLock lock(getWindowMutex());
1097 getGTKWindows().push_back(x: window_ptr);
1098 }
1099
1100 bool b_nautosize = ((flags & CV_WINDOW_AUTOSIZE) == 0);
1101 gtk_window_set_resizable( GTK_WINDOW(window->frame), resizable: b_nautosize );
1102
1103 // allow window to be resized
1104 if( b_nautosize ){
1105 GdkGeometry geometry;
1106 geometry.min_width = 50;
1107 geometry.min_height = 50;
1108 gtk_window_set_geometry_hints( GTK_WINDOW( window->frame ), GTK_WIDGET( window->widget ),
1109 geometry: &geometry, geom_mask: (GdkWindowHints) (GDK_HINT_MIN_SIZE));
1110 }
1111
1112#ifdef HAVE_OPENGL
1113 if (window->useGl)
1114 cvSetOpenGlContext(name.c_str());
1115#endif
1116
1117 return window_ptr;
1118}
1119
1120
1121#ifdef HAVE_OPENGL
1122
1123CV_IMPL void cvSetOpenGlContext(const char* name)
1124{
1125 GdkGLContext* glcontext;
1126 GdkGLDrawable* gldrawable;
1127
1128 CV_Assert(name && "NULL name string");
1129
1130 CV_LOCK_MUTEX();
1131
1132 auto window = icvFindWindowByName(name);
1133 if (!window)
1134 CV_Error( CV_StsNullPtr, "NULL window" );
1135
1136 if (!window->useGl)
1137 CV_Error( CV_OpenGlNotSupported, "Window doesn't support OpenGL" );
1138
1139 glcontext = gtk_widget_get_gl_context(window->widget);
1140 gldrawable = gtk_widget_get_gl_drawable(window->widget);
1141
1142 if (!gdk_gl_drawable_make_current(gldrawable, glcontext))
1143 CV_Error( CV_OpenGlApiCallError, "Can't Activate The GL Rendering Context" );
1144}
1145
1146CV_IMPL void cvUpdateWindow(const char* name)
1147{
1148 CV_Assert(name && "NULL name string");
1149
1150 CV_LOCK_MUTEX();
1151
1152 auto window = icvFindWindowByName(name);
1153 if (!window)
1154 return;
1155
1156 // window does not refresh without this
1157 gtk_widget_queue_draw( GTK_WIDGET(window->widget) );
1158}
1159
1160CV_IMPL void cvSetOpenGlDrawCallback(const char* name, CvOpenGlDrawCallback callback, void* userdata)
1161{
1162 CV_Assert(name && "NULL name string");
1163
1164 CV_LOCK_MUTEX();
1165
1166 auto window = icvFindWindowByName(name);
1167 if( !window )
1168 return;
1169
1170 if (!window->useGl)
1171 CV_Error( CV_OpenGlNotSupported, "Window was created without OpenGL context" );
1172
1173 window->glDrawCallback = callback;
1174 window->glDrawData = userdata;
1175}
1176
1177#endif // HAVE_OPENGL
1178
1179
1180
1181CvWindow::~CvWindow()
1182{
1183 if (frame)
1184 destroy();
1185}
1186
1187inline void CvWindow::destroy()
1188{
1189 CV_LOG_INFO(NULL, "OpenCV/UI: destroying GTK window: " << name);
1190 gtk_widget_destroy(widget: frame);
1191 frame = nullptr;
1192}
1193
1194static void checkLastWindow()
1195{
1196 // if last window...
1197 if (getGTKWindows().empty())
1198 {
1199#ifdef HAVE_GTHREAD
1200 if( thread_started )
1201 {
1202 // send key press signal to jump out of any waiting cvWaitKey's
1203 g_cond_broadcast( cond: cond_have_key );
1204 }
1205 else
1206 {
1207#endif
1208 // Some GTK+ modules (like the Unity module) use GDBusConnection,
1209 // which has a habit of postponing cleanup by performing it via
1210 // idle sources added to the main loop. Since this was the last window,
1211 // we can assume that no event processing is going to happen in the
1212 // nearest future, so we should force that cleanup (by handling all pending
1213 // events) while we still have the chance.
1214 // This is not needed if thread_started is true, because the background
1215 // thread will process events continuously.
1216 while( gtk_events_pending() )
1217 gtk_main_iteration();
1218#ifdef HAVE_GTHREAD
1219 }
1220#endif
1221 }
1222}
1223
1224static
1225void icvDeleteWindow_( CvWindow* window )
1226{
1227 AutoLock lock(getWindowMutex());
1228 auto& g_windows = getGTKWindows();
1229 bool found = false;
1230 for (auto i = g_windows.begin(); i != g_windows.end(); ++i)
1231 {
1232 if (i->get() == window)
1233 {
1234 g_windows.erase(position: i);
1235 found = true;
1236 break;
1237 }
1238 }
1239 CV_LOG_IF_WARNING(NULL, !found, "OpenCV/GTK: Can't destroy non-registered window");
1240 checkLastWindow();
1241}
1242
1243CV_IMPL void cvDestroyWindow( const char* name )
1244{
1245 CV_Assert(name && "NULL name string");
1246
1247 CV_LOCK_MUTEX();
1248 auto& g_windows = getGTKWindows();
1249
1250 bool found = false;
1251 for (auto i = g_windows.begin(); i != g_windows.end(); ++i)
1252 {
1253 if (i->get()->name == name)
1254 {
1255 g_windows.erase(position: i);
1256 found = true;
1257 break;
1258 }
1259 }
1260 CV_LOG_IF_ERROR(NULL, !found, "OpenCV/GTK: Can't destroy non-registered window: '" << name << "'");
1261
1262 checkLastWindow();
1263}
1264
1265
1266CV_IMPL void
1267cvDestroyAllWindows( void )
1268{
1269 CV_LOCK_MUTEX();
1270
1271 getGTKWindows().clear();
1272 checkLastWindow();
1273}
1274
1275// CvSize icvCalcOptimalWindowSize( CvWindow * window, CvSize new_image_size){
1276// CvSize window_size;
1277// GtkWidget * toplevel = gtk_widget_get_toplevel( window->frame );
1278// gdk_drawable_get_size( GDK_DRAWABLE(toplevel->window),
1279// &window_size.width, &window_size.height );
1280
1281// window_size.width = window_size.width + new_image_size.width - window->widget->allocation.width;
1282// window_size.height = window_size.height + new_image_size.height - window->widget->allocation.height;
1283
1284// return window_size;
1285// }
1286
1287CV_IMPL void
1288cvShowImage( const char* name, const CvArr* arr )
1289{
1290 CV_Assert(name && "NULL name string");
1291
1292 CV_LOCK_MUTEX();
1293
1294 auto window = icvFindWindowByName(name);
1295 if(!window)
1296 {
1297 cvNamedWindow(name, flags: 1);
1298 window = icvFindWindowByName(name);
1299 }
1300 CV_Assert(window);
1301
1302 if (arr)
1303 {
1304 #ifdef HAVE_OPENGL
1305 if (window->useGl)
1306 {
1307 cv::imshow(name, cv::cvarrToMat(arr));
1308 return;
1309 }
1310 #endif
1311
1312 CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget );
1313 cvImageWidgetSetImage( widget: image_widget, arr );
1314 }
1315}
1316
1317static void resizeWindow_(const std::shared_ptr<CvWindow>& window, int width, int height);
1318CV_IMPL void cvResizeWindow(const char* name, int width, int height )
1319{
1320 CV_Assert(name && "NULL name string");
1321
1322 CV_LOCK_MUTEX();
1323
1324 auto window = icvFindWindowByName(name);
1325 if(!window)
1326 return;
1327
1328 return resizeWindow_(window, width, height);
1329}
1330
1331static
1332void resizeWindow_(const std::shared_ptr<CvWindow>& window, int width, int height)
1333{
1334 CV_Assert(window);
1335 CvImageWidget* image_widget = CV_IMAGE_WIDGET( window->widget );
1336 //if(image_widget->flags & CV_WINDOW_AUTOSIZE)
1337 //EXIT;
1338
1339 gtk_window_set_resizable( GTK_WINDOW(window->frame), resizable: 1 );
1340 gtk_window_resize( GTK_WINDOW(window->frame), width, height );
1341
1342 // disable initial resize since presumably user wants to keep
1343 // this window size
1344 image_widget->flags &= ~CV_WINDOW_NO_IMAGE;
1345}
1346
1347
1348CV_IMPL void cvMoveWindow( const char* name, int x, int y )
1349{
1350 CV_Assert(name && "NULL name string");
1351
1352 CV_LOCK_MUTEX();
1353
1354 const auto window = icvFindWindowByName(name);
1355 if(!window)
1356 return;
1357
1358 gtk_window_move( GTK_WINDOW(window->frame), x, y );
1359}
1360
1361static
1362std::shared_ptr<CvTrackbar> icvFindTrackbarByName(const std::shared_ptr<CvWindow>& window, const std::string& name)
1363{
1364 CV_Assert(window);
1365 auto& trackbars = window->trackbars;
1366 for(size_t i = 0; i < trackbars.size(); ++i)
1367 {
1368 auto trackbar = trackbars[i];
1369 if (!trackbar)
1370 continue;
1371 if (trackbar->name == name)
1372 return trackbar;
1373 }
1374 return std::shared_ptr<CvTrackbar>();
1375}
1376
1377
1378static int
1379icvCreateTrackbar( const char* trackbar_name, const char* window_name,
1380 int* val, int count, CvTrackbarCallback on_notify,
1381 CvTrackbarCallback2 on_notify2, void* userdata )
1382{
1383 CV_Assert(window_name && "NULL window name");
1384 CV_Assert(trackbar_name && "NULL trackbar name");
1385
1386 if( count <= 0 )
1387 CV_Error( CV_StsOutOfRange, "Bad trackbar maximal value" );
1388
1389 CV_LOCK_MUTEX();
1390
1391 const auto window = icvFindWindowByName(name: window_name);
1392 if(!window)
1393 return 0;
1394
1395 auto trackbar_ = icvFindTrackbarByName(window, name: trackbar_name);
1396 if (!trackbar_)
1397 {
1398 trackbar_ = std::make_shared<CvTrackbar>(args&: trackbar_name);
1399 CvTrackbar* trackbar = trackbar_.get();
1400 trackbar->parent = window.get();
1401 window->trackbars.push_back(x: trackbar_);
1402
1403 GtkWidget* hscale_box = gtk_hbox_new( FALSE, spacing: 10 );
1404 GtkWidget* hscale_label = gtk_label_new( str: trackbar_name );
1405 GtkWidget* hscale = gtk_hscale_new_with_range( min: 0, max: count, step: 1 );
1406 gtk_scale_set_digits( GTK_SCALE(hscale), digits: 0 );
1407 //gtk_scale_set_value_pos( hscale, GTK_POS_TOP );
1408 gtk_scale_set_draw_value( GTK_SCALE(hscale), TRUE );
1409
1410 trackbar->widget = hscale;
1411 gtk_box_pack_start( GTK_BOX(hscale_box), child: hscale_label, FALSE, FALSE, padding: 5 );
1412 gtk_widget_show( widget: hscale_label );
1413 gtk_box_pack_start( GTK_BOX(hscale_box), child: hscale, TRUE, TRUE, padding: 5 );
1414 gtk_widget_show( widget: hscale );
1415 gtk_box_pack_start( GTK_BOX(window->paned), child: hscale_box, FALSE, FALSE, padding: 5 );
1416 gtk_widget_show( widget: hscale_box );
1417 }
1418
1419 CvTrackbar* trackbar = trackbar_.get(); CV_DbgAssert(trackbar);
1420
1421 if( val )
1422 {
1423 int value = *val;
1424 if( value < 0 )
1425 value = 0;
1426 if( value > count )
1427 value = count;
1428 gtk_range_set_value( GTK_RANGE(trackbar->widget), value );
1429 trackbar->pos = value;
1430 trackbar->data = val;
1431 }
1432
1433 trackbar->maxval = count;
1434 trackbar->notify = on_notify;
1435 trackbar->notify2 = on_notify2;
1436 trackbar->userdata = userdata;
1437 g_signal_connect( trackbar->widget, "value-changed",
1438 G_CALLBACK(icvOnTrackbar), trackbar );
1439
1440 // queue a widget resize to trigger a window resize to
1441 // compensate for the addition of trackbars
1442 gtk_widget_queue_resize( GTK_WIDGET(window->widget) );
1443
1444 return 1;
1445}
1446
1447CV_IMPL int
1448cvCreateTrackbar( const char* trackbar_name, const char* window_name,
1449 int* val, int count, CvTrackbarCallback on_notify )
1450{
1451 return icvCreateTrackbar(trackbar_name, window_name, val, count,
1452 on_notify, on_notify2: 0, userdata: 0);
1453}
1454
1455CV_IMPL int
1456cvCreateTrackbar2( const char* trackbar_name, const char* window_name,
1457 int* val, int count, CvTrackbarCallback2 on_notify2,
1458 void* userdata )
1459{
1460 return icvCreateTrackbar(trackbar_name, window_name, val, count,
1461 on_notify: 0, on_notify2, userdata);
1462}
1463
1464static
1465std::shared_ptr<CvTrackbar> createTrackbar_(
1466 const std::shared_ptr<CvWindow>& window, const std::string& name,
1467 int count,
1468 TrackbarCallback onChange, void* userdata
1469)
1470{
1471 CV_Assert(window);
1472 CV_Assert(!name.empty());
1473
1474 if (count <= 0)
1475 CV_Error(Error::StsOutOfRange, "Bad trackbar maximal value");
1476
1477 auto trackbar_ = std::make_shared<CvTrackbar>(args: name);
1478 CvTrackbar* trackbar = trackbar_.get();
1479 trackbar->parent = window.get();
1480 window->trackbars.push_back(x: trackbar_);
1481
1482 GtkWidget* hscale_box = gtk_hbox_new( FALSE, spacing: 10 );
1483 GtkWidget* hscale_label = gtk_label_new(str: name.c_str());
1484 GtkWidget* hscale = gtk_hscale_new_with_range( min: 0, max: count, step: 1 );
1485 gtk_scale_set_digits( GTK_SCALE(hscale), digits: 0 );
1486 //gtk_scale_set_value_pos( hscale, GTK_POS_TOP );
1487 gtk_scale_set_draw_value( GTK_SCALE(hscale), TRUE );
1488
1489 trackbar->widget = hscale;
1490 gtk_box_pack_start( GTK_BOX(hscale_box), child: hscale_label, FALSE, FALSE, padding: 5 );
1491 gtk_widget_show( widget: hscale_label );
1492 gtk_box_pack_start( GTK_BOX(hscale_box), child: hscale, TRUE, TRUE, padding: 5 );
1493 gtk_widget_show( widget: hscale );
1494 gtk_box_pack_start( GTK_BOX(window->paned), child: hscale_box, FALSE, FALSE, padding: 5 );
1495 gtk_widget_show( widget: hscale_box );
1496
1497 trackbar->maxval = count;
1498 trackbar->onChangeCallback = onChange;
1499 trackbar->userdata = userdata;
1500 g_signal_connect(trackbar->widget, "value-changed",
1501 G_CALLBACK(icvOnTrackbar), trackbar);
1502
1503 // queue a widget resize to trigger a window resize to
1504 // compensate for the addition of trackbars
1505 gtk_widget_queue_resize(GTK_WIDGET(window->widget));
1506
1507 return trackbar_;
1508}
1509
1510
1511CV_IMPL void
1512cvSetMouseCallback( const char* window_name, CvMouseCallback on_mouse, void* param )
1513{
1514 CV_Assert(window_name && "NULL window name");
1515
1516 CV_LOCK_MUTEX();
1517
1518 const auto window = icvFindWindowByName(name: window_name);
1519 if (!window)
1520 return;
1521
1522 window->on_mouse = on_mouse;
1523 window->on_mouse_param = param;
1524}
1525
1526
1527CV_IMPL int cvGetTrackbarPos( const char* trackbar_name, const char* window_name )
1528{
1529 CV_Assert(window_name && "NULL window name");
1530 CV_Assert(trackbar_name && "NULL trackbar name");
1531
1532 CV_LOCK_MUTEX();
1533
1534 const auto window = icvFindWindowByName(name: window_name);
1535 if (!window)
1536 return -1;
1537
1538 const auto trackbar = icvFindTrackbarByName(window,name: trackbar_name);
1539 if (!trackbar)
1540 return -1;
1541
1542 return trackbar->pos;
1543}
1544
1545static void setTrackbarPos_(const std::shared_ptr<CvTrackbar>& trackbar, int pos);
1546CV_IMPL void cvSetTrackbarPos( const char* trackbar_name, const char* window_name, int pos )
1547{
1548 CV_Assert(window_name && "NULL window name");
1549 CV_Assert(trackbar_name && "NULL trackbar name");
1550
1551 CV_LOCK_MUTEX();
1552
1553 const auto window = icvFindWindowByName(name: window_name);
1554 if(!window)
1555 return;
1556
1557 const auto trackbar = icvFindTrackbarByName(window, name: trackbar_name);
1558 if (!trackbar)
1559 {
1560 CV_Error( CV_StsNullPtr, "No trackbar found" );
1561 }
1562
1563 return setTrackbarPos_(trackbar, pos);
1564}
1565
1566static void setTrackbarPos_(const std::shared_ptr<CvTrackbar>& trackbar, int pos)
1567{
1568 CV_Assert(trackbar);
1569 CV_CheckLE(trackbar->minval, trackbar->maxval, "");
1570
1571 pos = std::max(a: pos, b: trackbar->minval);
1572 pos = std::min(a: pos, b: trackbar->maxval);
1573
1574 gtk_range_set_value( GTK_RANGE(trackbar->widget), value: pos );
1575}
1576
1577
1578CV_IMPL void cvSetTrackbarMax(const char* trackbar_name, const char* window_name, int maxval)
1579{
1580 CV_Assert(window_name && "NULL window name");
1581 CV_Assert(trackbar_name && "NULL trackbar name");
1582
1583 CV_LOCK_MUTEX();
1584
1585 const auto window = icvFindWindowByName(name: window_name);
1586 if(!window)
1587 return;
1588
1589 const auto trackbar = icvFindTrackbarByName(window,name: trackbar_name);
1590 if(!trackbar)
1591 return;
1592
1593 trackbar->maxval = maxval;
1594 if (trackbar->maxval >= trackbar->minval)
1595 gtk_range_set_range(GTK_RANGE(trackbar->widget), min: trackbar->minval, max: trackbar->maxval);
1596}
1597
1598
1599CV_IMPL void cvSetTrackbarMin(const char* trackbar_name, const char* window_name, int minval)
1600{
1601 CV_Assert(window_name && "NULL window name");
1602 CV_Assert(trackbar_name && "NULL trackbar name");
1603
1604 CV_LOCK_MUTEX();
1605
1606 const auto window = icvFindWindowByName(name: window_name);
1607 if(!window)
1608 return;
1609
1610 const auto trackbar = icvFindTrackbarByName(window,name: trackbar_name);
1611 if(!trackbar)
1612 return;
1613
1614 trackbar->minval = minval;
1615 if (trackbar->maxval >= trackbar->minval)
1616 gtk_range_set_range(GTK_RANGE(trackbar->widget), min: trackbar->minval, max: trackbar->maxval);
1617}
1618
1619
1620CV_IMPL void* cvGetWindowHandle( const char* window_name )
1621{
1622 CV_Assert(window_name && "NULL window name");
1623
1624 CV_LOCK_MUTEX();
1625
1626 const auto window = icvFindWindowByName(name: window_name);
1627 if(!window)
1628 return NULL;
1629
1630 return (void*)window->widget;
1631}
1632
1633
1634CV_IMPL const char* cvGetWindowName( void* window_handle )
1635{
1636 CV_Assert(window_handle && "NULL window handle");
1637
1638 CV_LOCK_MUTEX();
1639
1640 CvWindow* window = icvWindowByWidget( widget: (GtkWidget*)window_handle );
1641 if (window)
1642 return window->name.c_str();
1643
1644 return ""; // FIXME: NULL?
1645}
1646
1647static GtkFileFilter* icvMakeGtkFilter(const char* name, const char* patterns, GtkFileFilter* images)
1648{
1649 GtkFileFilter* filter = gtk_file_filter_new();
1650 gtk_file_filter_set_name(filter, name);
1651
1652 while(patterns[0])
1653 {
1654 gtk_file_filter_add_pattern(filter, pattern: patterns);
1655 gtk_file_filter_add_pattern(filter: images, pattern: patterns);
1656 patterns += strlen(s: patterns) + 1;
1657 }
1658
1659 return filter;
1660}
1661
1662static void icvShowSaveAsDialog(GtkWidget* widget, CvWindow* window)
1663{
1664 if (!window || !widget)
1665 return;
1666
1667 CvImageWidget* image_widget = CV_IMAGE_WIDGET(window->widget);
1668 if (!image_widget || !image_widget->original_image)
1669 return;
1670
1671 GtkWidget* dialog = gtk_file_chooser_dialog_new(title: "Save As...",
1672 GTK_WINDOW(widget),
1673 action: GTK_FILE_CHOOSER_ACTION_SAVE,
1674 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1675 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1676 NULL);
1677 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1678
1679 cv::String sname = gtk_window_get_title(GTK_WINDOW(window->frame));
1680 sname = sname.substr(pos: sname.find_last_of(s: "\\/") + 1) + ".png";
1681 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), name: sname.c_str());
1682
1683 GtkFileFilter* filter_all = gtk_file_filter_new();
1684 gtk_file_filter_set_name(filter: filter_all, name: "All Files");
1685 gtk_file_filter_add_pattern(filter: filter_all, pattern: "*");
1686
1687 GtkFileFilter* filter_images = gtk_file_filter_new();
1688 gtk_file_filter_set_name(filter: filter_images, name: "All Images");
1689
1690 GtkFileFilter* file_filters[] = {
1691 icvMakeGtkFilter(name: "Portable Network Graphics files (*.png)", patterns: "*.png\0", images: filter_images),
1692 icvMakeGtkFilter(name: "JPEG files (*.jpeg;*.jpg;*.jpe)", patterns: "*.jpeg\0*.jpg\0*.jpe\0", images: filter_images),
1693 icvMakeGtkFilter(name: "Windows bitmap (*.bmp;*.dib)", patterns: "*.bmp\0*.dib\0", images: filter_images),
1694 icvMakeGtkFilter(name: "TIFF Files (*.tiff;*.tif)", patterns: "*.tiff\0*.tif\0", images: filter_images),
1695 icvMakeGtkFilter(name: "JPEG-2000 files (*.jp2)", patterns: "*.jp2\0", images: filter_images),
1696 icvMakeGtkFilter(name: "WebP files (*.webp)", patterns: "*.webp\0", images: filter_images),
1697 icvMakeGtkFilter(name: "Portable image format (*.pbm;*.pgm;*.ppm;*.pxm;*.pnm)", patterns: "*.pbm\0*.pgm\0*.ppm\0*.pxm\0*.pnm\0", images: filter_images),
1698 icvMakeGtkFilter(name: "OpenEXR Image files (*.exr)", patterns: "*.exr\0", images: filter_images),
1699 icvMakeGtkFilter(name: "Radiance HDR (*.hdr;*.pic)", patterns: "*.hdr\0*.pic\0", images: filter_images),
1700 icvMakeGtkFilter(name: "Sun raster files (*.sr;*.ras)", patterns: "*.sr\0*.ras\0", images: filter_images),
1701 filter_images,
1702 filter_all
1703 };
1704
1705 for (size_t idx = 0; idx < sizeof(file_filters)/sizeof(file_filters[0]); ++idx)
1706 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter: file_filters[idx]); // filter ownership is transferred to dialog
1707 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter: filter_images);
1708
1709 cv::String filename;
1710 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1711 {
1712 char* fname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1713 filename = fname;
1714 g_free(mem: fname);
1715 }
1716 gtk_widget_destroy(widget: dialog);
1717
1718 if (!filename.empty())
1719 {
1720 cv::Mat bgr;
1721 cv::cvtColor(src: cv::cvarrToMat(arr: image_widget->original_image), dst: bgr, code: cv::COLOR_RGB2BGR);
1722 cv::imwrite(filename, img: bgr);
1723 }
1724}
1725
1726#if defined(GTK_VERSION2) && !defined (GDK_KEY_Escape)
1727#define GDK_KEY_Escape GDK_Escape
1728#define GDK_KEY_Return GDK_Return
1729#define GDK_KEY_Linefeed GDK_Linefeed
1730#define GDK_KEY_Tab GDK_Tab
1731#define GDK_KEY_s GDK_s
1732#define GDK_KEY_S GDK_S
1733#endif //GDK_KEY_Escape
1734
1735static gboolean icvOnKeyPress(GtkWidget* widget, GdkEventKey* event, gpointer user_data)
1736{
1737 int code = 0;
1738
1739 if ( BIT_ALLIN(event->state, GDK_CONTROL_MASK) && (event->keyval == GDK_KEY_s || event->keyval == GDK_KEY_S))
1740 {
1741 try
1742 {
1743 icvShowSaveAsDialog(widget, window: (CvWindow*)user_data);
1744 }
1745 catch(...)
1746 {
1747 // suppress all exceptions here
1748 }
1749 }
1750
1751 switch( event->keyval )
1752 {
1753 case GDK_KEY_Escape:
1754 code = 27;
1755 break;
1756 case GDK_KEY_Return:
1757 case GDK_KEY_Linefeed:
1758 code = 13;
1759 break;
1760 case GDK_KEY_Tab:
1761 code = '\t';
1762 break;
1763 default:
1764 code = event->keyval;
1765 }
1766
1767 code |= event->state << 16;
1768
1769#ifdef HAVE_GTHREAD
1770 if(thread_started)
1771 {
1772 g_mutex_lock(mutex: last_key_mutex);
1773 last_key = code;
1774 // signal any waiting threads
1775 g_cond_broadcast(cond: cond_have_key);
1776 g_mutex_unlock(mutex: last_key_mutex);
1777 }
1778 else
1779#endif
1780 {
1781 last_key = code;
1782 }
1783
1784 return FALSE;
1785}
1786
1787
1788static void icvOnTrackbar( GtkWidget* widget, gpointer user_data )
1789{
1790 int pos = cvRound( value: gtk_range_get_value(GTK_RANGE(widget)));
1791 CvTrackbar* trackbar = (CvTrackbar*)user_data;
1792
1793 if( trackbar && trackbar->signature == CV_TRACKBAR_MAGIC_VAL &&
1794 trackbar->widget == widget )
1795 {
1796 trackbar->pos = pos;
1797 if (trackbar->onChangeCallback)
1798 trackbar->onChangeCallback(pos, trackbar->userdata);
1799
1800 // deprecated
1801 if( trackbar->data )
1802 *trackbar->data = pos;
1803 if( trackbar->notify2 )
1804 trackbar->notify2(pos, trackbar->userdata);
1805 else if( trackbar->notify )
1806 trackbar->notify(pos);
1807 }
1808}
1809
1810static gboolean icvOnClose( GtkWidget* widget, GdkEvent* /*event*/, gpointer user_data )
1811{
1812 CvWindow* window = (CvWindow*)user_data;
1813 if( window->signature == CV_WINDOW_MAGIC_VAL &&
1814 window->frame == widget )
1815 {
1816 try
1817 {
1818 icvDeleteWindow_(window);
1819 }
1820 catch (...)
1821 {
1822 CV_LOG_WARNING(NULL, "OpenCV/GTK: unexpected C++ exception in icvDeleteWindow_");
1823 }
1824 }
1825 return TRUE;
1826}
1827
1828
1829static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_data )
1830{
1831 // TODO move this logic to CvImageWidget
1832 // TODO add try-catch wrappers into all callbacks
1833 CvWindow* window = (CvWindow*)user_data;
1834 if (!window || !widget ||
1835 window->signature != CV_WINDOW_MAGIC_VAL ||
1836 window->widget != widget ||
1837 !window->on_mouse)
1838 return FALSE;
1839
1840 CvPoint2D32f pt32f = {.x: -1., .y: -1.};
1841 CvPoint pt = {.x: -1,.y: -1};
1842 int cv_event = -1, state = 0, flags = 0;
1843 CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget );
1844
1845 if( event->type == GDK_MOTION_NOTIFY )
1846 {
1847 GdkEventMotion* event_motion = (GdkEventMotion*)event;
1848
1849 cv_event = CV_EVENT_MOUSEMOVE;
1850 pt32f.x = cvFloor(value: event_motion->x);
1851 pt32f.y = cvFloor(value: event_motion->y);
1852 state = event_motion->state;
1853 }
1854 else if( event->type == GDK_BUTTON_PRESS ||
1855 event->type == GDK_BUTTON_RELEASE ||
1856 event->type == GDK_2BUTTON_PRESS )
1857 {
1858 GdkEventButton* event_button = (GdkEventButton*)event;
1859 pt32f.x = cvFloor(value: event_button->x);
1860 pt32f.y = cvFloor(value: event_button->y);
1861
1862
1863 if( event_button->type == GDK_BUTTON_PRESS )
1864 {
1865 cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDOWN :
1866 event_button->button == 2 ? CV_EVENT_MBUTTONDOWN :
1867 event_button->button == 3 ? CV_EVENT_RBUTTONDOWN : 0;
1868 }
1869 else if( event_button->type == GDK_BUTTON_RELEASE )
1870 {
1871 cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONUP :
1872 event_button->button == 2 ? CV_EVENT_MBUTTONUP :
1873 event_button->button == 3 ? CV_EVENT_RBUTTONUP : 0;
1874 }
1875 else if( event_button->type == GDK_2BUTTON_PRESS )
1876 {
1877 cv_event = event_button->button == 1 ? CV_EVENT_LBUTTONDBLCLK :
1878 event_button->button == 2 ? CV_EVENT_MBUTTONDBLCLK :
1879 event_button->button == 3 ? CV_EVENT_RBUTTONDBLCLK : 0;
1880 }
1881 state = event_button->state;
1882 }
1883 else if( event->type == GDK_SCROLL )
1884 {
1885 GdkEventScroll* event_scroll = (GdkEventScroll*)event;
1886 pt32f.x = cvFloor(value: event_scroll->x);
1887 pt32f.y = cvFloor(value: event_scroll->y);
1888
1889#if defined(GTK_VERSION3_4)
1890 // NOTE: in current implementation doesn't possible to put into callback function delta_x and delta_y separately
1891 double delta = (event->scroll.delta_x + event->scroll.delta_y);
1892 cv_event = (event->scroll.delta_x==0) ? CV_EVENT_MOUSEWHEEL : CV_EVENT_MOUSEHWHEEL;
1893#else
1894 cv_event = CV_EVENT_MOUSEWHEEL;
1895#endif //GTK_VERSION3_4
1896
1897 state = event->scroll.state;
1898
1899 switch(event->scroll.direction) {
1900#if defined(GTK_VERSION3_4)
1901 case GDK_SCROLL_SMOOTH: flags |= (((int)delta << 16));
1902 break;
1903#endif //GTK_VERSION3_4
1904 case GDK_SCROLL_LEFT: cv_event = CV_EVENT_MOUSEHWHEEL;
1905 /* FALLTHRU */
1906 case GDK_SCROLL_UP: flags |= ~0xffff;
1907 break;
1908 case GDK_SCROLL_RIGHT: cv_event = CV_EVENT_MOUSEHWHEEL;
1909 /* FALLTHRU */
1910 case GDK_SCROLL_DOWN: flags |= (((int)1 << 16));
1911 break;
1912 default: ;
1913 };
1914 }
1915
1916 if( cv_event >= 0 )
1917 {
1918 // scale point if image is scaled
1919 if( (image_widget->flags & CV_WINDOW_AUTOSIZE)==0 &&
1920 image_widget->original_image &&
1921 image_widget->scaled_image )
1922 {
1923 // image origin is not necessarily at (0,0)
1924 int x0 = (gtk_widget_get_allocated_width(widget) - image_widget->scaled_image->cols)/2;
1925 int y0 = (gtk_widget_get_allocated_height(widget) - image_widget->scaled_image->rows)/2;
1926 pt.x = cvFloor( value: ((pt32f.x-x0)*image_widget->original_image->cols)/
1927 image_widget->scaled_image->cols );
1928 pt.y = cvFloor( value: ((pt32f.y-y0)*image_widget->original_image->rows)/
1929 image_widget->scaled_image->rows );
1930 }
1931 else
1932 {
1933 pt = cvPointFrom32f( point: pt32f );
1934 }
1935
1936 if (!image_widget->original_image/*OpenGL*/ || (
1937 (unsigned)pt.x < (unsigned)(image_widget->original_image->width) &&
1938 (unsigned)pt.y < (unsigned)(image_widget->original_image->height)
1939 ))
1940 {
1941 // handle non-keyboard (mouse) modifiers first
1942 flags |=
1943 BIT_MAP(state, GDK_BUTTON1_MASK, CV_EVENT_FLAG_LBUTTON) |
1944 BIT_MAP(state, GDK_BUTTON2_MASK, CV_EVENT_FLAG_MBUTTON) |
1945 BIT_MAP(state, GDK_BUTTON3_MASK, CV_EVENT_FLAG_RBUTTON);
1946 // keyboard modifiers
1947 state &= gtk_accelerator_get_default_mod_mask();
1948 flags |=
1949 BIT_MAP(state, GDK_SHIFT_MASK, CV_EVENT_FLAG_SHIFTKEY) |
1950 BIT_MAP(state, GDK_CONTROL_MASK, CV_EVENT_FLAG_CTRLKEY) |
1951 BIT_MAP(state, GDK_MOD1_MASK, CV_EVENT_FLAG_ALTKEY) |
1952 BIT_MAP(state, GDK_MOD2_MASK, CV_EVENT_FLAG_ALTKEY);
1953 window->on_mouse( cv_event, pt.x, pt.y, flags, window->on_mouse_param );
1954 }
1955 }
1956
1957 return FALSE;
1958}
1959
1960
1961static gboolean icvAlarm( gpointer user_data )
1962{
1963 *(int*)user_data = 1;
1964 return FALSE;
1965}
1966
1967
1968CV_IMPL int cvWaitKey( int delay )
1969{
1970#ifdef HAVE_GTHREAD
1971 if (thread_started && g_thread_self() != window_thread)
1972 {
1973 gboolean expired = true;
1974 int my_last_key;
1975
1976 g_mutex_lock(mutex: last_key_mutex);
1977 // wait for signal or timeout if delay > 0
1978 if(delay>0){
1979 GTimeVal timer;
1980 g_get_current_time(result: &timer);
1981 g_time_val_add(time_: &timer, microseconds: delay*1000);
1982 expired = !g_cond_timed_wait(cond: cond_have_key, mutex: last_key_mutex, timeval: &timer);
1983 }
1984 else{
1985 if (getGTKWindows().empty())
1986 {
1987 CV_LOG_WARNING(NULL, "cv::waitKey() is called without timeout and missing active windows. Ignoring");
1988 }
1989 else
1990 {
1991 g_cond_wait(cond: cond_have_key, mutex: last_key_mutex);
1992 expired=false;
1993 }
1994 }
1995 my_last_key = last_key;
1996 g_mutex_unlock(mutex: last_key_mutex);
1997 if (expired || getGTKWindows().empty())
1998 {
1999 return -1;
2000 }
2001 return my_last_key;
2002 }
2003 else
2004#endif
2005 {
2006 int expired = 0;
2007 guint timer = 0;
2008 if( delay > 0 )
2009 timer = g_timeout_add( interval: delay, function: icvAlarm, data: &expired );
2010 last_key = -1;
2011 while( gtk_main_iteration_do(TRUE) && last_key < 0 && !expired && (delay > 0 || !getGTKWindows().empty()))
2012 ;
2013
2014 if( delay > 0 && !expired )
2015 g_source_remove(tag: timer);
2016 }
2017 return last_key;
2018}
2019
2020namespace cv { namespace impl {
2021
2022using namespace cv::highgui_backend;
2023
2024class GTKTrackbar;
2025
2026class GTKWindow
2027 : public UIWindow
2028 , public std::enable_shared_from_this<GTKWindow>
2029{
2030protected:
2031 const std::string name_;
2032 std::weak_ptr<CvWindow> window_;
2033 std::map<std::string, std::shared_ptr<GTKTrackbar> > trackbars_;
2034public:
2035 GTKWindow(const std::string& name, const std::shared_ptr<CvWindow>& window)
2036 : name_(name)
2037 , window_(window)
2038 {
2039 // nothing
2040 }
2041
2042 ~GTKWindow() CV_OVERRIDE
2043 {
2044 if (!window_.expired())
2045 destroy();
2046 CV_LOG_DEBUG(NULL, "OpenCV/UI/GTK: GTKWindow(" << name_ << ") is disposed");
2047 }
2048
2049 const std::string& getID() const CV_OVERRIDE { return name_; }
2050
2051 bool isActive() const CV_OVERRIDE { return !window_.expired(); }
2052
2053 void destroy() CV_OVERRIDE
2054 {
2055 cv::AutoLock lock(getWindowMutex());
2056 if (!window_.expired())
2057 {
2058 auto window = window_.lock();
2059 if (window)
2060 window->destroy();
2061 window_.reset();
2062 }
2063 }
2064
2065 void imshow(InputArray image) CV_OVERRIDE
2066 {
2067 auto window = window_.lock();
2068 CV_Assert(window);
2069 CvImageWidget* image_widget = CV_IMAGE_WIDGET(window->widget);
2070 CV_Assert(image_widget);
2071 Mat img = image.getMat();
2072 CvMat c_img = cvMat(m: img); // TODO Drop C-API
2073 cvImageWidgetSetImage(widget: image_widget, arr: &c_img);
2074 }
2075
2076 double getProperty(int prop) const CV_OVERRIDE
2077 {
2078 auto window = window_.lock();
2079 CV_Assert(window);
2080 // see cvGetWindowProperty
2081 switch (prop)
2082 {
2083 case CV_WND_PROP_FULLSCREEN:
2084 return (double)window->status;
2085
2086 case CV_WND_PROP_AUTOSIZE:
2087 return (window->flags & CV_WINDOW_AUTOSIZE) ? 1.0 : 0.0;
2088
2089 case CV_WND_PROP_ASPECTRATIO:
2090 return getRatioWindow_(window);
2091
2092#ifdef HAVE_OPENGL
2093 case CV_WND_PROP_OPENGL:
2094 return window->useGl ? 1.0 : 0.0;
2095#endif
2096
2097 default:
2098 break;
2099 }
2100 return std::numeric_limits<double>::quiet_NaN();
2101 }
2102
2103 bool setProperty(int prop, double value) CV_OVERRIDE
2104 {
2105 auto window = window_.lock();
2106 CV_Assert(window);
2107 // see cvSetWindowProperty
2108 switch (prop)
2109 {
2110 case CV_WND_PROP_FULLSCREEN:
2111 if (value != CV_WINDOW_NORMAL && value != CV_WINDOW_FULLSCREEN) // bad arg
2112 break;
2113 setModeWindow_(window, mode: value);
2114 return true;
2115
2116 default:
2117 break;
2118 }
2119 return false;
2120 }
2121
2122 void resize(int width, int height) CV_OVERRIDE
2123 {
2124 auto window = window_.lock();
2125 CV_Assert(window);
2126 resizeWindow_(window, width, height);
2127 }
2128
2129 void move(int x, int y) CV_OVERRIDE
2130 {
2131 auto window = window_.lock();
2132 CV_Assert(window);
2133 gtk_window_move(GTK_WINDOW(window->frame), x, y);
2134 }
2135
2136 Rect getImageRect() const CV_OVERRIDE
2137 {
2138 auto window = window_.lock();
2139 CV_Assert(window);
2140 return getImageRect_(window);
2141 }
2142
2143 void setTitle(const std::string& title) CV_OVERRIDE
2144 {
2145 auto window = window_.lock();
2146 CV_Assert(window);
2147 gtk_window_set_title(GTK_WINDOW(window->frame), title: title.c_str());
2148 }
2149
2150 void setMouseCallback(MouseCallback onMouse, void* userdata /*= 0*/) CV_OVERRIDE
2151 {
2152 auto window = window_.lock();
2153 CV_Assert(window);
2154 window->on_mouse = onMouse;
2155 window->on_mouse_param = userdata;
2156 }
2157
2158 std::shared_ptr<UITrackbar> createTrackbar(
2159 const std::string& name,
2160 int count,
2161 TrackbarCallback onChange /*= 0*/,
2162 void* userdata /*= 0*/
2163 ) CV_OVERRIDE
2164 {
2165 auto window = window_.lock();
2166 CV_Assert(window);
2167 CV_LOG_INFO(NULL, "OpenCV/UI: Creating GTK trackbar at '" << name_ << "': '" << name << "'");
2168 auto trackbar = createTrackbar_(window, name, count, onChange, userdata);
2169 auto ui_trackbar = std::make_shared<GTKTrackbar>(args: name, args&: trackbar, args: shared_from_this());
2170 {
2171 cv::AutoLock lock(getWindowMutex());
2172 trackbars_.emplace(args: name, args&: ui_trackbar);
2173 }
2174 return std::static_pointer_cast<UITrackbar>(r: ui_trackbar);
2175 }
2176
2177 std::shared_ptr<UITrackbar> findTrackbar(const std::string& name) CV_OVERRIDE
2178 {
2179 cv::AutoLock lock(getWindowMutex());
2180 auto i = trackbars_.find(x: name);
2181 if (i != trackbars_.end())
2182 {
2183 return std::static_pointer_cast<UITrackbar>(r: i->second);
2184 }
2185 return std::shared_ptr<UITrackbar>();
2186 }
2187}; // GTKWindow
2188
2189
2190class GTKTrackbar : public UITrackbar
2191{
2192protected:
2193 /*const*/ std::string name_;
2194 std::weak_ptr<CvTrackbar> trackbar_;
2195 std::weak_ptr<GTKWindow> parent_;
2196 std::map<std::string, std::shared_ptr<GTKTrackbar> > trackbars_;
2197public:
2198 GTKTrackbar(const std::string& name, const std::shared_ptr<CvTrackbar>& trackbar, const std::shared_ptr<GTKWindow>& parent)
2199 : trackbar_(trackbar)
2200 , parent_(parent)
2201 {
2202 name_ = std::string("<") + name + ">@" + parent->getID();
2203 }
2204
2205 ~GTKTrackbar() CV_OVERRIDE
2206 {
2207 if (!trackbar_.expired())
2208 destroy();
2209 CV_LOG_DEBUG(NULL, "OpenCV/UI/GTK: GTKTrackbar(" << name_ << ") is disposed");
2210 }
2211
2212 const std::string& getID() const CV_OVERRIDE { return name_; }
2213
2214 bool isActive() const CV_OVERRIDE { return !trackbar_.expired(); }
2215
2216 void destroy() CV_OVERRIDE
2217 {
2218 // nothing (destroyed with parent window, dedicated trackbar removal is not supported)
2219 }
2220
2221 int getPos() const CV_OVERRIDE
2222 {
2223 auto trackbar = trackbar_.lock();
2224 CV_Assert(trackbar);
2225 return trackbar->pos;
2226 }
2227 void setPos(int pos) CV_OVERRIDE
2228 {
2229 auto trackbar = trackbar_.lock();
2230 CV_Assert(trackbar);
2231 return setTrackbarPos_(trackbar, pos);
2232 }
2233
2234 cv::Range getRange() const CV_OVERRIDE
2235 {
2236 auto trackbar = trackbar_.lock();
2237 CV_Assert(trackbar);
2238 return cv::Range(trackbar->minval, trackbar->maxval);
2239 }
2240
2241 void setRange(const cv::Range& range) CV_OVERRIDE
2242 {
2243 auto trackbar = trackbar_.lock();
2244 CV_Assert(trackbar);
2245 CV_CheckLE(range.start, range.end, "Invalid trackbar range");
2246 gtk_range_set_range(GTK_RANGE(trackbar->widget), min: range.start, max: range.end);
2247 }
2248}; // GTKTrackbar
2249
2250
2251class GTKBackendUI : public UIBackend
2252{
2253public:
2254 GTKBackendUI()
2255 {
2256 // NB: avoid static initialization order fiasco
2257 (void)getGTKWindows();
2258 }
2259 ~GTKBackendUI() CV_OVERRIDE
2260 {
2261 destroyAllWindows();
2262 }
2263
2264 void destroyAllWindows() CV_OVERRIDE
2265 {
2266 cvDestroyAllWindows();
2267 }
2268
2269 // namedWindow
2270 virtual std::shared_ptr<UIWindow> createWindow(
2271 const std::string& winname,
2272 int flags
2273 ) CV_OVERRIDE
2274 {
2275 CV_LOG_INFO(NULL, "OpenCV/UI: Creating GTK window: " << winname << " (" << flags << ")");
2276 auto window = namedWindow_(name: winname, flags);
2277 auto ui_window = std::make_shared<GTKWindow>(args: winname, args&: window);
2278 return ui_window;
2279 }
2280
2281 int waitKeyEx(int delay) CV_OVERRIDE
2282 {
2283 return cvWaitKey(delay);
2284 }
2285 int pollKey() CV_OVERRIDE
2286 {
2287 return cvWaitKey(delay: 1); // TODO
2288 }
2289}; // GTKBackendUI
2290
2291static
2292std::shared_ptr<GTKBackendUI>& getInstance()
2293{
2294 static std::shared_ptr<GTKBackendUI> g_instance = std::make_shared<GTKBackendUI>();
2295 return g_instance;
2296}
2297
2298} // namespace impl
2299
2300#ifndef BUILD_PLUGIN
2301namespace highgui_backend {
2302
2303std::shared_ptr<UIBackend> createUIBackendGTK()
2304{
2305 return impl::getInstance();
2306}
2307
2308} // namespace highgui_backend
2309#endif
2310
2311} // namespace
2312
2313#ifdef BUILD_PLUGIN
2314
2315#define ABI_VERSION 0
2316#define API_VERSION 0
2317#include "plugin_api.hpp"
2318
2319static
2320CvResult cv_getInstance(CV_OUT CvPluginUIBackend* handle) CV_NOEXCEPT
2321{
2322 try
2323 {
2324 if (!handle)
2325 return CV_ERROR_FAIL;
2326 *handle = cv::impl::getInstance().get();
2327 return CV_ERROR_OK;
2328 }
2329 catch (...)
2330 {
2331 return CV_ERROR_FAIL;
2332 }
2333}
2334
2335static const OpenCV_UI_Plugin_API plugin_api =
2336{
2337 {
2338 sizeof(OpenCV_UI_Plugin_API), ABI_VERSION, API_VERSION,
2339 CV_VERSION_MAJOR, CV_VERSION_MINOR, CV_VERSION_REVISION, CV_VERSION_STATUS,
2340 "GTK" CVAUX_STR(GTK_MAJOR_VERSION) " OpenCV UI plugin"
2341 },
2342 {
2343 /* 1*/cv_getInstance
2344 }
2345};
2346
2347const 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
2348{
2349 if (requested_abi_version == ABI_VERSION && requested_api_version <= API_VERSION)
2350 return &plugin_api;
2351 return NULL;
2352}
2353
2354#endif // BUILD_PLUGIN
2355
2356#endif // HAVE_GTK
2357

source code of opencv/modules/highgui/src/window_gtk.cpp