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
33namespace 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 */
47ImageWidget::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 */
106ImageWidget::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 */
128ImageWidget::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 */
159void
160ImageWidget::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 */
192void
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 */
214void
215ImageWidget::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 */
246unsigned int
248{
249 return width_;
250}
251
252/**
253 * Returns the image buffer height
254 * @return height of the contained image
255 */
256unsigned int
258{
259 return height_;
260}
261
262/**
263 * Returns the widgets pixel buffer (RGB!)
264 * @return the RGB pixel buffer
265 */
266Glib::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 */
281void
282ImageWidget::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 */
298void
299ImageWidget::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 */
323bool
324ImageWidget::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 */
374sigc::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 */
385void
386ImageWidget::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 */
394void
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 */
406void
407ImageWidget::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 */
434bool
435ImageWidget::save_image(std::string filename, Glib::ustring type) const noexcept
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 */
459void
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 */
472unsigned 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 */
484ImageWidget::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 */
506void
507ImageWidget::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 */
516void
517ImageWidget::RefThread::refresh_cam()
518{
519 do_refresh_ = true;
520}
521
522/**
523 * Refreshes the Image (getting a new frame from the camera)
524 */
525void
526ImageWidget::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
584void
585ImageWidget::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 */
610void
611ImageWidget::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 */
622void
623ImageWidget::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 */
640unsigned int
641ImageWidget::RefThread::get_img_num()
642{
643 return save_num_;
644}
645
646} // end namespace firevision
Base class for exceptions in Fawkes.
Definition: exception.h:36
virtual const char * what() const noexcept
Get primary string.
Definition: exception.cpp:639
Expected parameter is missing.
Definition: software.h:80
Mutex mutual exclusion lock.
Definition: mutex.h:33
void lock()
Lock this mutex.
Definition: mutex.cpp:87
void unlock()
Unlock the mutex.
Definition: mutex.cpp:131
A NULL pointer was supplied where not allowed.
Definition: software.h:32
Index out of bounds.
Definition: software.h:86
A class for handling time.
Definition: time.h:93
long in_msec() const
Convert the stored time into milli-seconds.
Definition: time.cpp:228
Camera interface for image aquiring devices in FireVision.
Definition: camera.h:33
virtual fawkes::Time * capture_time()
Get the Time of the last successfully captured image.
Definition: camera.cpp:137
virtual unsigned int pixel_height()=0
Height of image in pixels.
virtual void flush()=0
Flush image queue.
virtual unsigned int pixel_width()=0
Width of image in pixels.
virtual colorspace_t colorspace()=0
Colorspace of returned image.
virtual unsigned char * buffer()=0
Get access to current image buffer.
This class is an image container to display fawkes cameras (or image buffers) inside a Gtk::Container...
Definition: image_widget.h:43
void set_refresh_delay(unsigned int refresh_delay)
Sets the refresh delay for automatic camera refreshes.
void enable_camera(bool enable)
En-/disable the camera.
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.
sigc::signal< void, colorspace_t, unsigned char *, unsigned int, unsigned int > & signal_show()
Signal emits after a new buffer gets successfully shown (see.
virtual bool show(colorspace_t colorspace, unsigned char *buffer, unsigned int width=0, unsigned int height=0)
Show image from given colorspace.
void set_size(unsigned int width, unsigned int height)
Sets the size of the ImageWidget.
void set_camera(Camera *cam, unsigned int refresh_delay=0)
Set the camera from which the ImageWidget obtains the images.
ImageWidget(unsigned int width, unsigned int height)
Creates a new ImageWidget with predefined width and height.
unsigned int get_image_num()
Returns the latest image number.
bool save_image(std::string filename, Glib::ustring type) const noexcept
Saves the current content of the Image.
unsigned int get_height() const
Returns the image buffer height.
virtual ~ImageWidget()
Destructor.
Glib::RefPtr< Gdk::Pixbuf > get_buffer() const
Returns the widgets pixel buffer (RGB!)
unsigned int get_width() const
Returns the image buffer width.
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.
void refresh_cam()
Performs a refresh during the next loop of the refresh thread.
Lossy image scaler.
Definition: lossy.h:33
virtual void scale()
Scale image.
Definition: lossy.cpp:139
virtual void set_scaled_dimensions(unsigned int width, unsigned int height)
Set dimenins of scaled image buffer.
Definition: lossy.cpp:83
virtual void set_original_dimensions(unsigned int width, unsigned int height)
Set original image dimensions.
Definition: lossy.cpp:76
virtual void set_original_buffer(unsigned char *buffer)
Set original image buffer.
Definition: lossy.cpp:109
virtual void set_scaled_buffer(unsigned char *buffer)
Set scaled image buffer.
Definition: lossy.cpp:115
Structure defining an RGB pixel (in R-G-B byte ordering).
Definition: rgb.h:62