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