1 /*
2  * * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3  * * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19 
20 #include <stdlib.h> /* system() */
21 #include <rawstudio.h>
22 #include "rs-filter.h"
23 
24 #if 0 /* Change to 1 to enable performance info */
25 #define filter_performance printf
26 #define FILTER_SHOW_PERFORMANCE
27 #else
28 #define filter_performance(...)
29 #endif
30 
31 /* How much time should a filter at least have taken to show performance number */
32 #define FILTER_PERF_ELAPSED_MIN 0.001
33 #define CHAIN_PERF_ELAPSED_MIN 0.001
34 
35 G_DEFINE_TYPE (RSFilter, rs_filter, G_TYPE_OBJECT)
36 
37 enum {
38   CHANGED_SIGNAL,
39   LAST_SIGNAL
40 };
41 
42 static guint signals[LAST_SIGNAL] = { 0 };
43 
44 static void
dispose(GObject * obj)45 dispose(GObject *obj)
46 {
47 	RSFilter *filter = RS_FILTER(obj);
48 
49 	if (!filter->dispose_has_run)
50 	{
51 		filter->dispose_has_run = TRUE;
52 		if (filter->previous)
53 		{
54 			filter->previous->next_filters = g_slist_remove(filter->previous->next_filters, filter);
55 			g_object_unref(filter->previous);
56 		}
57 	}
58 }
59 
60 static void
rs_filter_class_init(RSFilterClass * klass)61 rs_filter_class_init(RSFilterClass *klass)
62 {
63 	RS_DEBUG(FILTERS, "rs_filter_class_init(%p)", klass);
64 	GObjectClass *object_class = G_OBJECT_CLASS(klass);
65 
66 	signals[CHANGED_SIGNAL] = g_signal_new ("changed",
67 		G_TYPE_FROM_CLASS (klass),
68 		G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
69 		0,
70 		NULL,
71 		NULL,
72 		g_cclosure_marshal_VOID__INT,
73 		G_TYPE_NONE, 1, G_TYPE_INT);
74 
75 	klass->get_image = NULL;
76 	klass->get_image8 = NULL;
77 	klass->get_size = NULL;
78 	klass->previous_changed = NULL;
79 
80 	object_class->dispose = dispose;
81 }
82 
83 static void
rs_filter_init(RSFilter * self)84 rs_filter_init(RSFilter *self)
85 {
86 	RS_DEBUG(FILTERS, "rs_filter_init(%p)", self);
87 	self->previous = NULL;
88 	self->next_filters = NULL;
89 	self->enabled = TRUE;
90 }
91 
92 /**
93  * Return a new instance of a RSFilter
94  * @param name The name of the filter
95  * @param previous The previous filter or NULL
96  * @return The newly instantiated RSFilter or NULL
97  */
98 RSFilter *
rs_filter_new(const gchar * name,RSFilter * previous)99 rs_filter_new(const gchar *name, RSFilter *previous)
100 {
101 	RS_DEBUG(FILTERS, "rs_filter_new(%s, %s [%p])", name, RS_FILTER_NAME(previous), previous);
102 	g_assert(name != NULL);
103 	g_assert((previous == NULL) || RS_IS_FILTER(previous));
104 
105 	GType type = g_type_from_name(name);
106 	RSFilter *filter = NULL;
107 
108 	if (g_type_is_a (type, RS_TYPE_FILTER))
109 		filter = g_object_new(type, NULL);
110 
111 	if (!RS_IS_FILTER(filter))
112 		g_warning("Could not instantiate filter of type \"%s\"", name);
113 
114 	if (previous)
115 		rs_filter_set_previous(filter, previous);
116 
117 	return filter;
118 }
119 
120 /**
121  * Set the previous RSFilter in a RSFilter-chain
122  * @param filter A RSFilter
123  * @param previous A previous RSFilter
124  */
125 void
rs_filter_set_previous(RSFilter * filter,RSFilter * previous)126 rs_filter_set_previous(RSFilter *filter, RSFilter *previous)
127 {
128 	RS_DEBUG(FILTERS, "rs_filter_set_previous(%p, %p)", filter, previous);
129 	g_assert(RS_IS_FILTER(filter));
130 	g_assert(RS_IS_FILTER(previous));
131 
132 	/* We will only set the previous filter if it differs from current previous filter */
133 	if (filter->previous != previous)
134 	{
135 		if (filter->previous)
136 		{
137 			/* If we already got a previous filter, clean up */
138 			filter->previous->next_filters = g_slist_remove(filter->previous->next_filters, filter);
139 			g_object_unref(filter->previous);
140 		}
141 		else
142 			filter->previous = g_object_ref(previous);
143 
144 		previous->next_filters = g_slist_append(previous->next_filters, filter);
145 	}
146 }
147 
148 /**
149  * Signal that a filter has changed, filters depending on this will be invoked
150  * This should only be called from filter code
151  * @param filter The changed filter
152  * @param mask A mask indicating what changed
153  */
154 void
rs_filter_changed(RSFilter * filter,RSFilterChangedMask mask)155 rs_filter_changed(RSFilter *filter, RSFilterChangedMask mask)
156 {
157 	RS_DEBUG(FILTERS, "rs_filter_changed(%s [%p], %04x)", RS_FILTER_NAME(filter), filter, mask);
158 	g_assert(RS_IS_FILTER(filter));
159 
160 	gint i, n_next = g_slist_length(filter->next_filters);
161 
162 	for(i=0; i<n_next; i++)
163 	{
164 		RSFilter *next = RS_FILTER(g_slist_nth_data(filter->next_filters, i));
165 
166 		g_assert(RS_IS_FILTER(next));
167 
168 		/* Notify "next" filter or try "next next" filter */
169 		if (RS_FILTER_GET_CLASS(next)->previous_changed)
170 			RS_FILTER_GET_CLASS(next)->previous_changed(next, filter, mask);
171 		else
172 			rs_filter_changed(next, mask);
173 	}
174 
175 	g_signal_emit(G_OBJECT(filter), signals[CHANGED_SIGNAL], 0, mask);
176 }
177 
178 /* Clamps ROI rectangle to image size */
179 /* Returns a new rectangle, or NULL if ROI was within bounds*/
180 
181 static GdkRectangle*
clamp_roi(const GdkRectangle * roi,RSFilter * filter,const RSFilterRequest * request)182 clamp_roi(const GdkRectangle *roi, RSFilter *filter, const RSFilterRequest *request)
183 {
184 	RSFilterResponse *response = rs_filter_get_size(filter, request);
185 	gint w = rs_filter_response_get_width(response);
186 	gint h = rs_filter_response_get_height(response);
187 	g_object_unref(response);
188 
189 	if ((roi->x >= 0) && (roi->y >=0) && (roi->x + roi->width <= w) && (roi->y + roi->height <= h))
190 		return NULL;
191 
192 	GdkRectangle* new_roi = g_new(GdkRectangle, 1);
193 	new_roi->x = MAX(0, roi->x);
194 	new_roi->y = MAX(0, roi->y);
195 	new_roi->width = MIN(w - new_roi->x, roi->width);
196 	new_roi->height = MAX(h - new_roi->y, roi->height);
197 	return new_roi;
198 }
199 
200 /**
201  * Get the output image from a RSFilter
202  * @param filter A RSFilter
203  * @param param A RSFilterRequest defining parameters for a image request
204  * @return A RS_IMAGE16, this must be unref'ed
205  */
206 RSFilterResponse *
rs_filter_get_image(RSFilter * filter,const RSFilterRequest * request)207 rs_filter_get_image(RSFilter *filter, const RSFilterRequest *request)
208 {
209 	GdkRectangle* roi = NULL;
210 	RSFilterRequest *r = NULL;
211 
212 	RS_DEBUG(FILTERS, "rs_filter_get_image(%s [%p])", RS_FILTER_NAME(filter), filter);
213 
214 	/* This timer-hack will break badly when multithreaded! */
215 	static gfloat last_elapsed = 0.0;
216 	static gint count = -1;
217 	gfloat elapsed;
218 	static GTimer *gt = NULL;
219 
220 	RSFilterResponse *response;
221 	RS_IMAGE16 *image;
222 	g_assert(RS_IS_FILTER(filter));
223 
224 	if (count == -1)
225 		gt = g_timer_new();
226 	count++;
227 
228 	if (filter->enabled && (roi = rs_filter_request_get_roi(request)))
229 	{
230 		roi = clamp_roi(roi, filter, request);
231 		if (roi)
232 		{
233 			r = rs_filter_request_clone(request);
234 			rs_filter_request_set_roi(r, roi);
235 			request = r;
236 		}
237 	}
238 
239 	if (RS_FILTER_GET_CLASS(filter)->get_image && filter->enabled)
240 		response = RS_FILTER_GET_CLASS(filter)->get_image(filter, request);
241 	else
242 		response = rs_filter_get_image(filter->previous, request);
243 
244 	g_assert(RS_IS_FILTER_RESPONSE(response));
245 
246 	image = rs_filter_response_get_image(response);
247 
248 	elapsed = g_timer_elapsed(gt, NULL) - last_elapsed;
249 
250 	if (roi)
251 		g_free(roi);
252 	if (r)
253 		g_object_unref(r);
254 
255 #ifdef FILTER_SHOW_PERFORMANCE
256 	if ((elapsed > FILTER_PERF_ELAPSED_MIN) && (image != NULL))
257 	{
258 		gint iw = image->w;
259 		gint ih = image->h;
260 		if (rs_filter_response_get_roi(response))
261 		{
262 			roi = rs_filter_response_get_roi(response);
263 			iw = roi->width;
264 			ih = roi->height;
265 		}
266 		filter_performance("%s took: \033[32m%.0f\033[0mms", RS_FILTER_NAME(filter), elapsed*1000);
267 		if ((elapsed > 0.001) && (image != NULL))
268 			filter_performance(" [\033[33m%.01f\033[0mMpix/s]", ((gfloat)(iw*ih))/elapsed/1000000.0);
269 		if (image)
270 			filter_performance(" [w: %d, h: %d, roi-w:%d, roi-h:%d, channels: %d, pixelsize: %d, rowstride: %d]",
271 				image->w, image->h, iw, ih, image->channels, image->pixelsize, image->rowstride);
272 		filter_performance("\n");
273 	}
274 #endif
275 	g_assert(RS_IS_IMAGE16(image) || (image == NULL));
276 	last_elapsed += elapsed;
277 
278 	count--;
279 	if (count == -1)
280 	{
281 		last_elapsed = 0.0;
282 		if (g_timer_elapsed(gt,NULL) > CHAIN_PERF_ELAPSED_MIN)
283 			filter_performance("Complete 16 bit chain took: \033[32m%.0f\033[0mms\n\n", g_timer_elapsed(gt, NULL)*1000.0);
284 		rs_filter_param_set_float(RS_FILTER_PARAM(response), "16-bit-time", g_timer_elapsed(gt, NULL));
285 		g_timer_destroy(gt);
286 	}
287 
288 	if (image)
289 		g_object_unref(image);
290 
291 	return response;
292 }
293 
294 
295 /**
296  * Get 8 bit output image from a RSFilter
297  * @param filter A RSFilter
298  * @param param A RSFilterRequest defining parameters for a image request
299  * @return A RS_IMAGE16, this must be unref'ed
300  */
301 RSFilterResponse *
rs_filter_get_image8(RSFilter * filter,const RSFilterRequest * request)302 rs_filter_get_image8(RSFilter *filter, const RSFilterRequest *request)
303 {
304 	RS_DEBUG(FILTERS, "rs_filter_get_image8(%s [%p])", RS_FILTER_NAME(filter), filter);
305 
306 	/* This timer-hack will break badly when multithreaded! */
307 	static gfloat last_elapsed = 0.0;
308 	static gint count = -1;
309 	gfloat elapsed, temp;
310 	static GTimer *gt = NULL;
311 
312 	RSFilterResponse *response = NULL;
313 	GdkPixbuf *image = NULL;
314 	GdkRectangle* roi = NULL;
315 	RSFilterRequest *r = NULL;
316 	g_assert(RS_IS_FILTER(filter));
317 
318 	if (count == -1)
319 		gt = g_timer_new();
320 	count++;
321 
322 	if (filter->enabled && (roi = rs_filter_request_get_roi(request)))
323 	{
324 		roi = clamp_roi(roi, filter, request);
325 		if (roi)
326 		{
327 			r = rs_filter_request_clone(request);
328 			rs_filter_request_set_roi(r, roi);
329 			request = r;
330 		}
331 	}
332 
333 	if (RS_FILTER_GET_CLASS(filter)->get_image8 && filter->enabled)
334 		response = RS_FILTER_GET_CLASS(filter)->get_image8(filter, request);
335 	else if (filter->previous)
336 		response = rs_filter_get_image8(filter->previous, request);
337 
338 	g_assert(RS_IS_FILTER_RESPONSE(response));
339 
340 	image = rs_filter_response_get_image8(response);
341 	elapsed = g_timer_elapsed(gt, NULL) - last_elapsed;
342 
343 	/* Subtract 16 bit time */
344 	if (rs_filter_param_get_float(RS_FILTER_PARAM(response), "16-bit-time", &temp))
345 		elapsed -= temp;
346 
347 	if (roi)
348 		g_free(roi);
349 	if (r)
350 		g_object_unref(r);
351 
352 #ifdef FILTER_SHOW_PERFORMANCE
353 	if ((elapsed > FILTER_PERF_ELAPSED_MIN) && (image != NULL)) {
354 		gint iw = gdk_pixbuf_get_width(image);
355 		gint ih = gdk_pixbuf_get_height(image);
356 		if (rs_filter_response_get_roi(response))
357 		{
358 			GdkRectangle *roi = rs_filter_response_get_roi(response);
359 			iw = roi->width;
360 			ih = roi->height;
361 		}
362 		filter_performance("%s took: \033[32m%.0f\033[0mms", RS_FILTER_NAME(filter), elapsed * 1000);
363 		filter_performance(" [\033[33m%.01f\033[0mMpix/s]", ((gfloat)(iw * ih)) / elapsed / 1000000.0);
364 		filter_performance("\n");
365 	}
366 #endif
367 
368 	last_elapsed += elapsed;
369 
370 	g_assert(GDK_IS_PIXBUF(image) || (image == NULL));
371 
372 	count--;
373 	if (count == -1)
374 	{
375 		last_elapsed = 0.0;
376 		rs_filter_param_get_float(RS_FILTER_PARAM(response), "16-bit-time", &last_elapsed);
377 		last_elapsed = g_timer_elapsed(gt, NULL)-last_elapsed;
378 		if (last_elapsed > CHAIN_PERF_ELAPSED_MIN)
379 			filter_performance("Complete 8 bit chain took: \033[32m%.0f\033[0mms\n\n", last_elapsed*1000.0);
380 		g_timer_destroy(gt);
381 		last_elapsed = 0.0;
382 	}
383 
384 	if (image)
385 		g_object_unref(image);
386 
387 	return response;
388 }
389 
390 /**
391  * Get predicted size of a RSFilter
392  * @param filter A RSFilter
393  * @param request A RSFilterRequest defining parameters for the request
394  */
395 RSFilterResponse *
rs_filter_get_size(RSFilter * filter,const RSFilterRequest * request)396 rs_filter_get_size(RSFilter *filter, const RSFilterRequest *request)
397 {
398 	RSFilterResponse *response = NULL;
399 
400 	g_assert(RS_IS_FILTER(filter));
401 
402 	if (RS_FILTER_GET_CLASS(filter)->get_size && filter->enabled)
403 		response = RS_FILTER_GET_CLASS(filter)->get_size(filter, request);
404 	else if (filter->previous)
405 		response = rs_filter_get_size(filter->previous, request);
406 
407 	return response;
408 }
409 
410 /**
411  * Get predicted size of a RSFilter
412  * @param filter A RSFilter
413  * @param request A RSFilterRequest defining parameters for the request
414  * @param width A pointer to a gint where the width will be written or NULL
415  * @param height A pointer to a gint where the height will be written or NULL
416  * @return TRUE if width/height is known, FALSE otherwise
417  */
418 gboolean
rs_filter_get_size_simple(RSFilter * filter,const RSFilterRequest * request,gint * width,gint * height)419 rs_filter_get_size_simple(RSFilter *filter, const RSFilterRequest *request, gint *width, gint *height)
420 {
421 	gint w, h;
422 	RSFilterResponse *response = rs_filter_get_size(filter, request);
423 
424 	w = rs_filter_response_get_width(response);
425 	h = rs_filter_response_get_height(response);
426 	if (width)
427 		*width = w;
428 	if (height)
429 		*height = h;
430 
431 	g_object_unref(response);
432 
433 	return ((w>0) && (h>0));
434 }
435 
436 /**
437  * Set a GObject property on zero or more filters above #filter recursively
438  * @param filter A RSFilter
439  * @param ... Pairs of property names and values followed by NULL
440  */
441 void
rs_filter_set_recursive(RSFilter * filter,...)442 rs_filter_set_recursive(RSFilter *filter, ...)
443 {
444 	va_list ap;
445 	gchar *property_name;
446 	RSFilter *current_filter;
447 	GParamSpec *spec;
448 	RSFilter *first_seen_here = NULL;
449 	GTypeValueTable *table = NULL;
450 	GType type = 0;
451 	union CValue {
452 		gint     v_int;
453 		glong    v_long;
454 		gint64   v_int64;
455 		gdouble  v_double;
456 		gpointer v_pointer;
457 	} value;
458 
459 	g_assert(RS_IS_FILTER(filter));
460 
461 	va_start(ap, filter);
462 
463 	/* Loop through all properties */
464 	while ((property_name = va_arg(ap, gchar *)))
465 	{
466 		/* We set table to NULL for every property to indicate that we (again)
467 		 * have an "unknown" type */
468 		table = NULL;
469 
470 		current_filter = filter;
471 		/* Iterate through all filters previous to filter */
472 		do {
473 			if ((spec = g_object_class_find_property(G_OBJECT_GET_CLASS(current_filter), property_name)))
474 				if (spec->flags & G_PARAM_WRITABLE)
475 				{
476 					/* If we got no GTypeValueTable at this point, we aquire
477 					 * one. We rely on all filters using the same type for all
478 					 * properties equally named */
479 					if (!table)
480 					{
481 						first_seen_here = current_filter;
482 						type = spec->value_type;
483 						table = g_type_value_table_peek(type);
484 
485 						/* If we have no valuetable, we're screwed, bail out */
486 						if (!table)
487 							g_error("No GTypeValueTable found for '%s'", g_type_name(type));
488 
489 						switch (table->collect_format[0])
490 						{
491 							case 'i': value.v_int = va_arg(ap, gint); break;
492 							case 'l': value.v_long = va_arg(ap, glong); break;
493 							case 'd': value.v_double = va_arg(ap, gdouble); break;
494 							case 'p': value.v_pointer = va_arg(ap, gpointer); break;
495 							default: g_error("Don't know how to collect for '%s'", g_type_name(type)); break;
496 						}
497 					}
498 
499 					if (table)
500 					{
501 						/* We try to catch cases where different filters use
502 						 * the same property name for different types */
503 						if (type != spec->value_type)
504 							g_warning("Diverging types found for property '%s' (on filter '%s' and '%s')",
505 								property_name,
506 								RS_FILTER_NAME(first_seen_here),
507 								RS_FILTER_NAME(current_filter));
508 
509 						switch (table->collect_format[0])
510 						{
511 							case 'i': g_object_set(current_filter, property_name, value.v_int, NULL); break;
512 							case 'l': g_object_set(current_filter, property_name, value.v_long, NULL); break;
513 							case 'd': g_object_set(current_filter, property_name, value.v_double, NULL); break;
514 							case 'p': g_object_set(current_filter, property_name, value.v_pointer, NULL); break;
515 							default: break;
516 						}
517 					}
518 				}
519 		} while (RS_IS_FILTER(current_filter = current_filter->previous));
520 		if (!table)
521 		{
522 //			g_warning("Property: %s could not be found in filter chain. Skipping further properties", property_name);
523 			va_end(ap);
524 			return;
525 		}
526 	}
527 
528 	va_end(ap);
529 }
530 
531 /**
532  * Get a GObject property from a RSFilter chain recursively
533  * @param filter A RSFilter
534  * @param ... Pairs of property names and a return pointers followed by NULL
535  */
536 void
rs_filter_get_recursive(RSFilter * filter,...)537 rs_filter_get_recursive(RSFilter *filter, ...)
538 {
539 	va_list ap;
540 	gchar *property_name;
541 	gpointer property_ret;
542 	RSFilter *current_filter;
543 
544 	g_assert(RS_IS_FILTER(filter));
545 
546 	va_start(ap, filter);
547 
548 	/* Loop through all properties */
549 	while ((property_name = va_arg(ap, gchar *)))
550 	{
551 		property_ret = va_arg(ap, gpointer);
552 
553 		g_assert(property_ret != NULL);
554 
555 		current_filter = filter;
556 		/* Iterate through all filter previous to filter */
557 		do {
558 			if (current_filter->enabled && g_object_class_find_property(G_OBJECT_GET_CLASS(current_filter), property_name))
559 			{
560 				g_object_get(current_filter, property_name, property_ret, NULL);
561 				break;
562 			}
563 		} while (RS_IS_FILTER(current_filter = current_filter->previous));
564 	}
565 
566 	va_end(ap);
567 }
568 
569 /**
570  * Set enabled state of a RSFilter
571  * @param filter A RSFilter
572  * @param enabled TRUE to enable filter, FALSE to disable
573  * @return Previous state
574  */
575 gboolean
rs_filter_set_enabled(RSFilter * filter,gboolean enabled)576 rs_filter_set_enabled(RSFilter *filter, gboolean enabled)
577 {
578 	gboolean previous_state;
579 
580 	g_assert(RS_IS_FILTER(filter));
581 
582 	previous_state = filter->enabled;
583 
584 	if (filter->enabled != enabled)
585 	{
586 		filter->enabled = enabled;
587 		rs_filter_changed(filter, RS_FILTER_CHANGED_PIXELDATA);
588 	}
589 
590 	return previous_state;
591 }
592 
593 /**
594  * Get enabled state of a RSFilter
595  * @param filter A RSFilter
596  * @return TRUE if filter is enabled, FALSE if disabled
597  */
598 gboolean
rs_filter_get_enabled(RSFilter * filter)599 rs_filter_get_enabled(RSFilter *filter)
600 {
601 	g_assert(RS_IS_FILTER(filter));
602 
603 	return filter->enabled;
604 }
605 
606 /**
607  * Set a label for a RSFilter - only used for debugging
608  * @param filter A RSFilter
609  * @param label A new label for the RSFilter, this will NOT be copied
610  */
611 extern void
rs_filter_set_label(RSFilter * filter,const gchar * label)612 rs_filter_set_label(RSFilter *filter, const gchar *label)
613 {
614 	g_assert(RS_IS_FILTER(filter));
615 
616 	filter->label = label;
617 }
618 
619 /**
620  * Get the label for a RSFilter
621  * @param filter A RSFilter
622  * @return The label for the RSFilter or NULL
623  */
624 const gchar *
rs_filter_get_label(RSFilter * filter)625 rs_filter_get_label(RSFilter *filter)
626 {
627 	g_assert(RS_IS_FILTER(filter));
628 
629 	return filter->label;
630 }
631 
632 static void
rs_filter_graph_helper(GString * str,RSFilter * filter)633 rs_filter_graph_helper(GString *str, RSFilter *filter)
634 {
635 	g_assert(str != NULL);
636 	g_assert(RS_IS_FILTER(filter));
637 
638 	g_string_append_printf(str, "\"%p\" [\n\tshape=\"Mrecord\"\n", filter);
639 
640 	if (!g_str_equal(RS_FILTER_NAME(filter), "RSCache"))
641 		g_string_append_printf(str, "\tcolor=grey\n\tstyle=filled\n");
642 
643 	if (filter->enabled)
644 		g_string_append_printf(str, "\tcolor=\"#66ba66\"\n");
645 	else
646 		g_string_append_printf(str, "\tcolor=grey\n");
647 
648 	g_string_append_printf(str, "\tlabel=<<table cellborder=\"0\" border=\"0\">\n");
649 
650 	GObjectClass *klass = G_OBJECT_GET_CLASS(filter);
651 	GParamSpec **specs;
652 	gint i;
653 	guint n_specs = 0;
654 
655 	/* Filter name (and label) */
656 	g_string_append_printf(str, "\t\t<tr>\n\t\t\t<td colspan=\"2\" bgcolor=\"black\"><font color=\"white\">%s", RS_FILTER_NAME(filter));
657 	if (filter->label)
658 		g_string_append_printf(str, " (%s)", filter->label);
659 	g_string_append_printf(str, "</font></td>\n\t\t</tr>\n");
660 
661 	/* Parameter and value list */
662 	specs = g_object_class_list_properties(G_OBJECT_CLASS(klass), &n_specs);
663 	for(i=0; i<n_specs; i++)
664 	{
665 		gboolean boolean = FALSE;
666 		gint integer = 0;
667 		gfloat loat = 0.0;
668 		gchar *ostr = NULL;
669 
670 		g_string_append_printf(str, "\t\t<tr>\n\t\t\t<td align=\"right\">%s:</td>\n\t\t\t<td align=\"left\">", specs[i]->name);
671 		/* We have to use if/else here, because RS_TYPE_* does not resolve to a constant */
672 		if (G_PARAM_SPEC_VALUE_TYPE(specs[i]) == RS_TYPE_LENS)
673 		{
674 			RSLens *lens;
675 			gchar *identifier;
676 
677 			g_object_get(filter, specs[i]->name, &lens, NULL);
678 			if (lens)
679 			{
680 				g_object_get(lens, "identifier", &identifier, NULL);
681 				g_object_unref(lens);
682 
683 				g_string_append_printf(str, "%s", identifier);
684 
685 				g_free(identifier);
686 			}
687 			else
688 				g_string_append_printf(str, "n/a");
689 		}
690 		else if (G_PARAM_SPEC_VALUE_TYPE(specs[i]) == RS_TYPE_ICC_PROFILE)
691 		{
692 			RSIccProfile *profile;
693 			gchar *profile_filename;
694 			gchar *profile_basename;
695 
696 			g_object_get(filter, specs[i]->name, &profile, NULL);
697 			g_object_get(profile, "filename", &profile_filename, NULL);
698 			g_object_unref(profile);
699 			profile_basename = g_path_get_basename (profile_filename);
700 			g_free(profile_filename);
701 
702 			g_string_append_printf(str, "%s", profile_basename);
703 			g_free(profile_basename);
704 		}
705 		else
706 			switch (G_PARAM_SPEC_VALUE_TYPE(specs[i]))
707 			{
708 				case G_TYPE_BOOLEAN:
709 					g_object_get(filter, specs[i]->name, &boolean, NULL);
710 					g_string_append_printf(str, "%s", (boolean) ? "TRUE" : "FALSE");
711 					break;
712 				case G_TYPE_INT:
713 					g_object_get(filter, specs[i]->name, &integer, NULL);
714 					g_string_append_printf(str, "%d", integer);
715 					break;
716 				case G_TYPE_FLOAT:
717 					g_object_get(filter, specs[i]->name, &loat, NULL);
718 					g_string_append_printf(str, "%.05f", loat);
719 					break;
720 				case G_TYPE_STRING:
721 					g_object_get(filter, specs[i]->name, &ostr, NULL);
722 					g_string_append_printf(str, "%s", ostr);
723 					break;
724 				default:
725 					g_string_append_printf(str, "n/a");
726 					break;
727 			}
728 		g_string_append_printf(str, "</td>\n\t\t</tr>\n");
729 	}
730 
731 	g_string_append_printf(str, "\t\t</table>>\n\t];\n");
732 
733 	gint n_next = g_slist_length(filter->next_filters);
734 
735 	for(i=0; i<n_next; i++)
736 	{
737 		RSFilter *next = RS_FILTER(g_slist_nth_data(filter->next_filters, i));
738 		RSFilterResponse *response = rs_filter_get_size(filter, RS_FILTER_REQUEST_QUICK);
739 
740 		/* Edge - print dimensions along */
741 		g_string_append_printf(str, "\t\"%p\" -> \"%p\" [label=\" %dx%d\"];\n",
742 			filter, next,
743 			rs_filter_response_get_width(response), rs_filter_response_get_height(response));
744 		g_object_unref(response);
745 
746 		/* Recursively call ourself for every "next" filter */
747 		rs_filter_graph_helper(str, next);
748 	}
749 }
750 
751 /**
752  * Draw a nice graph of the filter chain
753  * note: Requires graphviz
754  * @param filter The top-most filter to graph
755  */
756 void
rs_filter_graph(RSFilter * filter)757 rs_filter_graph(RSFilter *filter)
758 {
759 	gint ignore;
760 	g_assert(RS_IS_FILTER(filter));
761 	GString *str = g_string_new("digraph G {\n");
762 
763 	rs_filter_graph_helper(str, filter);
764 
765 	g_string_append_printf(str, "}\n");
766 	g_file_set_contents("/tmp/rs-filter-graph", str->str, str->len, NULL);
767 
768 	ignore = system("dot -Tpng >/tmp/rs-filter-graph.png </tmp/rs-filter-graph");
769 	ignore = system("gnome-open /tmp/rs-filter-graph.png");
770 
771 	g_string_free(str, (ignore == ignore));
772 }
773