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-gaussian-blur.h"
24 #include "svg/css-ostringstream.h"
26 #include "xml/repr.h"
28 /**
29 * Count how many times the filter is used by the styles of o and its
30 * descendants
31 */
32 static guint
33 count_filter_hrefs(SPObject *o, SPFilter *filter)
34 {
35 if (!o)
36 return 1;
38 guint i = 0;
40 SPStyle *style = SP_OBJECT_STYLE(o);
41 if (style
42 && style->filter.set
43 && style->filter.filter == filter)
44 {
45 i ++;
46 }
48 for (SPObject *child = sp_object_first_child(o);
49 child != NULL; child = SP_OBJECT_NEXT(child)) {
50 i += count_filter_hrefs(child, filter);
51 }
53 return i;
54 }
56 /**
57 * Sets a suitable filter effects area according to given blur radius,
58 * expansion and object size.
59 */
60 static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius,
61 double expansion, double expansionX,
62 double expansionY, double width, double height)
63 {
64 // TODO: make this more generic, now assumed, that only the blur
65 // being added can affect the required filter area
67 double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1);
68 double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1);
70 if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) {
71 // If not within the default 10% margin (see
72 // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins
73 // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible
74 // (the opacity at 2.4*radius is about 3e-3)
75 double xmargin = 2.4 * (rx) / width;
76 double ymargin = 2.4 * (ry) / height;
78 // TODO: set it in UserSpaceOnUse instead?
79 sp_repr_set_svg_double(repr, "x", -xmargin);
80 sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin);
81 sp_repr_set_svg_double(repr, "y", -ymargin);
82 sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin);
83 }
84 }
86 SPFilter *new_filter(SPDocument *document)
87 {
88 g_return_val_if_fail(document != NULL, NULL);
90 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
92 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
94 // create a new filter
95 Inkscape::XML::Node *repr;
96 repr = xml_doc->createElement("svg:filter");
98 // Append the new filter node to defs
99 SP_OBJECT_REPR(defs)->appendChild(repr);
100 Inkscape::GC::release(repr);
102 // get corresponding object
103 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
106 g_assert(f != NULL);
107 g_assert(SP_IS_FILTER(f));
109 return f;
110 }
112 SPFilterPrimitive *
113 filter_add_primitive(SPFilter *filter, const gchar *type)
114 {
115 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(filter->document);
117 //create filter primitive node
118 Inkscape::XML::Node *repr;
119 repr = xml_doc->createElement(type);
120 repr->setAttribute("inkscape:collect", "always");
122 //set primitive as child of filter node
123 filter->repr->appendChild(repr);
124 Inkscape::GC::release(repr);
126 // get corresponding object
127 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
129 g_assert(prim != NULL);
130 g_assert(SP_IS_FILTER_PRIMITIVE(prim));
132 return prim;
133 }
135 /**
136 * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height
137 */
138 SPFilter *
139 new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height)
140 {
141 g_return_val_if_fail(document != NULL, NULL);
143 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
145 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
147 // create a new filter
148 Inkscape::XML::Node *repr;
149 repr = xml_doc->createElement("svg:filter");
150 //repr->setAttribute("inkscape:collect", "always");
152 set_filter_area(repr, radius, expansion, expansionX, expansionY,
153 width, height);
155 //create feGaussianBlur node
156 Inkscape::XML::Node *b_repr;
157 b_repr = xml_doc->createElement("svg:feGaussianBlur");
158 //b_repr->setAttribute("inkscape:collect", "always");
160 double stdDeviation = radius;
161 if (expansion != 0)
162 stdDeviation /= expansion;
164 //set stdDeviation attribute
165 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
167 //set feGaussianBlur as child of filter node
168 repr->appendChild(b_repr);
169 Inkscape::GC::release(b_repr);
171 // Append the new filter node to defs
172 SP_OBJECT_REPR(defs)->appendChild(repr);
173 Inkscape::GC::release(repr);
175 // get corresponding object
176 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
177 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
179 g_assert(f != NULL);
180 g_assert(SP_IS_FILTER(f));
181 g_assert(b != NULL);
182 g_assert(SP_IS_GAUSSIANBLUR(b));
184 return f;
185 }
188 /**
189 * Creates a simple filter with a blend primitive and a blur primitive of specified radius for
190 * an item with the given matrix expansion, width and height
191 */
192 SPFilter *
193 new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion,
194 double expansionX, double expansionY, double width, double height)
195 {
196 g_return_val_if_fail(document != NULL, NULL);
198 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
200 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
202 // create a new filter
203 Inkscape::XML::Node *repr;
204 repr = xml_doc->createElement("svg:filter");
205 repr->setAttribute("inkscape:collect", "always");
207 // Append the new filter node to defs
208 SP_OBJECT_REPR(defs)->appendChild(repr);
209 Inkscape::GC::release(repr);
211 // get corresponding object
212 SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
214 // Gaussian blur primitive
215 if(radius != 0) {
216 set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height);
218 //create feGaussianBlur node
219 Inkscape::XML::Node *b_repr;
220 b_repr = xml_doc->createElement("svg:feGaussianBlur");
221 b_repr->setAttribute("inkscape:collect", "always");
223 double stdDeviation = radius;
224 if (expansion != 0)
225 stdDeviation /= expansion;
227 //set stdDeviation attribute
228 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
230 //set feGaussianBlur as child of filter node
231 repr->appendChild(b_repr);
232 Inkscape::GC::release(b_repr);
234 SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
235 g_assert(b != NULL);
236 g_assert(SP_IS_GAUSSIANBLUR(b));
237 }
238 // Blend primitive
239 if(strcmp(blendmode, "normal")) {
240 Inkscape::XML::Node *b_repr;
241 b_repr = xml_doc->createElement("svg:feBlend");
242 b_repr->setAttribute("inkscape:collect", "always");
243 b_repr->setAttribute("mode", blendmode);
244 b_repr->setAttribute("in2", "BackgroundImage");
246 // set feBlend as child of filter node
247 repr->appendChild(b_repr);
248 Inkscape::GC::release(b_repr);
250 // Enable background image buffer for document
251 Inkscape::XML::Node *root = b_repr->root();
252 if (!root->attribute("enable-background")) {
253 root->setAttribute("enable-background", "new");
254 }
256 SPFeBlend *b = SP_FEBLEND(document->getObjectByRepr(b_repr));
257 g_assert(b != NULL);
258 g_assert(SP_IS_FEBLEND(b));
259 }
261 g_assert(f != NULL);
262 g_assert(SP_IS_FILTER(f));
264 return f;
265 }
267 /**
268 * Creates a simple filter for the given item with blend and blur primitives, using the
269 * specified mode and radius, respectively
270 */
271 SPFilter *
272 new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius)
273 {
274 NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
276 double width;
277 double height;
278 if (r) {
279 width = r->extent(NR::X);
280 height= r->extent(NR::Y);
281 } else {
282 width = height = 0;
283 }
285 NR::Matrix i2d = sp_item_i2d_affine (item);
287 return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.expansion(), i2d.expansionX(), i2d.expansionY(), width, height));
288 }
290 /**
291 * Modifies the gaussian blur applied to the item.
292 * If no filters are applied to given item, creates a new blur filter.
293 * If a filter is applied and it contains a blur, modify that blur.
294 * If the filter doesn't contain blur, a blur is added to the filter.
295 * Should there be more references to modified filter, that filter is
296 * duplicated, so that other elements referring that filter are not modified.
297 */
298 /* TODO: this should be made more generic, not just for blurs */
299 SPFilter *
300 modify_filter_gaussian_blur_from_item(SPDocument *document, SPItem *item,
301 gdouble radius)
302 {
303 if (!item->style || !item->style->filter.set) {
304 //return new_filter_gaussian_blur_from_item(document, item, radius);
305 }
307 SPFilter *filter = SP_FILTER(item->style->filter.filter);
308 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
310 // If there are more users for this filter, duplicate it
311 if (SP_OBJECT_HREFCOUNT(filter) > count_filter_hrefs(item, filter)) {
312 Inkscape::XML::Node *repr;
313 repr = SP_OBJECT_REPR(item->style->filter.filter)->duplicate(xml_doc);
314 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
315 SP_OBJECT_REPR(defs)->appendChild(repr);
317 filter = SP_FILTER( document->getObjectByRepr(repr) );
318 Inkscape::GC::release(repr);
319 }
321 // Determine the required standard deviation value
322 NR::Matrix i2d = sp_item_i2d_affine (item);
323 double expansion = i2d.expansion();
324 double stdDeviation = radius;
325 if (expansion != 0)
326 stdDeviation /= expansion;
328 // Get the object size
329 NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
330 double width;
331 double height;
332 if (r) {
333 width = r->extent(NR::X);
334 height= r->extent(NR::Y);
335 } else {
336 width = height = 0;
337 }
339 // Set the filter effects area
340 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->filter.filter);
341 set_filter_area(repr, radius, expansion, i2d.expansionX(),
342 i2d.expansionY(), width, height);
344 // Search for gaussian blur primitives. If found, set the stdDeviation
345 // of the first one and return.
346 Inkscape::XML::Node *primitive = repr->firstChild();
347 while (primitive) {
348 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
349 sp_repr_set_svg_double(primitive, "stdDeviation",
350 stdDeviation);
351 return filter;
352 }
353 primitive = primitive->next();
354 }
356 // If there were no gaussian blur primitives, create a new one
358 //create feGaussianBlur node
359 Inkscape::XML::Node *b_repr;
360 b_repr = xml_doc->createElement("svg:feGaussianBlur");
361 //b_repr->setAttribute("inkscape:collect", "always");
363 //set stdDeviation attribute
364 sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
366 //set feGaussianBlur as child of filter node
367 SP_OBJECT_REPR(filter)->appendChild(b_repr);
368 Inkscape::GC::release(b_repr);
370 return filter;
371 }
373 void remove_filter (SPObject *item, bool recursive)
374 {
375 SPCSSAttr *css = sp_repr_css_attr_new ();
376 sp_repr_css_unset_property (css, "filter");
377 if (recursive)
378 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
379 else
380 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
381 sp_repr_css_attr_unref (css);
382 }
384 /**
385 * Removes the first feGaussianBlur from the filter attached to given item.
386 * Should this leave us with an empty filter, remove that filter.
387 */
388 /* TODO: the removed filter primitive may had had a named result image, so
389 * after removing, the filter may be in erroneous state, this situation should
390 * be handled gracefully */
391 void remove_filter_gaussian_blur (SPObject *item)
392 {
393 if (item->style && item->style->filter.set && item->style->filter.filter) {
394 // Search for the first blur primitive and remove it. (if found)
395 Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->filter.filter);
396 Inkscape::XML::Node *primitive = repr->firstChild();
397 while (primitive) {
398 if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
399 sp_repr_unparent(primitive);
400 break;
401 }
402 primitive = primitive->next();
403 }
405 // If there are no more primitives left in this filter, discard it.
406 if (repr->childCount() == 0) {
407 remove_filter(item, false);
408 }
409 }
410 }
412 /*
413 Local Variables:
414 mode:c++
415 c-file-style:"stroustrup"
416 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
417 indent-tabs-mode:nil
418 fill-column:99
419 End:
420 */
421 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :