Code

Using the blur slider no longer destroys filters already applied to the object
[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-filter.h"
22 #include "sp-gaussian-blur.h"
23 #include "svg/css-ostringstream.h"
25 #include "xml/repr.h"
27 /**
28  * Count how many times the filter is used by the styles of o and its
29  * descendants
30  */
31 static guint
32 count_filter_hrefs(SPObject *o, SPFilter *filter)
33 {
34     if (!o)
35         return 1;
37     guint i = 0;
39     SPStyle *style = SP_OBJECT_STYLE(o);
40     if (style
41         && style->filter.set
42         && style->filter.filter == filter)
43     {
44         i ++;
45     }
47     for (SPObject *child = sp_object_first_child(o);
48          child != NULL; child = SP_OBJECT_NEXT(child)) {
49         i += count_filter_hrefs(child, filter);
50     }
52     return i;
53 }
55 /**
56  * Sets a suitable filter effects area according to given blur radius,
57  * expansion and object size.
58  */
59 static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius,
60                             double expansion, double expansionX,
61                             double expansionY, double width, double height)
62 {
63     // TODO: make this more generic, now assumed, that only the blur
64     // being added can affect the required filter area
66     double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1);
67     double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1);
69     if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) {
70         // If not within the default 10% margin (see
71         // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins
72         // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible 
73         // (the opacity at 2.4*radius is about 3e-3)
74         double xmargin = 2.4 * (rx) / width;
75         double ymargin = 2.4 * (ry) / height;
77         // TODO: set it in UserSpaceOnUse instead?
78         sp_repr_set_svg_double(repr, "x", -xmargin);
79         sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin);
80         sp_repr_set_svg_double(repr, "y", -ymargin);
81         sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin);
82     }
83 }
85 /**
86  * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height
87  */
88 SPFilter *
89 new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height)
90 {
91     g_return_val_if_fail(document != NULL, NULL);
93     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
95     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
97     // create a new filter
98     Inkscape::XML::Node *repr;
99     repr = xml_doc->createElement("svg:filter");
100     //repr->setAttribute("inkscape:collect", "always");
102     set_filter_area(repr, radius, expansion, expansionX, expansionY,
103                     width, height);
105     //create feGaussianBlur node
106     Inkscape::XML::Node *b_repr;
107     b_repr = xml_doc->createElement("svg:feGaussianBlur");
108     //b_repr->setAttribute("inkscape:collect", "always");
109     
110     double stdDeviation = radius;
111     if (expansion != 0)
112         stdDeviation /= expansion;
114     //set stdDeviation attribute
115     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
116     
117     //set feGaussianBlur as child of filter node
118     repr->appendChild(b_repr);
119     Inkscape::GC::release(b_repr);
120     
121     // Append the new filter node to defs
122     SP_OBJECT_REPR(defs)->appendChild(repr);
123     Inkscape::GC::release(repr);
125     // get corresponding object
126     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
127     SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
128     
129     g_assert(f != NULL);
130     g_assert(SP_IS_FILTER(f));
131     g_assert(b != NULL);
132     g_assert(SP_IS_GAUSSIANBLUR(b));
134     return f;
137 /**
138  * Creates a filter with blur primitive of specified radius for the given item
139  */
140 SPFilter *
141 new_filter_gaussian_blur_from_item (SPDocument *document, SPItem *item, gdouble radius)
143     NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
145     double width;
146     double height;
147     if (r) {
148         width = r->extent(NR::X);
149         height= r->extent(NR::Y);
150     } else {
151         width = height = 0;
152     }
154     NR::Matrix i2d = sp_item_i2d_affine (item);
156     return (new_filter_gaussian_blur (document, radius, i2d.expansion(), i2d.expansionX(), i2d.expansionY(), width, height));
159 /**
160  * Modifies the gaussian blur applied to the item.
161  * If no filters are applied to given item, creates a new blur filter.
162  * If a filter is applied and it contains a blur, modify that blur.
163  * If the filter doesn't contain blur, a blur is added to the filter.
164  * Should there be more references to modified filter, that filter is
165  * duplicated, so that other elements referring that filter are not modified.
166  */
167 /* TODO: this should be made more generic, not just for blurs */
168 SPFilter *
169 modify_filter_gaussian_blur_from_item(SPDocument *document, SPItem *item,
170                                       gdouble radius)
172     if (!item->style || !item->style->filter.set) {
173         return new_filter_gaussian_blur_from_item(document, item, radius);
174     }
176     SPFilter *filter = SP_FILTER(item->style->filter.filter);
177     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
179     // If there are more users for this filter, duplicate it
180     if (SP_OBJECT_HREFCOUNT(filter) > count_filter_hrefs(item, filter)) {
181         Inkscape::XML::Node *repr;
182         repr = SP_OBJECT_REPR(item)->duplicate(xml_doc);
183         SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
184         SP_OBJECT_REPR(defs)->appendChild(repr);
186         filter = SP_FILTER( document->getObjectByRepr(repr) );
187         Inkscape::GC::release(repr);
188     }
190     // Determine the required standard deviation value
191     NR::Matrix i2d = sp_item_i2d_affine (item);
192     double expansion = i2d.expansion();
193     double stdDeviation = radius;
194     if (expansion != 0)
195         stdDeviation /= expansion;
197     // Get the object size
198     NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
199     double width;
200     double height;
201     if (r) {
202         width = r->extent(NR::X);
203         height= r->extent(NR::Y);
204     } else {
205         width = height = 0;
206     }
208     // Set the filter effects area
209     Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->filter.filter);
210     set_filter_area(repr, radius, expansion, i2d.expansionX(),
211                     i2d.expansionY(), width, height);
213     // Search for gaussian blur primitives. If found, set the stdDeviation
214     // of the first one and return.
215     Inkscape::XML::Node *primitive = repr->firstChild();
216     while (primitive) {
217         if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
218             sp_repr_set_svg_double(primitive, "stdDeviation",
219                                    stdDeviation);
220             return filter;
221         }
222         primitive = primitive->next();
223     }
225     // If there were no gaussian blur primitives, create a new one
227     //create feGaussianBlur node
228     Inkscape::XML::Node *b_repr;
229     b_repr = xml_doc->createElement("svg:feGaussianBlur");
230     //b_repr->setAttribute("inkscape:collect", "always");
231     
232     //set stdDeviation attribute
233     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
234     
235     //set feGaussianBlur as child of filter node
236     SP_OBJECT_REPR(filter)->appendChild(b_repr);
237     Inkscape::GC::release(b_repr);
239     return filter;
242 void remove_filter (SPObject *item, bool recursive)
244         SPCSSAttr *css = sp_repr_css_attr_new ();
245         sp_repr_css_unset_property (css, "filter");
246         if (recursive)
247                 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
248         else
249                 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
250       sp_repr_css_attr_unref (css);
253 /**
254  * Removes the first feGaussianBlur from the filter attached to given item.
255  * Should this leave us with an empty filter, remove that filter.
256  */
257 /* TODO: the removed filter primitive may had had a named result image, so
258  * after removing, the filter may be in erroneous state, this situation should
259  * be handled gracefully */
260 void remove_filter_gaussian_blur (SPObject *item)
262     if (item->style && item->style->filter.set && item->style->filter.filter) {
263         // Search for the first blur primitive and remove it. (if found)
264         Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->filter.filter);
265         Inkscape::XML::Node *primitive = repr->firstChild();
266         while (primitive) {
267             if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
268                 sp_repr_unparent(primitive);
269                 break;
270             }
271             primitive = primitive->next();
272         }
274         // If there are no more primitives left in this filter, discard it.
275         if (repr->childCount() == 0) {
276             remove_filter(item, false);
277         }
278     }
281 /*
282   Local Variables:
283   mode:c++
284   c-file-style:"stroustrup"
285   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
286   indent-tabs-mode:nil
287   fill-column:99
288   End:
289 */
290 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :