Fawkes API  Fawkes Development Version
image_widget.cpp
00001 /***************************************************************************
00002  *  image_widget.cpp - Gtkmm widget to draw an image inside a Gtk::Window
00003  *
00004  *  Created:  26.11.2008
00005  *  Copyright 2008 Christof Rath <christof.rath@gmail.com>
00006  *
00007  ****************************************************************************/
00008 
00009 /*  This program is free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  This program is distributed in the hope that it will be useful,
00015  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00016  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017  *  GNU Library General Public License for more details.
00018  *
00019  *  Read the full text in the LICENSE.GPL file in the doc directory.
00020  */
00021 
00022 
00023 #include "image_widget.h"
00024 
00025 #include <core/exceptions/software.h>
00026 #include <core/threading/mutex.h>
00027 #include <fvutils/color/conversions.h>
00028 #include <fvutils/color/yuv.h>
00029 #include <fvutils/scalers/lossy.h>
00030 #include <fvcams/camera.h>
00031 
00032 #include <iomanip>
00033 
00034 
00035 namespace firevision {
00036 #if 0 /* just to make Emacs auto-indent happy */
00037 }
00038 #endif
00039 
00040 /** @class ImageWidget <fvwidgets/image_widget.h>
00041  * This class is an image container to display fawkes cameras (or image
00042  * buffers) inside a Gtk::Container
00043  *
00044  * @author Christof Rath
00045  */
00046 
00047 /**
00048  * Creates a new ImageWidget with predefined width and height
00049  * @param width of the widget
00050  * @param height of the widget
00051  */
00052 ImageWidget::ImageWidget(unsigned int width, unsigned int height)
00053 {
00054   __cam            = NULL;
00055   __cam_enabled    = false;
00056   __cam_mutex      = new fawkes::Mutex;
00057   __refresh_thread = NULL;
00058 
00059   set_size(width, height);
00060 }
00061 
00062 /**
00063  * Creates a new ImageWidget with a Camera as image source
00064  * @param cam the image source
00065  * @param refresh_delay if greater 0 a thread gets created that refreshes
00066  *        the Image every refresh_delay milliseconds
00067  * @param width of the widget (if not equal to the camera width the image
00068  *        gets scaled)
00069  * @param height of the widget (if not equal to the camera height the
00070  *        image gets scaled)
00071  */
00072 ImageWidget::ImageWidget(Camera *cam, unsigned int refresh_delay, unsigned int width, unsigned int height)
00073 {
00074   if (!cam) throw fawkes::NullPointerException("Parameter cam may not be NULL");
00075 
00076   __cam            = cam;
00077   __cam_enabled    = true;
00078   __cam_mutex      = new fawkes::Mutex;
00079   __cam_has_buffer = false;
00080 
00081   set_size(width, height);
00082 
00083   try {
00084     fawkes::Time *time = __cam->capture_time();
00085     delete time;
00086     __cam_has_timestamp = true;
00087   }
00088   catch (fawkes::Exception &e) {
00089     __cam_has_timestamp = false;
00090   }
00091 
00092   __refresh_thread = new RefThread(this, refresh_delay);
00093   __refresh_thread->start();
00094   __refresh_thread->refresh_cam();
00095 }
00096 
00097 /** Constructor for Gtk::Builder.
00098  * Constructor that can be used to instantiate an ImageWidget as a
00099  * derived widget from a Gtk builder file.
00100  *
00101  * Note: The ImageWidget (and its internal buffer) is set to the size
00102  * as in the UI file, in case no camera is set afterwards. Use @see
00103  * ImageWidget::set_size() to resize the ImageWidget afterwards.
00104  *
00105  * @param cobject pointer to the base object
00106  * @param builder Builder
00107  */
00108 ImageWidget::ImageWidget(BaseObjectType* cobject, Glib::RefPtr<Gtk::Builder> builder)
00109   : Gtk::Image(cobject)
00110 {
00111   __cam            = NULL;
00112   __cam_enabled    = false;
00113   __cam_mutex      = new fawkes::Mutex;
00114   __refresh_thread = NULL;
00115 //   set_size(Gtk::Image::get_width(), Gtk::Image::get_height());
00116 }
00117 
00118 #ifdef HAVE_GLADEMM
00119 /** Constructor for Glade.
00120  * Constructor that can be used to instantiate an ImageWidget as a
00121  * derived widget from a Glade file.
00122  *
00123  * Note: The ImageWidget (and its internal buffer) is set to the size
00124  * as in the glade file, in case no camera is set afterwards. Use @see
00125  * ImageWidget::set_size() to resize the ImageWidget afterwards.
00126  *
00127  * @param cobject pointer to the base object
00128  * @param refxml the Glade XML file
00129  */
00130 ImageWidget::ImageWidget(BaseObjectType* cobject, Glib::RefPtr<Gnome::Glade::Xml> refxml)
00131   : Gtk::Image( cobject )
00132 {
00133   __cam            = NULL;
00134   __cam_enabled    = false;
00135   __cam_mutex      = new fawkes::Mutex;
00136   __refresh_thread = NULL;
00137 
00138 //   set_size(Gtk::Image::get_width(), Gtk::Image::get_height());
00139 }
00140 #endif
00141 
00142 /**
00143  * Destructor
00144  */
00145 ImageWidget::~ImageWidget()
00146 {
00147   if (__refresh_thread) __refresh_thread->stop();
00148   delete __cam_mutex;
00149 }
00150 
00151 /** Set the camera from which the ImageWidget obtains the images.
00152  *
00153  * Note: The size of the ImageWidget remains untouched and the cameras
00154  * image gets scaled appropriately. Use ImageWidget::set_size(0, 0) to
00155  * set the widget to the size of the camera.
00156  *
00157  * @param cam the camera
00158  * @param refresh_delay the delay between two refreshs in milliseconds
00159  */
00160 void
00161 ImageWidget::set_camera(Camera *cam, unsigned int refresh_delay)
00162 {
00163   __cam            = cam;
00164   __cam_enabled    = true;
00165   __cam_has_buffer = false;
00166 
00167   set_size(__cam->pixel_width(), __cam->pixel_height());
00168 
00169   try {
00170     fawkes::Time *time = __cam->capture_time();
00171     delete time;
00172     __cam_has_timestamp = true;
00173   }
00174   catch (fawkes::Exception &e) {
00175     __cam_has_timestamp = false;
00176   }
00177 
00178   if ( __refresh_thread ) {
00179     __refresh_thread->set_delay(refresh_delay);
00180   } else {
00181     __refresh_thread = new RefThread(this, refresh_delay);
00182     __refresh_thread->start();
00183   }
00184 
00185   __refresh_thread->refresh_cam();
00186 }
00187 
00188 /**
00189  * En-/disable the camera.
00190  * @param enable if true the camera is enabled and the refresh thread
00191  * is start, if false the refresh thread is stopped and the camera is
00192  * disabled
00193  */
00194 void
00195 ImageWidget::enable_camera(bool enable)
00196 {
00197   if ( !enable && __cam_enabled ) {
00198     __refresh_thread->stop();
00199   } else if ( __refresh_thread && enable && !__cam_enabled ) {
00200     __refresh_thread->start();
00201   }
00202 
00203   __cam_enabled = enable;
00204 }
00205 
00206 /** Sets the size of the ImageWidget.
00207  * Updates the internal buffer and the size request for the ImageWidget.
00208  * If width and/or height are set to 0 (and a Camera is set) the
00209  * ImageWidget will be set to the camera dimensions.
00210  *
00211  * Note: The ImageWidget must be refreshed after changing its size!
00212  *
00213  * @param width The new width
00214  * @param height The new height
00215  */
00216 void
00217 ImageWidget::set_size(unsigned int width, unsigned int height)
00218 {
00219   if (!width || ! height) {
00220     if (__cam) {
00221       width  = __cam->pixel_width();
00222       height = __cam->pixel_height();
00223     }
00224     else {
00225       throw fawkes::IllegalArgumentException("ImageWidget::set_size(): width and/or height may not be 0 if no Camera is set");
00226     }
00227   }
00228 
00229   if (!__pixbuf || __width != width || __height != height) {
00230     __width  = width;
00231     __height = height;
00232 
00233 #if GLIBMM_MAJOR_VERSION > 2 || ( GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION >= 14 )
00234     __pixbuf.reset();
00235 #else
00236     __pixbuf.clear();
00237 #endif
00238 
00239     __pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, __width, __height);
00240 
00241     set_size_request(__width, __height);
00242   }
00243 }
00244 /**
00245  * Returns the image buffer width
00246  * @return width of the contained image
00247  */
00248 unsigned int
00249 ImageWidget::get_width() const
00250 {
00251     return __width;
00252 }
00253 
00254 /**
00255  * Returns the image buffer height
00256  * @return height of the contained image
00257  */
00258 unsigned int
00259 ImageWidget::get_height() const
00260 {
00261     return __height;
00262 }
00263 
00264 /**
00265  * Returns the widgets pixel buffer (RGB!)
00266  * @return the RGB pixel buffer
00267  */
00268 Glib::RefPtr<Gdk::Pixbuf>
00269 ImageWidget::get_buffer() const
00270 {
00271     return __pixbuf;
00272 }
00273 
00274 /**
00275  * Sets a pixel to the given RGB colors
00276  *
00277  * @param x position of the pixel
00278  * @param y position of the pixel
00279  * @param r component of the color
00280  * @param g component of the color
00281  * @param b component of the color
00282  */
00283 void
00284 ImageWidget::set_rgb(unsigned int x, unsigned int y, unsigned char r, unsigned char g, unsigned char b)
00285 {
00286   set_rgb (x, y, (RGB_t){r, g, b});
00287 }
00288 
00289 /**
00290  * Sets a pixel to the given RGB colors
00291  *
00292  * @param x position of the pixel
00293  * @param y position of the pixel
00294  * @param rgb the color
00295  */
00296 void
00297 ImageWidget::set_rgb(unsigned int x, unsigned int y, RGB_t rgb)
00298 {
00299   if (x >= __width) throw fawkes::OutOfBoundsException("x-Coordinate exeeds image width", x, 0, __width);
00300   if (y >= __height) throw fawkes::OutOfBoundsException("y-Coordinate exeeds image height", x, 0, __height);
00301 
00302   RGB_t * target = RGB_PIXEL_AT(__pixbuf->get_pixels(), __width, x, y);
00303   *target = rgb;
00304 }
00305 
00306 /**
00307  * Show image from given colorspace.
00308  * Warning: If width and/or height not set, it is assumed, that the given
00309  * buffer has the same dimension as the widget.
00310  *
00311  * @param colorspace colorspace of the supplied buffer
00312  * @param buffer image buffer
00313  * @param width Width of the provided buffer (may be scaled to ImageWidget
00314  *        dimensions)
00315  * @param height Height of the provided buffer (may be scaled to
00316  *        ImageWidget dimensions)
00317  * @return TRUE if the buffer chould have been shown
00318  */
00319 bool
00320 ImageWidget::show(colorspace_t colorspace, unsigned char *buffer, unsigned int width, unsigned int height)
00321 {
00322   try {
00323     if (!width || !height || (width == __width && height == __height)) {
00324       convert(colorspace, RGB, buffer, __pixbuf->get_pixels(), __width, __height);
00325     }
00326     else {
00327       unsigned char *scaled_buffer = (unsigned char *)malloc(colorspace_buffer_size(colorspace, __width, __height));
00328 
00329       if (scaled_buffer) {
00330         LossyScaler scaler;
00331         scaler.set_original_buffer(buffer);
00332         scaler.set_original_dimensions(width, height);
00333         scaler.set_scaled_buffer(scaled_buffer);
00334         scaler.set_scaled_dimensions(__width, __height);
00335         scaler.scale();
00336 
00337         convert(colorspace, RGB, scaled_buffer, __pixbuf->get_pixels(), __width, __height);
00338 
00339         free(scaled_buffer);
00340       }
00341     }
00342   }
00343   catch (fawkes::Exception &e) {
00344     printf("ImageWidget::show(): %s\n", e.what());
00345     return false;
00346   }
00347 
00348   try {
00349     set(__pixbuf);
00350     __signal_show.emit(colorspace, buffer, width, height);
00351     return true;
00352   }
00353   catch (fawkes::Exception &e) {
00354     printf("ImageWidget::show(): Could not set the new image (%s)\n", e.what());
00355   }
00356 
00357   return false;
00358 }
00359 
00360 
00361 /** Signal emits after a new buffer gets successfully shown
00362  * (see @see ImageWidget::show()).
00363  *
00364  * The buffer's validity can not be guaranteed beyond the called functions
00365  * scope! In case the source of the widget is a Camera, the buffer gets
00366  * disposed after calling ImageWidget::show.
00367  *
00368  * @return The signal_show signal
00369  */
00370 sigc::signal<void, colorspace_t, unsigned char *, unsigned int, unsigned int> &
00371 ImageWidget::signal_show()
00372 {
00373   return __signal_show;
00374 }
00375 
00376 
00377 /**
00378  * Sets the refresh delay for automatic camera refreshes
00379  *
00380  * @param refresh_delay im [ms]
00381  */
00382 void
00383 ImageWidget::set_refresh_delay(unsigned int refresh_delay)
00384 {
00385   __refresh_thread->set_delay(refresh_delay);
00386 }
00387 
00388 
00389 /**
00390  * Performs a refresh during the next loop of the refresh thread
00391  */
00392 void
00393 ImageWidget::refresh_cam()
00394 {
00395   if ( __cam_enabled ) {
00396     __refresh_thread->refresh_cam();
00397   }
00398 }
00399 
00400 /**
00401  * Sets the widgets pixbuf after (i.e. non blocking) retrieving the image
00402  * over the network.
00403  */
00404 void
00405 ImageWidget::set_cam()
00406 {
00407   if ( !__cam_enabled ) { return; }
00408 
00409   __cam_mutex->lock();
00410 
00411   if (__cam_has_buffer) {
00412     show(__cam->colorspace(), __cam->buffer(), __cam->pixel_width(), __cam->pixel_height());
00413     __cam->flush();
00414     __cam_has_buffer = false;
00415   }
00416 
00417   __cam_mutex->unlock();
00418 }
00419 
00420 /**
00421  * Saves the current content of the Image
00422  * @param filename of the output
00423  * @param type of the output (By default, "jpeg", "png", "ico" and "bmp"
00424  *        are possible file formats to save in, but more formats may be
00425  *        installed. The list of all writable formats can be determined
00426  *        by using Gdk::Pixbuf::get_formats() with
00427  *        Gdk::PixbufFormat::is_writable().)
00428  * @return true on success, false otherwise
00429  */
00430 bool
00431 ImageWidget::save_image(std::string filename, Glib::ustring type) const throw()
00432 {
00433   __cam_mutex->lock();
00434 
00435   try {
00436 #ifdef GLIBMM_EXCEPTIONS_ENABLED
00437     __pixbuf->save(filename, type);
00438 #else
00439     std::auto_ptr<Glib::Error> error;
00440     __pixbuf->save(filename, type, error);
00441 #endif
00442     __cam_mutex->unlock();
00443     return true;
00444   }
00445   catch (Glib::Exception &e) {
00446     __cam_mutex->unlock();
00447     printf("save failed: %s\n", e.what().c_str());
00448     return false;
00449   }
00450 }
00451 
00452 /**
00453  * Saves the content of the image on every refresh
00454  *
00455  * @param enable  enables or disables the feature
00456  * @param path    to save the images at
00457  * @param type    file type (@see ImageWidget::save_image)
00458  * @param img_num of which to start the numbering (actually the first
00459  *        image is numbered img_num + 1)
00460  */
00461 void
00462 ImageWidget::save_on_refresh_cam(bool enable, std::string path, Glib::ustring type, unsigned int img_num)
00463 {
00464   __refresh_thread->save_on_refresh(enable, path, type, img_num);
00465 }
00466 
00467 /**
00468  * Returns the latest image number
00469  * @return the latest image number
00470  */
00471 unsigned int
00472 ImageWidget::get_image_num()
00473 {
00474   return __refresh_thread->get_img_num();
00475 }
00476 
00477 /**
00478  * Creates a new refresh thread
00479  *
00480  * @param widget to be refreshed
00481  * @param refresh_delay time between two refreshes (in [ms])
00482  */
00483 ImageWidget::RefThread::RefThread(ImageWidget *widget, unsigned int refresh_delay)
00484 : Thread("ImageWidget refresh thread")
00485 {
00486   set_delete_on_exit(true);
00487 
00488   __widget     = widget;
00489   __stop       = false;
00490   __do_refresh = false;
00491 
00492   __save_imgs  = false;
00493   __save_num   = 0;
00494 
00495   __dispatcher.connect( sigc::mem_fun( *widget , &ImageWidget::set_cam ) );
00496 
00497   set_delay(refresh_delay);
00498 }
00499 
00500 /**
00501  * Sets the refresh delay for automatic camera refreshes
00502  *
00503  * @param refresh_delay im [ms]
00504  */
00505 void
00506 ImageWidget::RefThread::set_delay(unsigned int refresh_delay)
00507 {
00508   __refresh_delay = refresh_delay;
00509   __loop_cnt = 0;
00510 }
00511 
00512 /**
00513  * Refreshes the camera during the next loop
00514  */
00515 void
00516 ImageWidget::RefThread::refresh_cam()
00517 {
00518   __do_refresh = true;
00519 }
00520 
00521 /**
00522  * Refreshes the Image (getting a new frame from the camera)
00523  */
00524 void
00525 ImageWidget::RefThread::perform_refresh()
00526 {
00527   if (!__widget->__cam) {
00528     throw fawkes::NullPointerException("Camera hasn't been given during creation");
00529   }
00530 
00531   try {
00532     if (__widget->__cam_mutex->try_lock()) {
00533       __widget->__cam->dispose_buffer();
00534       __widget->__cam->capture();
00535       if (!__stop) {
00536         __widget->__cam_has_buffer = true;
00537         __widget->__cam_mutex->unlock();
00538 
00539         if (__widget->__cam->ready()) {
00540           __dispatcher();
00541 
00542           if (__save_imgs) {
00543             char *ctmp;
00544             if (__widget->__cam_has_timestamp) {
00545               try {
00546                 fawkes::Time *ts = __widget->__cam->capture_time();
00547                 if (asprintf(&ctmp, "%s/%06u.%ld.%s", __save_path.c_str(), ++__save_num, ts->in_msec(), __save_type.c_str()) != -1) {
00548                   Glib::ustring fn = ctmp;
00549                   __widget->save_image(fn, __save_type);
00550                   free(ctmp);
00551                 } else {
00552                   printf("Cannot save image, asprintf() ran out of memory\n");
00553                 }
00554                 delete ts;
00555               }
00556               catch (fawkes::Exception &e) {
00557                 printf("Cannot save image (%s)\n", e.what());
00558               }
00559             }
00560             else {
00561               if (asprintf(&ctmp, "%s/%06u.%s", __save_path.c_str(), ++__save_num, __save_type.c_str()) != -1) {
00562                 Glib::ustring fn = ctmp;
00563                 __widget->save_image(fn, __save_type);
00564                 free(ctmp);
00565               } else {
00566                 printf("Cannot save image, asprintf() ran out of memory\n");
00567               }
00568             }
00569           }
00570         }
00571       }
00572     }
00573   }
00574   catch (fawkes::Exception &e) {
00575     printf("Could not capture the image (%s)\n", e.what());
00576   }
00577 }
00578 
00579 
00580 void
00581 ImageWidget::RefThread::loop()
00582 {
00583   if (!__stop) {
00584     ++__loop_cnt;
00585 
00586     if (__refresh_delay && !(__loop_cnt % __refresh_delay)) {
00587       perform_refresh();
00588       __do_refresh = false;
00589       __loop_cnt = 0;
00590     }
00591 
00592     if (__do_refresh) {
00593       perform_refresh();
00594       __do_refresh = false;
00595       __loop_cnt   = 0;
00596     }
00597   }
00598   else exit();
00599 
00600   Glib::usleep(1000);
00601 }
00602 
00603 /**
00604  * Stops (and destroys) the thread as soon as possible (at the next loop)
00605  */
00606 void
00607 ImageWidget::RefThread::stop()
00608 {
00609   __stop = true;
00610 }
00611 
00612 
00613 /** Set save on refresh.
00614  * @param enabled true to enable, false to disable
00615  * @param path save path
00616  * @param type save type
00617  * @param img_num image number to save
00618  */
00619 void
00620 ImageWidget::RefThread::save_on_refresh(bool enabled, std::string path, Glib::ustring type, unsigned int img_num)
00621 {
00622   __save_imgs = enabled;
00623 
00624   if (__save_imgs) {
00625     __save_path = path;
00626     __save_type = type;
00627     __save_num  = img_num;
00628   }
00629 }
00630 
00631 /** Get image number.
00632  * @return image number
00633  */
00634 unsigned int
00635 ImageWidget::RefThread::get_img_num()
00636 {
00637   return __save_num;
00638 }
00639 
00640 } // end namespace firevision