1 #define __SP_FILTER_CHEMISTRY_C__
3 /*
4 * Various utility methods for filters
5 *
6 * Authors:
7 * Hugo Rodrigues
8 * bulia byak
9 * Niko Kiirala
10 *
11 * Copyright (C) 2006-2008 authors
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
17 #include "style.h"
18 #include "document-private.h"
19 #include "desktop-style.h"
21 #include "filter-chemistry.h"
22 #include "filter-enums.h"
24 #include "filters/blend.h"
25 #include "sp-filter.h"
26 #include "sp-filter-reference.h"
27 #include "sp-gaussian-blur.h"
28 #include "svg/css-ostringstream.h"
29 #include "libnr/nr-matrix-fns.h"
31 #include "xml/repr.h"
33 /**
34 * Count how many times the filter is used by the styles of o and its
35 * descendants
36 */
37 static guint
38 count_filter_hrefs(SPObject *o, SPFilter *filter)
39 {
40 if (!o)
41 return 1;
43 guint i = 0;
45 SPStyle *style = SP_OBJECT_STYLE(o);
46 if (style
47 && style->filter.set
48 && style->getFilter() == filter)
49 {
50 i ++;
51 }
53 for (SPObject *child = o->first_child();
54 child != NULL; child = SP_OBJECT_NEXT(child)) {
55 i += count_filter_hrefs(child, filter);
56 }
58 return i;
59 }
61 /**
62 * Sets a suitable filter effects area according to given blur radius,
63 * expansion and object size.
64 */
65 static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius,
66 double expansion, double expansionX,
67 double expansionY, double width, double height)
68 {
69 // TODO: make this more generic, now assumed, that only the blur
70 // being added can affect the required filter area
72 double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1);
73 double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1);
75 if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) {
76 // If not within the default 10% margin (see
77 // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins
78 // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible
79 // (the opacity at 2.4*radius is about 3e-3)
80 double xmargin = 2.4 * (rx) / width;
81 double ymargin = 2.4 * (ry) / height;
83 // TODO: set it in UserSpaceOnUse instead?
84 sp_repr_set_svg_double(repr, "x", -xmargin);
85 sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin);
86 sp_repr_set_svg_double(repr, "y", -ymargin);
87 sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin);
88 }
89 }
91 SPFilter *new_filter(SPDocument *document)
92 {
93 g_return_val_if_fail(document != NULL, NULL);
95 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
97 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
99 // create a new filter
100 Inkscape::XML::Node *repr;
101 repr = xml_doc->createElement("svg:filter");
103 // Append the new filter node to defs
104 //SP_OBJECT_REPR(defs)->appendChild(repr);
105 defs->appendChild(repr);
106 Inkscape::GC::release(repr);
108 // get corresponding object
109 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
112 g_assert(f != NULL);
113 g_assert(SP_IS_FILTER(f));
115 return f;
116 }
118 SPFilterPrimitive *
119 filter_add_primitive(SPFilter *filter, const Inkscape::Filters::FilterPrimitiveType type)
120 {
121 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(filter->document);
123 //create filter primitive node
124 Inkscape::XML::Node *repr;
125 repr = xml_doc->createElement(FPConverter.get_key(type).c_str());
127 // set default values
128 switch(type) {
129 case Inkscape::Filters::NR_FILTER_BLEND:
130 repr->setAttribute("blend", "normal");
131 break;
132 case Inkscape::Filters::NR_FILTER_COLORMATRIX:
133 break;
134 case Inkscape::Filters::NR_FILTER_COMPONENTTRANSFER:
135 break;
136 case Inkscape::Filters::NR_FILTER_COMPOSITE:
137 break;
138 case Inkscape::Filters::NR_FILTER_CONVOLVEMATRIX:
139 repr->setAttribute("order", "3 3");
140 repr->setAttribute("kernelMatrix", "0 0 0 0 0 0 0 0 0");
141 break;
142 case Inkscape::Filters::NR_FILTER_DIFFUSELIGHTING:
143 break;
144 case Inkscape::Filters::NR_FILTER_DISPLACEMENTMAP:
145 break;
146 case Inkscape::Filters::NR_FILTER_FLOOD:
147 break;
148 case Inkscape::Filters::NR_FILTER_GAUSSIANBLUR:
149 repr->setAttribute("stdDeviation", "1");
150 break;
151 case Inkscape::Filters::NR_FILTER_IMAGE:
152 break;
153 case Inkscape::Filters::NR_FILTER_MERGE:
154 break;
155 case Inkscape::Filters::NR_FILTER_MORPHOLOGY:
156 break;
157 case Inkscape::Filters::NR_FILTER_OFFSET:
158 repr->setAttribute("dx", "0");
159 repr->setAttribute("dy", "0");
160 break;
161 case Inkscape::Filters::NR_FILTER_SPECULARLIGHTING:
162 break;
163 case Inkscape::Filters::NR_FILTER_TILE:
164 break;
165 case Inkscape::Filters::NR_FILTER_TURBULENCE:
166 break;
167 default:
168 break;
169 }
171 //set primitive as child of filter node
172 // XML tree being used directly while/where it shouldn't be...
173 //filter->getRepr()->appendChild(repr);
174 filter->appendChild(repr);
175 Inkscape::GC::release(repr);
177 // get corresponding object
178 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
180 g_assert(prim != NULL);
181 g_assert(SP_IS_FILTER_PRIMITIVE(prim));
183 return prim;
184 }
186 /**
187 * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height
188 */
189 SPFilter *
190 new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height)
191 {
192 g_return_val_if_fail(document != NULL, NULL);
194 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
196 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
198 // create a new filter
199 Inkscape::XML::Node *repr;
200 repr = xml_doc->createElement("svg:filter");
201 //repr->setAttribute("inkscape:collect", "always");
203 set_filter_area(repr, radius, expansion, expansionX, expansionY,
204 width, height);
206 //create feGaussianBlur node
207 Inkscape::XML::Node *b_repr;
208 b_repr = xml_doc->createElement("svg:feGaussianBlur");
209 //b_repr->setAttribute("inkscape:collect", "always");
211 double stdDeviation = radius;
212 if (expansion != 0)
213 stdDeviation /= expansion;
215 //set stdDeviation attribute
216 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
218 //set feGaussianBlur as child of filter node
219 repr->appendChild(b_repr);
220 Inkscape::GC::release(b_repr);
222 // Append the new filter node to defs
223 //SP_OBJECT_REPR(defs)->appendChild(repr);
224 defs->appendChild(repr);
225 Inkscape::GC::release(repr);
227 // get corresponding object
228 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
229 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
231 g_assert(f != NULL);
232 g_assert(SP_IS_FILTER(f));
233 g_assert(b != NULL);
234 g_assert(SP_IS_GAUSSIANBLUR(b));
236 return f;
237 }
240 /**
241 * Creates a simple filter with a blend primitive and a blur primitive of specified radius for
242 * an item with the given matrix expansion, width and height
243 */
244 SPFilter *
245 new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion,
246 double expansionX, double expansionY, double width, double height)
247 {
248 g_return_val_if_fail(document != NULL, NULL);
250 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
252 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
254 // create a new filter
255 Inkscape::XML::Node *repr;
256 repr = xml_doc->createElement("svg:filter");
257 repr->setAttribute("inkscape:collect", "always");
259 // Append the new filter node to defs
260 //SP_OBJECT_REPR(defs)->appendChild(repr);
261 defs->appendChild(repr);
262 Inkscape::GC::release(repr);
264 // get corresponding object
265 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
267 // Gaussian blur primitive
268 if(radius != 0) {
269 set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height);
271 //create feGaussianBlur node
272 Inkscape::XML::Node *b_repr;
273 b_repr = xml_doc->createElement("svg:feGaussianBlur");
274 b_repr->setAttribute("inkscape:collect", "always");
276 double stdDeviation = radius;
277 if (expansion != 0)
278 stdDeviation /= expansion;
280 //set stdDeviation attribute
281 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
283 //set feGaussianBlur as child of filter node
284 repr->appendChild(b_repr);
285 Inkscape::GC::release(b_repr);
287 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
288 g_assert(b != NULL);
289 g_assert(SP_IS_GAUSSIANBLUR(b));
290 }
291 // Blend primitive
292 if(strcmp(blendmode, "normal")) {
293 Inkscape::XML::Node *b_repr;
294 b_repr = xml_doc->createElement("svg:feBlend");
295 b_repr->setAttribute("inkscape:collect", "always");
296 b_repr->setAttribute("mode", blendmode);
297 b_repr->setAttribute("in2", "BackgroundImage");
299 // set feBlend as child of filter node
300 repr->appendChild(b_repr);
301 Inkscape::GC::release(b_repr);
303 // Enable background image buffer for document
304 Inkscape::XML::Node *root = b_repr->root();
305 if (!root->attribute("enable-background")) {
306 root->setAttribute("enable-background", "new");
307 }
309 SPFeBlend *b = SP_FEBLEND(document->getObjectByRepr(b_repr));
310 g_assert(b != NULL);
311 g_assert(SP_IS_FEBLEND(b));
312 }
314 g_assert(f != NULL);
315 g_assert(SP_IS_FILTER(f));
317 return f;
318 }
320 /**
321 * Creates a simple filter for the given item with blend and blur primitives, using the
322 * specified mode and radius, respectively
323 */
324 SPFilter *
325 new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius)
326 {
327 Geom::OptRect const r = item->getBboxDesktop(SPItem::GEOMETRIC_BBOX);
329 double width;
330 double height;
331 if (r) {
332 width = r->dimensions()[Geom::X];
333 height= r->dimensions()[Geom::Y];
334 } else {
335 width = height = 0;
336 }
338 Geom::Matrix i2d (item->i2d_affine () );
340 return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.descrim(), i2d.expansionX(), i2d.expansionY(), width, height));
341 }
343 /**
344 * Modifies the gaussian blur applied to the item.
345 * If no filters are applied to given item, creates a new blur filter.
346 * If a filter is applied and it contains a blur, modify that blur.
347 * If the filter doesn't contain blur, a blur is added to the filter.
348 * Should there be more references to modified filter, that filter is
349 * duplicated, so that other elements referring that filter are not modified.
350 */
351 /* TODO: this should be made more generic, not just for blurs */
352 SPFilter *
353 modify_filter_gaussian_blur_from_item(SPDocument *document, SPItem *item,
354 gdouble radius)
355 {
356 if (!item->style || !item->style->filter.set) {
357 return new_filter_simple_from_item(document, item, "normal", radius);
358 }
360 SPFilter *filter = SP_FILTER(item->style->getFilter());
361 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
363 // If there are more users for this filter, duplicate it
364 if (SP_OBJECT_HREFCOUNT(filter) > count_filter_hrefs(item, filter)) {
365 Inkscape::XML::Node *repr;
366 repr = SP_OBJECT_REPR(item->style->getFilter())->duplicate(xml_doc);
367 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
368 //SP_OBJECT_REPR(defs)->appendChild(repr);
369 defs->appendChild(repr);
371 filter = SP_FILTER( document->getObjectByRepr(repr) );
372 Inkscape::GC::release(repr);
373 }
375 // Determine the required standard deviation value
376 Geom::Matrix i2d (item->i2d_affine ());
377 double expansion = i2d.descrim();
378 double stdDeviation = radius;
379 if (expansion != 0)
380 stdDeviation /= expansion;
382 // Get the object size
383 Geom::OptRect const r = item->getBboxDesktop(SPItem::GEOMETRIC_BBOX);
384 double width;
385 double height;
386 if (r) {
387 width = r->dimensions()[Geom::X];
388 height= r->dimensions()[Geom::Y];
389 } else {
390 width = height = 0;
391 }
393 // Set the filter effects area
394 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
395 set_filter_area(repr, radius, expansion, i2d.expansionX(),
396 i2d.expansionY(), width, height);
398 // Search for gaussian blur primitives. If found, set the stdDeviation
399 // of the first one and return.
400 Inkscape::XML::Node *primitive = repr->firstChild();
401 while (primitive) {
402 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
403 sp_repr_set_svg_double(primitive, "stdDeviation",
404 stdDeviation);
405 return filter;
406 }
407 primitive = primitive->next();
408 }
410 // If there were no gaussian blur primitives, create a new one
412 //create feGaussianBlur node
413 Inkscape::XML::Node *b_repr;
414 b_repr = xml_doc->createElement("svg:feGaussianBlur");
415 //b_repr->setAttribute("inkscape:collect", "always");
417 //set stdDeviation attribute
418 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
420 //set feGaussianBlur as child of filter node
421 SP_OBJECT_REPR(filter)->appendChild(b_repr);
422 Inkscape::GC::release(b_repr);
424 return filter;
425 }
427 void remove_filter (SPObject *item, bool recursive)
428 {
429 SPCSSAttr *css = sp_repr_css_attr_new ();
430 sp_repr_css_unset_property (css, "filter");
431 if (recursive)
432 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
433 else
434 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
435 sp_repr_css_attr_unref (css);
436 }
438 /**
439 * Removes the first feGaussianBlur from the filter attached to given item.
440 * Should this leave us with an empty filter, remove that filter.
441 */
442 /* TODO: the removed filter primitive may had had a named result image, so
443 * after removing, the filter may be in erroneous state, this situation should
444 * be handled gracefully */
445 void remove_filter_gaussian_blur (SPObject *item)
446 {
447 if (item->style && item->style->filter.set && item->style->getFilter()) {
448 // Search for the first blur primitive and remove it. (if found)
449 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
450 Inkscape::XML::Node *primitive = repr->firstChild();
451 while (primitive) {
452 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
453 sp_repr_unparent(primitive);
454 break;
455 }
456 primitive = primitive->next();
457 }
459 // If there are no more primitives left in this filter, discard it.
460 if (repr->childCount() == 0) {
461 remove_filter(item, false);
462 }
463 }
464 }
466 bool filter_is_single_gaussian_blur(SPFilter *filter)
467 {
468 return (SP_OBJECT(filter)->firstChild() &&
469 SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() &&
470 SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild()));
471 }
473 double get_single_gaussian_blur_radius(SPFilter *filter)
474 {
475 if (SP_OBJECT(filter)->firstChild() &&
476 SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() &&
477 SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild())) {
479 SPGaussianBlur *gb = SP_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild());
480 double x = gb->stdDeviation.getNumber();
481 double y = gb->stdDeviation.getOptNumber();
482 if (x > 0 && y > 0) {
483 return MAX(x, y);
484 }
485 return x;
486 }
487 return 0.0;
488 }
492 /*
493 Local Variables:
494 mode:c++
495 c-file-style:"stroustrup"
496 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
497 indent-tabs-mode:nil
498 fill-column:99
499 End:
500 */
501 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :