Code

switch SPStyle to using SPFilterReference for filters; sp_style_new now requires...
[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-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->filter.href->getObject() == 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) );
105     
106     
107     g_assert(f != NULL);
108     g_assert(SP_IS_FILTER(f));
110     return f;
113 SPFilterPrimitive *
114 filter_add_primitive(SPFilter *filter, const gchar *type)
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);
126     
127     // get corresponding object
128     SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
129  
130     g_assert(prim != NULL);
131     g_assert(SP_IS_FILTER_PRIMITIVE(prim));
133     return prim;
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)
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");
160     
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);
167     
168     //set feGaussianBlur as child of filter node
169     repr->appendChild(b_repr);
170     Inkscape::GC::release(b_repr);
171     
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) );
179     
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;
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)
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);
211  
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");
223         
224         double stdDeviation = radius;
225         if (expansion != 0)
226             stdDeviation /= expansion;
227         
228         //set stdDeviation attribute
229         sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
230      
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     }
261     
262     g_assert(f != NULL);
263     g_assert(SP_IS_FILTER(f));
264  
265     return f;
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)
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));
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)
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->filter.href->getObject());
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->filter.href->getObject())->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->filter.href->getObject());
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");
363     
364     //set stdDeviation attribute
365     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
366     
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;
374 void remove_filter (SPObject *item, bool recursive)
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);
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)
394     if (item->style && item->style->filter.set && item->style->filter.href->getObject()) {
395         // Search for the first blur primitive and remove it. (if found)
396         Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->filter.href->getObject());
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     }
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 :