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