Fawkes API  Fawkes Development Version
image_widget.cpp
1 /***************************************************************************
2  * image_widget.cpp - Gtkmm widget to draw an image inside a Gtk::Window
3  *
4  * Created: 26.11.2008
5  * Copyright 2008 Christof Rath <christof.rath@gmail.com>
6  *
7  ****************************************************************************/
8 
9 /* This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Library General Public License for more details.
18  *
19  * Read the full text in the LICENSE.GPL file in the doc directory.
20  */
21 
22 
23 #include "image_widget.h"
24 
25 #include <core/exceptions/software.h>
26 #include <core/threading/mutex.h>
27 #include <fvutils/color/conversions.h>
28 #include <fvutils/color/yuv.h>
29 #include <fvutils/scalers/lossy.h>
30 #include <fvcams/camera.h>
31 
32 #include <iomanip>
33 
34 
35 namespace firevision {
36 #if 0 /* just to make Emacs auto-indent happy */
37 }
38 #endif
39 
40 /** @class ImageWidget <fvwidgets/image_widget.h>
41  * This class is an image container to display fawkes cameras (or image
42  * buffers) inside a Gtk::Container
43  *
44  * @author Christof Rath
45  */
46 
47 /**
48  * Creates a new ImageWidget with predefined width and height
49  * @param width of the widget
50  * @param height of the widget
51  */
52 ImageWidget::ImageWidget(unsigned int width, unsigned int height)
53 {
54  __cam = NULL;
55  __cam_enabled = false;
56  __cam_mutex = new fawkes::Mutex;
57  __refresh_thread = NULL;
58 
59  set_size(width, height);
60 }
61 
62 /**
63  * Creates a new ImageWidget with a Camera as image source
64  * @param cam the image source
65  * @param refresh_delay if greater 0 a thread gets created that refreshes
66  * the Image every refresh_delay milliseconds
67  * @param width of the widget (if not equal to the camera width the image
68  * gets scaled)
69  * @param height of the widget (if not equal to the camera height the
70  * image gets scaled)
71  */
72 ImageWidget::ImageWidget(Camera *cam, unsigned int refresh_delay, unsigned int width, unsigned int height)
73 {
74  if (!cam) throw fawkes::NullPointerException("Parameter cam may not be NULL");
75 
76  __cam = cam;
77  __cam_enabled = true;
78  __cam_mutex = new fawkes::Mutex;
79  __cam_has_buffer = false;
80 
81  set_size(width, height);
82 
83  try {
84  fawkes::Time *time = __cam->capture_time();
85  delete time;
86  __cam_has_timestamp = true;
87  }
88  catch (fawkes::Exception &e) {
89  __cam_has_timestamp = false;
90  }
91 
92  __refresh_thread = new RefThread(this, refresh_delay);
93  __refresh_thread->start();
94  __refresh_thread->refresh_cam();
95 }
96 
97 /** Constructor for Gtk::Builder.
98  * Constructor that can be used to instantiate an ImageWidget as a
99  * derived widget from a Gtk builder file.
100  *
101  * Note: The ImageWidget (and its internal buffer) is set to the size
102  * as in the UI file, in case no camera is set afterwards. Use @see
103  * ImageWidget::set_size() to resize the ImageWidget afterwards.
104  *
105  * @param cobject pointer to the base object
106  * @param builder Builder
107  */
108 ImageWidget::ImageWidget(BaseObjectType* cobject, Glib::RefPtr<Gtk::Builder> builder)
109  : Gtk::Image(cobject)
110 {
111  __cam = NULL;
112  __cam_enabled = false;
113  __cam_mutex = new fawkes::Mutex;
114  __refresh_thread = NULL;
115 // set_size(Gtk::Image::get_width(), Gtk::Image::get_height());
116 }
117 
118 #ifdef HAVE_GLADEMM
119 /** Constructor for Glade.
120  * Constructor that can be used to instantiate an ImageWidget as a
121  * derived widget from a Glade file.
122  *
123  * Note: The ImageWidget (and its internal buffer) is set to the size
124  * as in the glade file, in case no camera is set afterwards. Use @see
125  * ImageWidget::set_size() to resize the ImageWidget afterwards.
126  *
127  * @param cobject pointer to the base object
128  * @param refxml the Glade XML file
129  */
130 ImageWidget::ImageWidget(BaseObjectType* cobject, Glib::RefPtr<Gnome::Glade::Xml> refxml)
131  : Gtk::Image( cobject )
132 {
133  __cam = NULL;
134  __cam_enabled = false;
135  __cam_mutex = new fawkes::Mutex;
136  __refresh_thread = NULL;
137 
138 // set_size(Gtk::Image::get_width(), Gtk::Image::get_height());
139 }
140 #endif
141 
142 /**
143  * Destructor
144  */
146 {
147  if (__refresh_thread) __refresh_thread->stop();
148  delete __cam_mutex;
149 }
150 
151 /** Set the camera from which the ImageWidget obtains the images.
152  *
153  * Note: The size of the ImageWidget remains untouched and the cameras
154  * image gets scaled appropriately. Use ImageWidget::set_size(0, 0) to
155  * set the widget to the size of the camera.
156  *
157  * @param cam the camera
158  * @param refresh_delay the delay between two refreshs in milliseconds
159  */
160 void
161 ImageWidget::set_camera(Camera *cam, unsigned int refresh_delay)
162 {
163  __cam = cam;
164  __cam_enabled = true;
165  __cam_has_buffer = false;
166 
167  set_size(__cam->pixel_width(), __cam->pixel_height());
168 
169  try {
170  fawkes::Time *time = __cam->capture_time();
171  delete time;
172  __cam_has_timestamp = true;
173  }
174  catch (fawkes::Exception &e) {
175  __cam_has_timestamp = false;
176  }
177 
178  if ( __refresh_thread ) {
179  __refresh_thread->set_delay(refresh_delay);
180  } else {
181  __refresh_thread = new RefThread(this, refresh_delay);
182  __refresh_thread->start();
183  }
184 
185  __refresh_thread->refresh_cam();
186 }
187 
188 /**
189  * En-/disable the camera.
190  * @param enable if true the camera is enabled and the refresh thread
191  * is start, if false the refresh thread is stopped and the camera is
192  * disabled
193  */
194 void
196 {
197  if ( !enable && __cam_enabled ) {
198  __refresh_thread->stop();
199  } else if ( __refresh_thread && enable && !__cam_enabled ) {
200  __refresh_thread->start();
201  }
202 
203  __cam_enabled = enable;
204 }
205 
206 /** Sets the size of the ImageWidget.
207  * Updates the internal buffer and the size request for the ImageWidget.
208  * If width and/or height are set to 0 (and a Camera is set) the
209  * ImageWidget will be set to the camera dimensions.
210  *
211  * Note: The ImageWidget must be refreshed after changing its size!
212  *
213  * @param width The new width
214  * @param height The new height
215  */
216 void
217 ImageWidget::set_size(unsigned int width, unsigned int height)
218 {
219  if (!width || ! height) {
220  if (__cam) {
221  width = __cam->pixel_width();
222  height = __cam->pixel_height();
223  }
224  else {
225  throw fawkes::IllegalArgumentException("ImageWidget::set_size(): width and/or height may not be 0 if no Camera is set");
226  }
227  }
228 
229  if (!__pixbuf || __width != width || __height != height) {
230  __width = width;
231  __height = height;
232 
233 #if GLIBMM_MAJOR_VERSION > 2 || ( GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION >= 14 )
234  __pixbuf.reset();
235 #else
236  __pixbuf.clear();
237 #endif
238 
239  __pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, __width, __height);
240 
241  set_size_request(__width, __height);
242  }
243 }
244 /**
245  * Returns the image buffer width
246  * @return width of the contained image
247  */
248 unsigned int
250 {
251  return __width;
252 }
253 
254 /**
255  * Returns the image buffer height
256  * @return height of the contained image
257  */
258 unsigned int
260 {
261  return __height;
262 }
263 
264 /**
265  * Returns the widgets pixel buffer (RGB!)
266  * @return the RGB pixel buffer
267  */
268 Glib::RefPtr<Gdk::Pixbuf>
270 {
271  return __pixbuf;
272 }
273 
274 /**
275  * Sets a pixel to the given RGB colors
276  *
277  * @param x position of the pixel
278  * @param y position of the pixel
279  * @param r component of the color
280  * @param g component of the color
281  * @param b component of the color
282  */
283 void
284 ImageWidget::set_rgb(unsigned int x, unsigned int y, unsigned char r, unsigned char g, unsigned char b)
285 {
286  set_rgb (x, y, (RGB_t){r, g, b});
287 }
288 
289 /**
290  * Sets a pixel to the given RGB colors
291  *
292  * @param x position of the pixel
293  * @param y position of the pixel
294  * @param rgb the color
295  */
296 void
297 ImageWidget::set_rgb(unsigned int x, unsigned int y, RGB_t rgb)
298 {
299  if (x >= __width) throw fawkes::OutOfBoundsException("x-Coordinate exeeds image width", x, 0, __width);
300  if (y >= __height) throw fawkes::OutOfBoundsException("y-Coordinate exeeds image height", x, 0, __height);
301 
302  RGB_t * target = RGB_PIXEL_AT(__pixbuf->get_pixels(), __width, x, y);
303  *target = rgb;
304 }
305 
306 /**
307  * Show image from given colorspace.
308  * Warning: If width and/or height not set, it is assumed, that the given
309  * buffer has the same dimension as the widget.
310  *
311  * @param colorspace colorspace of the supplied buffer
312  * @param buffer image buffer
313  * @param width Width of the provided buffer (may be scaled to ImageWidget
314  * dimensions)
315  * @param height Height of the provided buffer (may be scaled to
316  * ImageWidget dimensions)
317  * @return TRUE if the buffer chould have been shown
318  */
319 bool
320 ImageWidget::show(colorspace_t colorspace, unsigned char *buffer, unsigned int width, unsigned int height)
321 {
322  try {
323  if (!width || !height || (width == __width && height == __height)) {
324  convert(colorspace, RGB, buffer, __pixbuf->get_pixels(), __width, __height);
325  }
326  else {
327  unsigned char *scaled_buffer = (unsigned char *)malloc(colorspace_buffer_size(colorspace, __width, __height));
328 
329  if (scaled_buffer) {
330  LossyScaler scaler;
331  scaler.set_original_buffer(buffer);
332  scaler.set_original_dimensions(width, height);
333  scaler.set_scaled_buffer(scaled_buffer);
334  scaler.set_scaled_dimensions(__width, __height);
335  scaler.scale();
336 
337  convert(colorspace, RGB, scaled_buffer, __pixbuf->get_pixels(), __width, __height);
338 
339  free(scaled_buffer);
340  }
341  }
342  }
343  catch (fawkes::Exception &e) {
344  printf("ImageWidget::show(): %s\n", e.what());
345  return false;
346  }
347 
348  try {
349  set(__pixbuf);
350  __signal_show.emit(colorspace, buffer, width, height);
351  return true;
352  }
353  catch (fawkes::Exception &e) {
354  printf("ImageWidget::show(): Could not set the new image (%s)\n", e.what());
355  }
356 
357  return false;
358 }
359 
360 
361 /** Signal emits after a new buffer gets successfully shown
362  * (see @see ImageWidget::show()).
363  *
364  * The buffer's validity can not be guaranteed beyond the called functions
365  * scope! In case the source of the widget is a Camera, the buffer gets
366  * disposed after calling ImageWidget::show.
367  *
368  * @return The signal_show signal
369  */
370 sigc::signal<void, colorspace_t, unsigned char *, unsigned int, unsigned int> &
372 {
373  return __signal_show;
374 }
375 
376 
377 /**
378  * Sets the refresh delay for automatic camera refreshes
379  *
380  * @param refresh_delay im [ms]
381  */
382 void
383 ImageWidget::set_refresh_delay(unsigned int refresh_delay)
384 {
385  __refresh_thread->set_delay(refresh_delay);
386 }
387 
388 
389 /**
390  * Performs a refresh during the next loop of the refresh thread
391  */
392 void
394 {
395  if ( __cam_enabled ) {
396  __refresh_thread->refresh_cam();
397  }
398 }
399 
400 /**
401  * Sets the widgets pixbuf after (i.e. non blocking) retrieving the image
402  * over the network.
403  */
404 void
405 ImageWidget::set_cam()
406 {
407  if ( !__cam_enabled ) { return; }
408 
409  __cam_mutex->lock();
410 
411  if (__cam_has_buffer) {
412  show(__cam->colorspace(), __cam->buffer(), __cam->pixel_width(), __cam->pixel_height());
413  __cam->flush();
414  __cam_has_buffer = false;
415  }
416 
417  __cam_mutex->unlock();
418 }
419 
420 /**
421  * Saves the current content of the Image
422  * @param filename of the output
423  * @param type of the output (By default, "jpeg", "png", "ico" and "bmp"
424  * are possible file formats to save in, but more formats may be
425  * installed. The list of all writable formats can be determined
426  * by using Gdk::Pixbuf::get_formats() with
427  * Gdk::PixbufFormat::is_writable().)
428  * @return true on success, false otherwise
429  */
430 bool
431 ImageWidget::save_image(std::string filename, Glib::ustring type) const throw()
432 {
433  __cam_mutex->lock();
434 
435  try {
436  __pixbuf->save(filename, type);
437  __cam_mutex->unlock();
438  return true;
439  }
440  catch (Glib::Exception &e) {
441  __cam_mutex->unlock();
442  printf("save failed: %s\n", e.what().c_str());
443  return false;
444  }
445 }
446 
447 /**
448  * Saves the content of the image on every refresh
449  *
450  * @param enable enables or disables the feature
451  * @param path to save the images at
452  * @param type file type (@see ImageWidget::save_image)
453  * @param img_num of which to start the numbering (actually the first
454  * image is numbered img_num + 1)
455  */
456 void
457 ImageWidget::save_on_refresh_cam(bool enable, std::string path, Glib::ustring type, unsigned int img_num)
458 {
459  __refresh_thread->save_on_refresh(enable, path, type, img_num);
460 }
461 
462 /**
463  * Returns the latest image number
464  * @return the latest image number
465  */
466 unsigned int
468 {
469  return __refresh_thread->get_img_num();
470 }
471 
472 /**
473  * Creates a new refresh thread
474  *
475  * @param widget to be refreshed
476  * @param refresh_delay time between two refreshes (in [ms])
477  */
478 ImageWidget::RefThread::RefThread(ImageWidget *widget, unsigned int refresh_delay)
479 : Thread("ImageWidget refresh thread")
480 {
481  set_delete_on_exit(true);
482 
483  __widget = widget;
484  __stop = false;
485  __do_refresh = false;
486 
487  __save_imgs = false;
488  __save_num = 0;
489 
490  __dispatcher.connect( sigc::mem_fun( *widget , &ImageWidget::set_cam ) );
491 
492  set_delay(refresh_delay);
493 }
494 
495 /**
496  * Sets the refresh delay for automatic camera refreshes
497  *
498  * @param refresh_delay im [ms]
499  */
500 void
501 ImageWidget::RefThread::set_delay(unsigned int refresh_delay)
502 {
503  __refresh_delay = refresh_delay;
504  __loop_cnt = 0;
505 }
506 
507 /**
508  * Refreshes the camera during the next loop
509  */
510 void
511 ImageWidget::RefThread::refresh_cam()
512 {
513  __do_refresh = true;
514 }
515 
516 /**
517  * Refreshes the Image (getting a new frame from the camera)
518  */
519 void
520 ImageWidget::RefThread::perform_refresh()
521 {
522  if (!__widget->__cam) {
523  throw fawkes::NullPointerException("Camera hasn't been given during creation");
524  }
525 
526  try {
527  if (__widget->__cam_mutex->try_lock()) {
528  __widget->__cam->dispose_buffer();
529  __widget->__cam->capture();
530  if (!__stop) {
531  __widget->__cam_has_buffer = true;
532  __widget->__cam_mutex->unlock();
533 
534  if (__widget->__cam->ready()) {
535  __dispatcher();
536 
537  if (__save_imgs) {
538  char *ctmp;
539  if (__widget->__cam_has_timestamp) {
540  try {
541  fawkes::Time *ts = __widget->__cam->capture_time();
542  if (asprintf(&ctmp, "%s/%06u.%ld.%s", __save_path.c_str(), ++__save_num, ts->in_msec(), __save_type.c_str()) != -1) {
543  Glib::ustring fn = ctmp;
544  __widget->save_image(fn, __save_type);
545  free(ctmp);
546  } else {
547  printf("Cannot save image, asprintf() ran out of memory\n");
548  }
549  delete ts;
550  }
551  catch (fawkes::Exception &e) {
552  printf("Cannot save image (%s)\n", e.what());
553  }
554  }
555  else {
556  if (asprintf(&ctmp, "%s/%06u.%s", __save_path.c_str(), ++__save_num, __save_type.c_str()) != -1) {
557  Glib::ustring fn = ctmp;
558  __widget->save_image(fn, __save_type);
559  free(ctmp);
560  } else {
561  printf("Cannot save image, asprintf() ran out of memory\n");
562  }
563  }
564  }
565  }
566  }
567  }
568  }
569  catch (fawkes::Exception &e) {
570  printf("Could not capture the image (%s)\n", e.what());
571  }
572 }
573 
574 
575 void
576 ImageWidget::RefThread::loop()
577 {
578  if (!__stop) {
579  ++__loop_cnt;
580 
581  if (__refresh_delay && !(__loop_cnt % __refresh_delay)) {
582  perform_refresh();
583  __do_refresh = false;
584  __loop_cnt = 0;
585  }
586 
587  if (__do_refresh) {
588  perform_refresh();
589  __do_refresh = false;
590  __loop_cnt = 0;
591  }
592  }
593  else exit();
594 
595  Glib::usleep(1000);
596 }
597 
598 /**
599  * Stops (and destroys) the thread as soon as possible (at the next loop)
600  */
601 void
602 ImageWidget::RefThread::stop()
603 {
604  __stop = true;
605 }
606 
607 
608 /** Set save on refresh.
609  * @param enabled true to enable, false to disable
610  * @param path save path
611  * @param type save type
612  * @param img_num image number to save
613  */
614 void
615 ImageWidget::RefThread::save_on_refresh(bool enabled, std::string path, Glib::ustring type, unsigned int img_num)
616 {
617  __save_imgs = enabled;
618 
619  if (__save_imgs) {
620  __save_path = path;
621  __save_type = type;
622  __save_num = img_num;
623  }
624 }
625 
626 /** Get image number.
627  * @return image number
628  */
629 unsigned int
630 ImageWidget::RefThread::get_img_num()
631 {
632  return __save_num;
633 }
634 
635 } // end namespace firevision
sigc::signal< void, colorspace_t, unsigned char *, unsigned int, unsigned int > & signal_show()
Signal emits after a new buffer gets successfully shown (see.
ImageWidget(unsigned int width, unsigned int height)
Creates a new ImageWidget with predefined width and height.
This class is an image container to display fawkes cameras (or image buffers) inside a Gtk::Container...
Definition: image_widget.h:45
Structure defining an RGB pixel (in R-G-B byte ordering).
Definition: rgb.h:44
virtual void set_scaled_buffer(unsigned char *buffer)
Set scaled image buffer.
Definition: lossy.cpp:126
virtual void set_original_buffer(unsigned char *buffer)
Set original image buffer.
Definition: lossy.cpp:119
Camera interface for image aquiring devices in FireVision.
Definition: camera.h:35
void set_rgb(unsigned int x, unsigned int y, unsigned char r, unsigned char g, unsigned char b)
Sets a pixel to the given RGB colors.
void unlock()
Unlock the mutex.
Definition: mutex.cpp:135
virtual unsigned int pixel_width()=0
Width of image in pixels.
Glib::RefPtr< Gdk::Pixbuf > get_buffer() const
Returns the widgets pixel buffer (RGB!)
A class for handling time.
Definition: time.h:91
void set_camera(Camera *cam, unsigned int refresh_delay=0)
Set the camera from which the ImageWidget obtains the images.
virtual const char * what() const
Get primary string.
Definition: exception.cpp:661
A NULL pointer was supplied where not allowed.
Definition: software.h:34
virtual colorspace_t colorspace()=0
Colorspace of returned image.
void set_refresh_delay(unsigned int refresh_delay)
Sets the refresh delay for automatic camera refreshes.
void refresh_cam()
Performs a refresh during the next loop of the refresh thread.
long in_msec() const
Convert the stored time into milli-seconds.
Definition: time.cpp:242
Lossy image scaler.
Definition: lossy.h:35
Base class for exceptions in Fawkes.
Definition: exception.h:36
unsigned int get_width() const
Returns the image buffer width.
virtual fawkes::Time * capture_time()
Get the Time of the last successfully captured image.
Definition: camera.cpp:141
virtual void flush()=0
Flush image queue.
void set_size(unsigned int width, unsigned int height)
Sets the size of the ImageWidget.
bool save_image(std::string filename, Glib::ustring type) const
Saves the current content of the Image.
virtual void set_original_dimensions(unsigned int width, unsigned int height)
Set original image dimensions.
Definition: lossy.cpp:83
void enable_camera(bool enable)
En-/disable the camera.
virtual void set_scaled_dimensions(unsigned int width, unsigned int height)
Set dimenins of scaled image buffer.
Definition: lossy.cpp:92
virtual bool show(colorspace_t colorspace, unsigned char *buffer, unsigned int width=0, unsigned int height=0)
Show image from given colorspace.
void save_on_refresh_cam(bool enabled, std::string path="", Glib::ustring type="", unsigned int img_num=0)
Saves the content of the image on every refresh.
virtual unsigned char * buffer()=0
Get access to current image buffer.
unsigned int get_image_num()
Returns the latest image number.
virtual unsigned int pixel_height()=0
Height of image in pixels.
void lock()
Lock this mutex.
Definition: mutex.cpp:89
Mutex mutual exclusion lock.
Definition: mutex.h:32
Index out of bounds.
Definition: software.h:88
virtual void scale()
Scale image.
Definition: lossy.cpp:153
Expected parameter is missing.
Definition: software.h:82
virtual ~ImageWidget()
Destructor.
unsigned int get_height() const
Returns the image buffer height.