e59036362bdff71760bbcce65d09e65af7e97b02
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 "sp-feblend.h"
22 #include "sp-filter.h"
23 #include "sp-filter-reference.h"
24 #include "sp-gaussian-blur.h"
25 #include "svg/css-ostringstream.h"
27 #include "xml/repr.h"
29 /**
30 * Count how many times the filter is used by the styles of o and its
31 * descendants
32 */
33 static guint
34 count_filter_hrefs(SPObject *o, SPFilter *filter)
35 {
36 if (!o)
37 return 1;
39 guint i = 0;
41 SPStyle *style = SP_OBJECT_STYLE(o);
42 if (style
43 && style->filter.set
44 && style->getFilter() == filter)
45 {
46 i ++;
47 }
49 for (SPObject *child = sp_object_first_child(o);
50 child != NULL; child = SP_OBJECT_NEXT(child)) {
51 i += count_filter_hrefs(child, filter);
52 }
54 return i;
55 }
57 /**
58 * Sets a suitable filter effects area according to given blur radius,
59 * expansion and object size.
60 */
61 static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius,
62 double expansion, double expansionX,
63 double expansionY, double width, double height)
64 {
65 // TODO: make this more generic, now assumed, that only the blur
66 // being added can affect the required filter area
68 double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1);
69 double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1);
71 if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) {
72 // If not within the default 10% margin (see
73 // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins
74 // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible
75 // (the opacity at 2.4*radius is about 3e-3)
76 double xmargin = 2.4 * (rx) / width;
77 double ymargin = 2.4 * (ry) / height;
79 // TODO: set it in UserSpaceOnUse instead?
80 sp_repr_set_svg_double(repr, "x", -xmargin);
81 sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin);
82 sp_repr_set_svg_double(repr, "y", -ymargin);
83 sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin);
84 }
85 }
87 SPFilter *new_filter(SPDocument *document)
88 {
89 g_return_val_if_fail(document != NULL, NULL);
91 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
93 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
95 // create a new filter
96 Inkscape::XML::Node *repr;
97 repr = xml_doc->createElement("svg:filter");
99 // Append the new filter node to defs
100 SP_OBJECT_REPR(defs)->appendChild(repr);
101 Inkscape::GC::release(repr);
103 // get corresponding object
104 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
107 g_assert(f != NULL);
108 g_assert(SP_IS_FILTER(f));
110 return f;
111 }
113 SPFilterPrimitive *
114 filter_add_primitive(SPFilter *filter, const gchar *type)
115 {
116 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(filter->document);
118 //create filter primitive node
119 Inkscape::XML::Node *repr;
120 repr = xml_doc->createElement(type);
121 repr->setAttribute("inkscape:collect", "always");
123 //set primitive as child of filter node
124 filter->repr->appendChild(repr);
125 Inkscape::GC::release(repr);
127 // get corresponding object
128 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
130 g_assert(prim != NULL);
131 g_assert(SP_IS_FILTER_PRIMITIVE(prim));
133 return prim;
134 }
136 /**
137 * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height
138 */
139 SPFilter *
140 new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height)
141 {
142 g_return_val_if_fail(document != NULL, NULL);
144 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
146 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
148 // create a new filter
149 Inkscape::XML::Node *repr;
150 repr = xml_doc->createElement("svg:filter");
151 //repr->setAttribute("inkscape:collect", "always");
153 set_filter_area(repr, radius, expansion, expansionX, expansionY,
154 width, height);
156 //create feGaussianBlur node
157 Inkscape::XML::Node *b_repr;
158 b_repr = xml_doc->createElement("svg:feGaussianBlur");
159 //b_repr->setAttribute("inkscape:collect", "always");
161 double stdDeviation = radius;
162 if (expansion != 0)
163 stdDeviation /= expansion;
165 //set stdDeviation attribute
166 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
168 //set feGaussianBlur as child of filter node
169 repr->appendChild(b_repr);
170 Inkscape::GC::release(b_repr);
172 // Append the new filter node to defs
173 SP_OBJECT_REPR(defs)->appendChild(repr);
174 Inkscape::GC::release(repr);
176 // get corresponding object
177 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
178 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
180 g_assert(f != NULL);
181 g_assert(SP_IS_FILTER(f));
182 g_assert(b != NULL);
183 g_assert(SP_IS_GAUSSIANBLUR(b));
185 return f;
186 }
189 /**
190 * Creates a simple filter with a blend primitive and a blur primitive of specified radius for
191 * an item with the given matrix expansion, width and height
192 */
193 SPFilter *
194 new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion,
195 double expansionX, double expansionY, double width, double height)
196 {
197 g_return_val_if_fail(document != NULL, NULL);
199 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
201 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
203 // create a new filter
204 Inkscape::XML::Node *repr;
205 repr = xml_doc->createElement("svg:filter");
206 repr->setAttribute("inkscape:collect", "always");
208 // Append the new filter node to defs
209 SP_OBJECT_REPR(defs)->appendChild(repr);
210 Inkscape::GC::release(repr);
212 // get corresponding object
213 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
215 // Gaussian blur primitive
216 if(radius != 0) {
217 set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height);
219 //create feGaussianBlur node
220 Inkscape::XML::Node *b_repr;
221 b_repr = xml_doc->createElement("svg:feGaussianBlur");
222 b_repr->setAttribute("inkscape:collect", "always");
224 double stdDeviation = radius;
225 if (expansion != 0)
226 stdDeviation /= expansion;
228 //set stdDeviation attribute
229 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
231 //set feGaussianBlur as child of filter node
232 repr->appendChild(b_repr);
233 Inkscape::GC::release(b_repr);
235 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
236 g_assert(b != NULL);
237 g_assert(SP_IS_GAUSSIANBLUR(b));
238 }
239 // Blend primitive
240 if(strcmp(blendmode, "normal")) {
241 Inkscape::XML::Node *b_repr;
242 b_repr = xml_doc->createElement("svg:feBlend");
243 b_repr->setAttribute("inkscape:collect", "always");
244 b_repr->setAttribute("mode", blendmode);
245 b_repr->setAttribute("in2", "BackgroundImage");
247 // set feBlend as child of filter node
248 repr->appendChild(b_repr);
249 Inkscape::GC::release(b_repr);
251 // Enable background image buffer for document
252 Inkscape::XML::Node *root = b_repr->root();
253 if (!root->attribute("enable-background")) {
254 root->setAttribute("enable-background", "new");
255 }
257 SPFeBlend *b = SP_FEBLEND(document->getObjectByRepr(b_repr));
258 g_assert(b != NULL);
259 g_assert(SP_IS_FEBLEND(b));
260 }
262 g_assert(f != NULL);
263 g_assert(SP_IS_FILTER(f));
265 return f;
266 }
268 /**
269 * Creates a simple filter for the given item with blend and blur primitives, using the
270 * specified mode and radius, respectively
271 */
272 SPFilter *
273 new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius)
274 {
275 NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
277 double width;
278 double height;
279 if (r) {
280 width = r->extent(NR::X);
281 height= r->extent(NR::Y);
282 } else {
283 width = height = 0;
284 }
286 NR::Matrix i2d = sp_item_i2d_affine (item);
288 return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.expansion(), i2d.expansionX(), i2d.expansionY(), width, height));
289 }
291 /**
292 * Modifies the gaussian blur applied to the item.
293 * If no filters are applied to given item, creates a new blur filter.
294 * If a filter is applied and it contains a blur, modify that blur.
295 * If the filter doesn't contain blur, a blur is added to the filter.
296 * Should there be more references to modified filter, that filter is
297 * duplicated, so that other elements referring that filter are not modified.
298 */
299 /* TODO: this should be made more generic, not just for blurs */
300 SPFilter *
301 modify_filter_gaussian_blur_from_item(SPDocument *document, SPItem *item,
302 gdouble radius)
303 {
304 if (!item->style || !item->style->filter.set) {
305 //return new_filter_gaussian_blur_from_item(document, item, radius);
306 }
308 SPFilter *filter = SP_FILTER(item->style->getFilter());
309 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
311 // If there are more users for this filter, duplicate it
312 if (SP_OBJECT_HREFCOUNT(filter) > count_filter_hrefs(item, filter)) {
313 Inkscape::XML::Node *repr;
314 repr = SP_OBJECT_REPR(item->style->getFilter())->duplicate(xml_doc);
315 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
316 SP_OBJECT_REPR(defs)->appendChild(repr);
318 filter = SP_FILTER( document->getObjectByRepr(repr) );
319 Inkscape::GC::release(repr);
320 }
322 // Determine the required standard deviation value
323 NR::Matrix i2d = sp_item_i2d_affine (item);
324 double expansion = i2d.expansion();
325 double stdDeviation = radius;
326 if (expansion != 0)
327 stdDeviation /= expansion;
329 // Get the object size
330 NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
331 double width;
332 double height;
333 if (r) {
334 width = r->extent(NR::X);
335 height= r->extent(NR::Y);
336 } else {
337 width = height = 0;
338 }
340 // Set the filter effects area
341 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
342 set_filter_area(repr, radius, expansion, i2d.expansionX(),
343 i2d.expansionY(), width, height);
345 // Search for gaussian blur primitives. If found, set the stdDeviation
346 // of the first one and return.
347 Inkscape::XML::Node *primitive = repr->firstChild();
348 while (primitive) {
349 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
350 sp_repr_set_svg_double(primitive, "stdDeviation",
351 stdDeviation);
352 return filter;
353 }
354 primitive = primitive->next();
355 }
357 // If there were no gaussian blur primitives, create a new one
359 //create feGaussianBlur node
360 Inkscape::XML::Node *b_repr;
361 b_repr = xml_doc->createElement("svg:feGaussianBlur");
362 //b_repr->setAttribute("inkscape:collect", "always");
364 //set stdDeviation attribute
365 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
367 //set feGaussianBlur as child of filter node
368 SP_OBJECT_REPR(filter)->appendChild(b_repr);
369 Inkscape::GC::release(b_repr);
371 return filter;
372 }
374 void remove_filter (SPObject *item, bool recursive)
375 {
376 SPCSSAttr *css = sp_repr_css_attr_new ();
377 sp_repr_css_unset_property (css, "filter");
378 if (recursive)
379 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
380 else
381 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
382 sp_repr_css_attr_unref (css);
383 }
385 /**
386 * Removes the first feGaussianBlur from the filter attached to given item.
387 * Should this leave us with an empty filter, remove that filter.
388 */
389 /* TODO: the removed filter primitive may had had a named result image, so
390 * after removing, the filter may be in erroneous state, this situation should
391 * be handled gracefully */
392 void remove_filter_gaussian_blur (SPObject *item)
393 {
394 if (item->style && item->style->filter.set && item->style->getFilter()) {
395 // Search for the first blur primitive and remove it. (if found)
396 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
397 Inkscape::XML::Node *primitive = repr->firstChild();
398 while (primitive) {
399 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
400 sp_repr_unparent(primitive);
401 break;
402 }
403 primitive = primitive->next();
404 }
406 // If there are no more primitives left in this filter, discard it.
407 if (repr->childCount() == 0) {
408 remove_filter(item, false);
409 }
410 }
411 }
413 /*
414 Local Variables:
415 mode:c++
416 c-file-style:"stroustrup"
417 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
418 indent-tabs-mode:nil
419 fill-column:99
420 End:
421 */
422 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :