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,2007 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 "sp-feblend.h"
25 #include "sp-filter.h"
26 #include "sp-filter-reference.h"
27 #include "sp-gaussian-blur.h"
28 #include "svg/css-ostringstream.h"
30 #include "xml/repr.h"
32 /**
33 * Count how many times the filter is used by the styles of o and its
34 * descendants
35 */
36 static guint
37 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 = sp_object_first_child(o);
53 child != NULL; child = SP_OBJECT_NEXT(child)) {
54 i += count_filter_hrefs(child, filter);
55 }
57 return i;
58 }
60 /**
61 * Sets a suitable filter effects area according to given blur radius,
62 * expansion and object size.
63 */
64 static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius,
65 double expansion, double expansionX,
66 double expansionY, double width, double height)
67 {
68 // TODO: make this more generic, now assumed, that only the blur
69 // being added can affect the required filter area
71 double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1);
72 double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1);
74 if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) {
75 // If not within the default 10% margin (see
76 // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins
77 // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible
78 // (the opacity at 2.4*radius is about 3e-3)
79 double xmargin = 2.4 * (rx) / width;
80 double ymargin = 2.4 * (ry) / height;
82 // TODO: set it in UserSpaceOnUse instead?
83 sp_repr_set_svg_double(repr, "x", -xmargin);
84 sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin);
85 sp_repr_set_svg_double(repr, "y", -ymargin);
86 sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin);
87 }
88 }
90 SPFilter *new_filter(SPDocument *document)
91 {
92 g_return_val_if_fail(document != NULL, NULL);
94 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
96 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
98 // create a new filter
99 Inkscape::XML::Node *repr;
100 repr = xml_doc->createElement("svg:filter");
102 // Append the new filter node to defs
103 SP_OBJECT_REPR(defs)->appendChild(repr);
104 Inkscape::GC::release(repr);
106 // get corresponding object
107 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
110 g_assert(f != NULL);
111 g_assert(SP_IS_FILTER(f));
113 return f;
114 }
116 SPFilterPrimitive *
117 filter_add_primitive(SPFilter *filter, const NR::FilterPrimitiveType type)
118 {
119 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(filter->document);
121 //create filter primitive node
122 Inkscape::XML::Node *repr;
123 repr = xml_doc->createElement(FPConverter.get_key(type).c_str());
124 repr->setAttribute("inkscape:collect", "always");
126 // set default values
127 switch(type) {
128 case NR::NR_FILTER_BLEND:
129 repr->setAttribute("blend", "normal");
130 break;
131 case NR::NR_FILTER_COLORMATRIX:
132 break;
133 case NR::NR_FILTER_COMPONENTTRANSFER:
134 break;
135 case NR::NR_FILTER_COMPOSITE:
136 break;
137 case NR::NR_FILTER_CONVOLVEMATRIX:
138 break;
139 case NR::NR_FILTER_DIFFUSELIGHTING:
140 break;
141 case NR::NR_FILTER_DISPLACEMENTMAP:
142 break;
143 case NR::NR_FILTER_FLOOD:
144 break;
145 case NR::NR_FILTER_GAUSSIANBLUR:
146 repr->setAttribute("stddeviation", "1");
147 break;
148 case NR::NR_FILTER_IMAGE:
149 break;
150 case NR::NR_FILTER_MERGE:
151 break;
152 case NR::NR_FILTER_MORPHOLOGY:
153 break;
154 case NR::NR_FILTER_OFFSET:
155 repr->setAttribute("dx", "0");
156 repr->setAttribute("dy", "0");
157 break;
158 case NR::NR_FILTER_SPECULARLIGHTING:
159 break;
160 case NR::NR_FILTER_TILE:
161 break;
162 case NR::NR_FILTER_TURBULENCE:
163 break;
164 default:
165 break;
166 }
168 //set primitive as child of filter node
169 filter->repr->appendChild(repr);
170 Inkscape::GC::release(repr);
172 // get corresponding object
173 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
175 g_assert(prim != NULL);
176 g_assert(SP_IS_FILTER_PRIMITIVE(prim));
178 return prim;
179 }
181 /**
182 * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height
183 */
184 SPFilter *
185 new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height)
186 {
187 g_return_val_if_fail(document != NULL, NULL);
189 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
191 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
193 // create a new filter
194 Inkscape::XML::Node *repr;
195 repr = xml_doc->createElement("svg:filter");
196 //repr->setAttribute("inkscape:collect", "always");
198 set_filter_area(repr, radius, expansion, expansionX, expansionY,
199 width, height);
201 //create feGaussianBlur node
202 Inkscape::XML::Node *b_repr;
203 b_repr = xml_doc->createElement("svg:feGaussianBlur");
204 //b_repr->setAttribute("inkscape:collect", "always");
206 double stdDeviation = radius;
207 if (expansion != 0)
208 stdDeviation /= expansion;
210 //set stdDeviation attribute
211 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
213 //set feGaussianBlur as child of filter node
214 repr->appendChild(b_repr);
215 Inkscape::GC::release(b_repr);
217 // Append the new filter node to defs
218 SP_OBJECT_REPR(defs)->appendChild(repr);
219 Inkscape::GC::release(repr);
221 // get corresponding object
222 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
223 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
225 g_assert(f != NULL);
226 g_assert(SP_IS_FILTER(f));
227 g_assert(b != NULL);
228 g_assert(SP_IS_GAUSSIANBLUR(b));
230 return f;
231 }
234 /**
235 * Creates a simple filter with a blend primitive and a blur primitive of specified radius for
236 * an item with the given matrix expansion, width and height
237 */
238 SPFilter *
239 new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion,
240 double expansionX, double expansionY, double width, double height)
241 {
242 g_return_val_if_fail(document != NULL, NULL);
244 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
246 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
248 // create a new filter
249 Inkscape::XML::Node *repr;
250 repr = xml_doc->createElement("svg:filter");
251 repr->setAttribute("inkscape:collect", "always");
253 // Append the new filter node to defs
254 SP_OBJECT_REPR(defs)->appendChild(repr);
255 Inkscape::GC::release(repr);
257 // get corresponding object
258 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
260 // Gaussian blur primitive
261 if(radius != 0) {
262 set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height);
264 //create feGaussianBlur node
265 Inkscape::XML::Node *b_repr;
266 b_repr = xml_doc->createElement("svg:feGaussianBlur");
267 b_repr->setAttribute("inkscape:collect", "always");
269 double stdDeviation = radius;
270 if (expansion != 0)
271 stdDeviation /= expansion;
273 //set stdDeviation attribute
274 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
276 //set feGaussianBlur as child of filter node
277 repr->appendChild(b_repr);
278 Inkscape::GC::release(b_repr);
280 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
281 g_assert(b != NULL);
282 g_assert(SP_IS_GAUSSIANBLUR(b));
283 }
284 // Blend primitive
285 if(strcmp(blendmode, "normal")) {
286 Inkscape::XML::Node *b_repr;
287 b_repr = xml_doc->createElement("svg:feBlend");
288 b_repr->setAttribute("inkscape:collect", "always");
289 b_repr->setAttribute("mode", blendmode);
290 b_repr->setAttribute("in2", "BackgroundImage");
292 // set feBlend as child of filter node
293 repr->appendChild(b_repr);
294 Inkscape::GC::release(b_repr);
296 // Enable background image buffer for document
297 Inkscape::XML::Node *root = b_repr->root();
298 if (!root->attribute("enable-background")) {
299 root->setAttribute("enable-background", "new");
300 }
302 SPFeBlend *b = SP_FEBLEND(document->getObjectByRepr(b_repr));
303 g_assert(b != NULL);
304 g_assert(SP_IS_FEBLEND(b));
305 }
307 g_assert(f != NULL);
308 g_assert(SP_IS_FILTER(f));
310 return f;
311 }
313 /**
314 * Creates a simple filter for the given item with blend and blur primitives, using the
315 * specified mode and radius, respectively
316 */
317 SPFilter *
318 new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius)
319 {
320 NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
322 double width;
323 double height;
324 if (r) {
325 width = r->extent(NR::X);
326 height= r->extent(NR::Y);
327 } else {
328 width = height = 0;
329 }
331 NR::Matrix i2d = sp_item_i2d_affine (item);
333 return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.expansion(), i2d.expansionX(), i2d.expansionY(), width, height));
334 }
336 /**
337 * Modifies the gaussian blur applied to the item.
338 * If no filters are applied to given item, creates a new blur filter.
339 * If a filter is applied and it contains a blur, modify that blur.
340 * If the filter doesn't contain blur, a blur is added to the filter.
341 * Should there be more references to modified filter, that filter is
342 * duplicated, so that other elements referring that filter are not modified.
343 */
344 /* TODO: this should be made more generic, not just for blurs */
345 SPFilter *
346 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_gaussian_blur_from_item(document, item, radius);
351 }
353 SPFilter *filter = SP_FILTER(item->style->getFilter());
354 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
356 // If there are more users for this filter, duplicate it
357 if (SP_OBJECT_HREFCOUNT(filter) > count_filter_hrefs(item, filter)) {
358 Inkscape::XML::Node *repr;
359 repr = SP_OBJECT_REPR(item->style->getFilter())->duplicate(xml_doc);
360 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
361 SP_OBJECT_REPR(defs)->appendChild(repr);
363 filter = SP_FILTER( document->getObjectByRepr(repr) );
364 Inkscape::GC::release(repr);
365 }
367 // Determine the required standard deviation value
368 NR::Matrix i2d = sp_item_i2d_affine (item);
369 double expansion = i2d.expansion();
370 double stdDeviation = radius;
371 if (expansion != 0)
372 stdDeviation /= expansion;
374 // Get the object size
375 NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
376 double width;
377 double height;
378 if (r) {
379 width = r->extent(NR::X);
380 height= r->extent(NR::Y);
381 } else {
382 width = height = 0;
383 }
385 // Set the filter effects area
386 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
387 set_filter_area(repr, radius, expansion, i2d.expansionX(),
388 i2d.expansionY(), width, height);
390 // Search for gaussian blur primitives. If found, set the stdDeviation
391 // of the first one and return.
392 Inkscape::XML::Node *primitive = repr->firstChild();
393 while (primitive) {
394 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
395 sp_repr_set_svg_double(primitive, "stdDeviation",
396 stdDeviation);
397 return filter;
398 }
399 primitive = primitive->next();
400 }
402 // If there were no gaussian blur primitives, create a new one
404 //create feGaussianBlur node
405 Inkscape::XML::Node *b_repr;
406 b_repr = xml_doc->createElement("svg:feGaussianBlur");
407 //b_repr->setAttribute("inkscape:collect", "always");
409 //set stdDeviation attribute
410 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
412 //set feGaussianBlur as child of filter node
413 SP_OBJECT_REPR(filter)->appendChild(b_repr);
414 Inkscape::GC::release(b_repr);
416 return filter;
417 }
419 void remove_filter (SPObject *item, bool recursive)
420 {
421 SPCSSAttr *css = sp_repr_css_attr_new ();
422 sp_repr_css_unset_property (css, "filter");
423 if (recursive)
424 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
425 else
426 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
427 sp_repr_css_attr_unref (css);
428 }
430 /**
431 * Removes the first feGaussianBlur from the filter attached to given item.
432 * Should this leave us with an empty filter, remove that filter.
433 */
434 /* TODO: the removed filter primitive may had had a named result image, so
435 * after removing, the filter may be in erroneous state, this situation should
436 * be handled gracefully */
437 void remove_filter_gaussian_blur (SPObject *item)
438 {
439 if (item->style && item->style->filter.set && item->style->getFilter()) {
440 // Search for the first blur primitive and remove it. (if found)
441 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
442 Inkscape::XML::Node *primitive = repr->firstChild();
443 while (primitive) {
444 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
445 sp_repr_unparent(primitive);
446 break;
447 }
448 primitive = primitive->next();
449 }
451 // If there are no more primitives left in this filter, discard it.
452 if (repr->childCount() == 0) {
453 remove_filter(item, false);
454 }
455 }
456 }
458 /*
459 Local Variables:
460 mode:c++
461 c-file-style:"stroustrup"
462 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
463 indent-tabs-mode:nil
464 fill-column:99
465 End:
466 */
467 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :