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