Fawkes API  Fawkes Development Version
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
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 #ifdef GLIBMM_EXCEPTIONS_ENABLED
437  __pixbuf->save(filename, type);
438 #else
439  std::auto_ptr<Glib::Error> error;
440  __pixbuf->save(filename, type, error);
441 #endif
442  __cam_mutex->unlock();
443  return true;
444  }
445  catch (Glib::Exception &e) {
446  __cam_mutex->unlock();
447  printf("save failed: %s\n", e.what().c_str());
448  return false;
449  }
450 }
451 
452 /**
453  * Saves the content of the image on every refresh
454  *
455  * @param enable enables or disables the feature
456  * @param path to save the images at
457  * @param type file type (@see ImageWidget::save_image)
458  * @param img_num of which to start the numbering (actually the first
459  * image is numbered img_num + 1)
460  */
461 void
462 ImageWidget::save_on_refresh_cam(bool enable, std::string path, Glib::ustring type, unsigned int img_num)
463 {
464  __refresh_thread->save_on_refresh(enable, path, type, img_num);
465 }
466 
467 /**
468  * Returns the latest image number
469  * @return the latest image number
470  */
471 unsigned int
473 {
474  return __refresh_thread->get_img_num();
475 }
476 
477 /**
478  * Creates a new refresh thread
479  *
480  * @param widget to be refreshed
481  * @param refresh_delay time between two refreshes (in [ms])
482  */
483 ImageWidget::RefThread::RefThread(ImageWidget *widget, unsigned int refresh_delay)
484 : Thread("ImageWidget refresh thread")
485 {
486  set_delete_on_exit(true);
487 
488  __widget = widget;
489  __stop = false;
490  __do_refresh = false;
491 
492  __save_imgs = false;
493  __save_num = 0;
494 
495  __dispatcher.connect( sigc::mem_fun( *widget , &ImageWidget::set_cam ) );
496 
497  set_delay(refresh_delay);
498 }
499 
500 /**
501  * Sets the refresh delay for automatic camera refreshes
502  *
503  * @param refresh_delay im [ms]
504  */
505 void
506 ImageWidget::RefThread::set_delay(unsigned int refresh_delay)
507 {
508  __refresh_delay = refresh_delay;
509  __loop_cnt = 0;
510 }
511 
512 /**
513  * Refreshes the camera during the next loop
514  */
515 void
516 ImageWidget::RefThread::refresh_cam()
517 {
518  __do_refresh = true;
519 }
520 
521 /**
522  * Refreshes the Image (getting a new frame from the camera)
523  */
524 void
525 ImageWidget::RefThread::perform_refresh()
526 {
527  if (!__widget->__cam) {
528  throw fawkes::NullPointerException("Camera hasn't been given during creation");
529  }
530 
531  try {
532  if (__widget->__cam_mutex->try_lock()) {
533  __widget->__cam->dispose_buffer();
534  __widget->__cam->capture();
535  if (!__stop) {
536  __widget->__cam_has_buffer = true;
537  __widget->__cam_mutex->unlock();
538 
539  if (__widget->__cam->ready()) {
540  __dispatcher();
541 
542  if (__save_imgs) {
543  char *ctmp;
544  if (__widget->__cam_has_timestamp) {
545  try {
546  fawkes::Time *ts = __widget->__cam->capture_time();
547  if (asprintf(&ctmp, "%s/%06u.%ld.%s", __save_path.c_str(), ++__save_num, ts->in_msec(), __save_type.c_str()) != -1) {
548  Glib::ustring fn = ctmp;
549  __widget->save_image(fn, __save_type);
550  free(ctmp);
551  } else {
552  printf("Cannot save image, asprintf() ran out of memory\n");
553  }
554  delete ts;
555  }
556  catch (fawkes::Exception &e) {
557  printf("Cannot save image (%s)\n", e.what());
558  }
559  }
560  else {
561  if (asprintf(&ctmp, "%s/%06u.%s", __save_path.c_str(), ++__save_num, __save_type.c_str()) != -1) {
562  Glib::ustring fn = ctmp;
563  __widget->save_image(fn, __save_type);
564  free(ctmp);
565  } else {
566  printf("Cannot save image, asprintf() ran out of memory\n");
567  }
568  }
569  }
570  }
571  }
572  }
573  }
574  catch (fawkes::Exception &e) {
575  printf("Could not capture the image (%s)\n", e.what());
576  }
577 }
578 
579 
580 void
581 ImageWidget::RefThread::loop()
582 {
583  if (!__stop) {
584  ++__loop_cnt;
585 
586  if (__refresh_delay && !(__loop_cnt % __refresh_delay)) {
587  perform_refresh();
588  __do_refresh = false;
589  __loop_cnt = 0;
590  }
591 
592  if (__do_refresh) {
593  perform_refresh();
594  __do_refresh = false;
595  __loop_cnt = 0;
596  }
597  }
598  else exit();
599 
600  Glib::usleep(1000);
601 }
602 
603 /**
604  * Stops (and destroys) the thread as soon as possible (at the next loop)
605  */
606 void
607 ImageWidget::RefThread::stop()
608 {
609  __stop = true;
610 }
611 
612 
613 /** Set save on refresh.
614  * @param enabled true to enable, false to disable
615  * @param path save path
616  * @param type save type
617  * @param img_num image number to save
618  */
619 void
620 ImageWidget::RefThread::save_on_refresh(bool enabled, std::string path, Glib::ustring type, unsigned int img_num)
621 {
622  __save_imgs = enabled;
623 
624  if (__save_imgs) {
625  __save_path = path;
626  __save_type = type;
627  __save_num = img_num;
628  }
629 }
630 
631 /** Get image number.
632  * @return image number
633  */
634 unsigned int
635 ImageWidget::RefThread::get_img_num()
636 {
637  return __save_num;
638 }
639 
640 } // 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.
long in_msec() const
Convert the stored time into milli-seconds.
Definition: time.cpp:242
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.
unsigned int get_width() const
Returns the image buffer width.
void unlock()
Unlock the mutex.
Definition: mutex.cpp:135
virtual unsigned int pixel_width()=0
Width of image in pixels.
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.
A NULL pointer was supplied where not allowed.
Definition: software.h:34
bool save_image(std::string filename, Glib::ustring type) const
Saves the current content of the Image.
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.
Lossy image scaler.
Definition: lossy.h:35
unsigned int get_height() const
Returns the image buffer height.
virtual const char * what() const
Get primary string.
Definition: exception.cpp:661
Base class for exceptions in Fawkes.
Definition: exception.h:36
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.
Glib::RefPtr< Gdk::Pixbuf > get_buffer() const
Returns the widgets pixel buffer (RGB!)
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.