Code

more unreffing temporary styles properly
[inkscape.git] / src / filter-chemistry.cpp
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) );
104     
105     
106     g_assert(f != NULL);
107     g_assert(SP_IS_FILTER(f));
109     return f;
112 SPFilterPrimitive *
113 filter_add_primitive(SPFilter *filter, const gchar *type)
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);
125     
126     // get corresponding object
127     SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
128  
129     g_assert(prim != NULL);
130     g_assert(SP_IS_FILTER_PRIMITIVE(prim));
132     return prim;
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)
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");
159     
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);
166     
167     //set feGaussianBlur as child of filter node
168     repr->appendChild(b_repr);
169     Inkscape::GC::release(b_repr);
170     
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) );
178     
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;
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)
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);
210  
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");
222         
223         double stdDeviation = radius;
224         if (expansion != 0)
225             stdDeviation /= expansion;
226         
227         //set stdDeviation attribute
228         sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
229      
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     }
260     
261     g_assert(f != NULL);
262     g_assert(SP_IS_FILTER(f));
263  
264     return f;
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)
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));
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)
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");
362     
363     //set stdDeviation attribute
364     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
365     
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;
373 void remove_filter (SPObject *item, bool recursive)
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);
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)
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     }
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 :