1 /*
2 * Various utility methods for filters
3 *
4 * Authors:
5 * Hugo Rodrigues
6 * bulia byak
7 * Niko Kiirala
8 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
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 count_filter_hrefs(SPObject *o, SPFilter *filter)
38 {
39 if (!o)
40 return 1;
42 guint i = 0;
44 SPStyle *style = SP_OBJECT_STYLE(o);
45 if (style
46 && style->filter.set
47 && style->getFilter() == filter)
48 {
49 i ++;
50 }
52 for ( SPObject *child = o->firstChild(); child; child = child->getNext() ) {
53 i += count_filter_hrefs(child, filter);
54 }
56 return i;
57 }
59 /**
60 * Sets a suitable filter effects area according to given blur radius,
61 * expansion and object size.
62 */
63 static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius,
64 double expansion, double expansionX,
65 double expansionY, double width, double height)
66 {
67 // TODO: make this more generic, now assumed, that only the blur
68 // being added can affect the required filter area
70 double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1);
71 double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1);
73 if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) {
74 // If not within the default 10% margin (see
75 // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins
76 // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible
77 // (the opacity at 2.4*radius is about 3e-3)
78 double xmargin = 2.4 * (rx) / width;
79 double ymargin = 2.4 * (ry) / height;
81 // TODO: set it in UserSpaceOnUse instead?
82 sp_repr_set_svg_double(repr, "x", -xmargin);
83 sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin);
84 sp_repr_set_svg_double(repr, "y", -ymargin);
85 sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin);
86 }
87 }
89 SPFilter *new_filter(SPDocument *document)
90 {
91 g_return_val_if_fail(document != NULL, NULL);
93 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
95 Inkscape::XML::Document *xml_doc = document->getReprDoc();
97 // create a new filter
98 Inkscape::XML::Node *repr;
99 repr = xml_doc->createElement("svg:filter");
101 // Append the new filter node to defs
102 defs->appendChild(repr);
103 Inkscape::GC::release(repr);
105 // get corresponding object
106 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
109 g_assert(f != NULL);
110 g_assert(SP_IS_FILTER(f));
112 return f;
113 }
115 SPFilterPrimitive *
116 filter_add_primitive(SPFilter *filter, const Inkscape::Filters::FilterPrimitiveType type)
117 {
118 Inkscape::XML::Document *xml_doc = filter->document->getReprDoc();
120 //create filter primitive node
121 Inkscape::XML::Node *repr;
122 repr = xml_doc->createElement(FPConverter.get_key(type).c_str());
124 // set default values
125 switch(type) {
126 case Inkscape::Filters::NR_FILTER_BLEND:
127 repr->setAttribute("blend", "normal");
128 break;
129 case Inkscape::Filters::NR_FILTER_COLORMATRIX:
130 break;
131 case Inkscape::Filters::NR_FILTER_COMPONENTTRANSFER:
132 break;
133 case Inkscape::Filters::NR_FILTER_COMPOSITE:
134 break;
135 case Inkscape::Filters::NR_FILTER_CONVOLVEMATRIX:
136 repr->setAttribute("order", "3 3");
137 repr->setAttribute("kernelMatrix", "0 0 0 0 0 0 0 0 0");
138 break;
139 case Inkscape::Filters::NR_FILTER_DIFFUSELIGHTING:
140 break;
141 case Inkscape::Filters::NR_FILTER_DISPLACEMENTMAP:
142 break;
143 case Inkscape::Filters::NR_FILTER_FLOOD:
144 break;
145 case Inkscape::Filters::NR_FILTER_GAUSSIANBLUR:
146 repr->setAttribute("stdDeviation", "1");
147 break;
148 case Inkscape::Filters::NR_FILTER_IMAGE:
149 break;
150 case Inkscape::Filters::NR_FILTER_MERGE:
151 break;
152 case Inkscape::Filters::NR_FILTER_MORPHOLOGY:
153 break;
154 case Inkscape::Filters::NR_FILTER_OFFSET:
155 repr->setAttribute("dx", "0");
156 repr->setAttribute("dy", "0");
157 break;
158 case Inkscape::Filters::NR_FILTER_SPECULARLIGHTING:
159 break;
160 case Inkscape::Filters::NR_FILTER_TILE:
161 break;
162 case Inkscape::Filters::NR_FILTER_TURBULENCE:
163 break;
164 default:
165 break;
166 }
168 //set primitive as child of filter node
169 // XML tree being used directly while/where it shouldn't be...
170 filter->appendChild(repr);
171 Inkscape::GC::release(repr);
173 // get corresponding object
174 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
176 g_assert(prim != NULL);
177 g_assert(SP_IS_FILTER_PRIMITIVE(prim));
179 return prim;
180 }
182 /**
183 * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height
184 */
185 SPFilter *
186 new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height)
187 {
188 g_return_val_if_fail(document != NULL, NULL);
190 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
192 Inkscape::XML::Document *xml_doc = document->getReprDoc();
194 // create a new filter
195 Inkscape::XML::Node *repr;
196 repr = xml_doc->createElement("svg:filter");
197 //repr->setAttribute("inkscape:collect", "always");
199 set_filter_area(repr, radius, expansion, expansionX, expansionY,
200 width, height);
202 //create feGaussianBlur node
203 Inkscape::XML::Node *b_repr;
204 b_repr = xml_doc->createElement("svg:feGaussianBlur");
205 //b_repr->setAttribute("inkscape:collect", "always");
207 double stdDeviation = radius;
208 if (expansion != 0)
209 stdDeviation /= expansion;
211 //set stdDeviation attribute
212 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
214 //set feGaussianBlur as child of filter node
215 repr->appendChild(b_repr);
216 Inkscape::GC::release(b_repr);
218 // Append the new filter node to defs
219 defs->appendChild(repr);
220 Inkscape::GC::release(repr);
222 // get corresponding object
223 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
224 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
226 g_assert(f != NULL);
227 g_assert(SP_IS_FILTER(f));
228 g_assert(b != NULL);
229 g_assert(SP_IS_GAUSSIANBLUR(b));
231 return f;
232 }
235 /**
236 * Creates a simple filter with a blend primitive and a blur primitive of specified radius for
237 * an item with the given matrix expansion, width and height
238 */
239 SPFilter *
240 new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion,
241 double expansionX, double expansionY, double width, double height)
242 {
243 g_return_val_if_fail(document != NULL, NULL);
245 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
247 Inkscape::XML::Document *xml_doc = document->getReprDoc();
249 // create a new filter
250 Inkscape::XML::Node *repr;
251 repr = xml_doc->createElement("svg:filter");
252 repr->setAttribute("inkscape:collect", "always");
254 // Append the new filter node to defs
255 defs->appendChild(repr);
256 Inkscape::GC::release(repr);
258 // get corresponding object
259 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
261 // Gaussian blur primitive
262 if(radius != 0) {
263 set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height);
265 //create feGaussianBlur node
266 Inkscape::XML::Node *b_repr;
267 b_repr = xml_doc->createElement("svg:feGaussianBlur");
268 b_repr->setAttribute("inkscape:collect", "always");
270 double stdDeviation = radius;
271 if (expansion != 0)
272 stdDeviation /= expansion;
274 //set stdDeviation attribute
275 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
277 //set feGaussianBlur as child of filter node
278 repr->appendChild(b_repr);
279 Inkscape::GC::release(b_repr);
281 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
282 g_assert(b != NULL);
283 g_assert(SP_IS_GAUSSIANBLUR(b));
284 }
285 // Blend primitive
286 if(strcmp(blendmode, "normal")) {
287 Inkscape::XML::Node *b_repr;
288 b_repr = xml_doc->createElement("svg:feBlend");
289 b_repr->setAttribute("inkscape:collect", "always");
290 b_repr->setAttribute("mode", blendmode);
291 b_repr->setAttribute("in2", "BackgroundImage");
293 // set feBlend as child of filter node
294 repr->appendChild(b_repr);
295 Inkscape::GC::release(b_repr);
297 // Enable background image buffer for document
298 Inkscape::XML::Node *root = b_repr->root();
299 if (!root->attribute("enable-background")) {
300 root->setAttribute("enable-background", "new");
301 }
303 SPFeBlend *b = SP_FEBLEND(document->getObjectByRepr(b_repr));
304 g_assert(b != NULL);
305 g_assert(SP_IS_FEBLEND(b));
306 }
308 g_assert(f != NULL);
309 g_assert(SP_IS_FILTER(f));
311 return f;
312 }
314 /**
315 * Creates a simple filter for the given item with blend and blur primitives, using the
316 * specified mode and radius, respectively
317 */
318 SPFilter *
319 new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius)
320 {
321 Geom::OptRect const r = item->getBboxDesktop(SPItem::GEOMETRIC_BBOX);
323 double width;
324 double height;
325 if (r) {
326 width = r->dimensions()[Geom::X];
327 height= r->dimensions()[Geom::Y];
328 } else {
329 width = height = 0;
330 }
332 Geom::Matrix i2d (item->i2d_affine () );
334 return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.descrim(), i2d.expansionX(), i2d.expansionY(), width, height));
335 }
337 /**
338 * Modifies the gaussian blur applied to the item.
339 * If no filters are applied to given item, creates a new blur filter.
340 * If a filter is applied and it contains a blur, modify that blur.
341 * If the filter doesn't contain blur, a blur is added to the filter.
342 * Should there be more references to modified filter, that filter is
343 * duplicated, so that other elements referring that filter are not modified.
344 */
345 /* TODO: this should be made more generic, not just for blurs */
346 SPFilter *modify_filter_gaussian_blur_from_item(SPDocument *document, SPItem *item,
347 gdouble radius)
348 {
349 if (!item->style || !item->style->filter.set) {
350 return new_filter_simple_from_item(document, item, "normal", radius);
351 }
353 SPFilter *filter = SP_FILTER(item->style->getFilter());
354 Inkscape::XML::Document *xml_doc = document->getReprDoc();
356 // If there are more users for this filter, duplicate it
357 if (filter->hrefcount > count_filter_hrefs(item, filter)) {
358 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter())->duplicate(xml_doc);
359 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
360 defs->appendChild(repr);
362 filter = SP_FILTER( document->getObjectByRepr(repr) );
363 Inkscape::GC::release(repr);
364 }
366 // Determine the required standard deviation value
367 Geom::Matrix i2d (item->i2d_affine ());
368 double expansion = i2d.descrim();
369 double stdDeviation = radius;
370 if (expansion != 0)
371 stdDeviation /= expansion;
373 // Get the object size
374 Geom::OptRect const r = item->getBboxDesktop(SPItem::GEOMETRIC_BBOX);
375 double width;
376 double height;
377 if (r) {
378 width = r->dimensions()[Geom::X];
379 height= r->dimensions()[Geom::Y];
380 } else {
381 width = height = 0;
382 }
384 // Set the filter effects area
385 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
386 set_filter_area(repr, radius, expansion, i2d.expansionX(),
387 i2d.expansionY(), width, height);
389 // Search for gaussian blur primitives. If found, set the stdDeviation
390 // of the first one and return.
391 Inkscape::XML::Node *primitive = repr->firstChild();
392 while (primitive) {
393 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
394 sp_repr_set_svg_double(primitive, "stdDeviation",
395 stdDeviation);
396 return filter;
397 }
398 primitive = primitive->next();
399 }
401 // If there were no gaussian blur primitives, create a new one
403 //create feGaussianBlur node
404 Inkscape::XML::Node *b_repr;
405 b_repr = xml_doc->createElement("svg:feGaussianBlur");
406 //b_repr->setAttribute("inkscape:collect", "always");
408 //set stdDeviation attribute
409 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
411 //set feGaussianBlur as child of filter node
412 SP_OBJECT_REPR(filter)->appendChild(b_repr);
413 Inkscape::GC::release(b_repr);
415 return filter;
416 }
418 void remove_filter (SPObject *item, bool recursive)
419 {
420 SPCSSAttr *css = sp_repr_css_attr_new ();
421 sp_repr_css_unset_property (css, "filter");
422 if (recursive)
423 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
424 else
425 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
426 sp_repr_css_attr_unref (css);
427 }
429 /**
430 * Removes the first feGaussianBlur from the filter attached to given item.
431 * Should this leave us with an empty filter, remove that filter.
432 */
433 /* TODO: the removed filter primitive may had had a named result image, so
434 * after removing, the filter may be in erroneous state, this situation should
435 * be handled gracefully */
436 void remove_filter_gaussian_blur (SPObject *item)
437 {
438 if (item->style && item->style->filter.set && item->style->getFilter()) {
439 // Search for the first blur primitive and remove it. (if found)
440 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
441 Inkscape::XML::Node *primitive = repr->firstChild();
442 while (primitive) {
443 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
444 sp_repr_unparent(primitive);
445 break;
446 }
447 primitive = primitive->next();
448 }
450 // If there are no more primitives left in this filter, discard it.
451 if (repr->childCount() == 0) {
452 remove_filter(item, false);
453 }
454 }
455 }
457 bool filter_is_single_gaussian_blur(SPFilter *filter)
458 {
459 return (SP_OBJECT(filter)->firstChild() &&
460 SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() &&
461 SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild()));
462 }
464 double get_single_gaussian_blur_radius(SPFilter *filter)
465 {
466 if (SP_OBJECT(filter)->firstChild() &&
467 SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() &&
468 SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild())) {
470 SPGaussianBlur *gb = SP_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild());
471 double x = gb->stdDeviation.getNumber();
472 double y = gb->stdDeviation.getOptNumber();
473 if (x > 0 && y > 0) {
474 return MAX(x, y);
475 }
476 return x;
477 }
478 return 0.0;
479 }
483 /*
484 Local Variables:
485 mode:c++
486 c-file-style:"stroustrup"
487 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
488 indent-tabs-mode:nil
489 fill-column:99
490 End:
491 */
492 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :