Code

b9ff1287b9bdbc4bc08497b413ec180fdf56f206
[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 "filter-chemistry.h"
22 #include "filter-enums.h"
24 #include "sp-feblend.h"
25 #include "sp-filter.h"
26 #include "sp-filter-reference.h"
27 #include "sp-gaussian-blur.h"
28 #include "svg/css-ostringstream.h"
30 #include "xml/repr.h"
32 /**
33  * Count how many times the filter is used by the styles of o and its
34  * descendants
35  */
36 static guint
37 count_filter_hrefs(SPObject *o, SPFilter *filter)
38 {
39     if (!o)
40         return 1;
42     guint i = 0;
44     SPStyle *style = SP_OBJECT_STYLE(o);
45     if (style
46         && style->filter.set
47         && style->getFilter() == filter)
48     {
49         i ++;
50     }
52     for (SPObject *child = sp_object_first_child(o);
53          child != NULL; child = SP_OBJECT_NEXT(child)) {
54         i += count_filter_hrefs(child, filter);
55     }
57     return i;
58 }
60 /**
61  * Sets a suitable filter effects area according to given blur radius,
62  * expansion and object size.
63  */
64 static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius,
65                             double expansion, double expansionX,
66                             double expansionY, double width, double height)
67 {
68     // TODO: make this more generic, now assumed, that only the blur
69     // being added can affect the required filter area
71     double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1);
72     double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1);
74     if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) {
75         // If not within the default 10% margin (see
76         // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins
77         // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible 
78         // (the opacity at 2.4*radius is about 3e-3)
79         double xmargin = 2.4 * (rx) / width;
80         double ymargin = 2.4 * (ry) / height;
82         // TODO: set it in UserSpaceOnUse instead?
83         sp_repr_set_svg_double(repr, "x", -xmargin);
84         sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin);
85         sp_repr_set_svg_double(repr, "y", -ymargin);
86         sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin);
87     }
88 }
90 SPFilter *new_filter(SPDocument *document)
91 {
92     g_return_val_if_fail(document != NULL, NULL);
94     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
96     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
98     // create a new filter
99     Inkscape::XML::Node *repr;
100     repr = xml_doc->createElement("svg:filter");
102     // Append the new filter node to defs
103     SP_OBJECT_REPR(defs)->appendChild(repr);
104     Inkscape::GC::release(repr);
106     // get corresponding object
107     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
108     
109     
110     g_assert(f != NULL);
111     g_assert(SP_IS_FILTER(f));
113     return f;
116 SPFilterPrimitive *
117 filter_add_primitive(SPFilter *filter, const NR::FilterPrimitiveType type)
119     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(filter->document);
121     //create filter primitive node
122     Inkscape::XML::Node *repr;
123     repr = xml_doc->createElement(FPConverter.get_key(type).c_str());
125     // set default values
126     switch(type) {
127         case NR::NR_FILTER_BLEND:
128             repr->setAttribute("blend", "normal");
129             break;
130         case NR::NR_FILTER_COLORMATRIX:
131             break;
132         case NR::NR_FILTER_COMPONENTTRANSFER:
133             break;
134         case NR::NR_FILTER_COMPOSITE:
135             break;
136         case NR::NR_FILTER_CONVOLVEMATRIX:
137             repr->setAttribute("order", "3 3");
138             repr->setAttribute("kernelMatrix", "0 0 0 0 0 0 0 0 0");
139             break;
140         case NR::NR_FILTER_DIFFUSELIGHTING:
141             break;
142         case NR::NR_FILTER_DISPLACEMENTMAP:
143             break;
144         case NR::NR_FILTER_FLOOD:
145             break;
146         case NR::NR_FILTER_GAUSSIANBLUR:
147             repr->setAttribute("stdDeviation", "1");
148             break;
149         case NR::NR_FILTER_IMAGE:
150             break;
151         case NR::NR_FILTER_MERGE:
152             break;
153         case NR::NR_FILTER_MORPHOLOGY:
154             break;
155         case NR::NR_FILTER_OFFSET:
156             repr->setAttribute("dx", "0");
157             repr->setAttribute("dy", "0");
158             break;
159         case NR::NR_FILTER_SPECULARLIGHTING:
160             break;
161         case NR::NR_FILTER_TILE:
162             break;
163         case NR::NR_FILTER_TURBULENCE:
164             break;
165         default:
166             break;
167     }
169     //set primitive as child of filter node
170     filter->repr->appendChild(repr);
171     Inkscape::GC::release(repr);
172     
173     // get corresponding object
174     SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
175  
176     g_assert(prim != NULL);
177     g_assert(SP_IS_FILTER_PRIMITIVE(prim));
179     return prim;
182 /**
183  * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height
184  */
185 SPFilter *
186 new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height)
188     g_return_val_if_fail(document != NULL, NULL);
190     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
192     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
194     // create a new filter
195     Inkscape::XML::Node *repr;
196     repr = xml_doc->createElement("svg:filter");
197     //repr->setAttribute("inkscape:collect", "always");
199     set_filter_area(repr, radius, expansion, expansionX, expansionY,
200                     width, height);
202     //create feGaussianBlur node
203     Inkscape::XML::Node *b_repr;
204     b_repr = xml_doc->createElement("svg:feGaussianBlur");
205     //b_repr->setAttribute("inkscape:collect", "always");
206     
207     double stdDeviation = radius;
208     if (expansion != 0)
209         stdDeviation /= expansion;
211     //set stdDeviation attribute
212     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
213     
214     //set feGaussianBlur as child of filter node
215     repr->appendChild(b_repr);
216     Inkscape::GC::release(b_repr);
217     
218     // Append the new filter node to defs
219     SP_OBJECT_REPR(defs)->appendChild(repr);
220     Inkscape::GC::release(repr);
222     // get corresponding object
223     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
224     SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
225     
226     g_assert(f != NULL);
227     g_assert(SP_IS_FILTER(f));
228     g_assert(b != NULL);
229     g_assert(SP_IS_GAUSSIANBLUR(b));
231     return f;
235 /**
236  * Creates a simple filter with a blend primitive and a blur primitive of specified radius for
237  * an item with the given matrix expansion, width and height
238  */
239 SPFilter *
240 new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion,
241                                 double expansionX, double expansionY, double width, double height)
243     g_return_val_if_fail(document != NULL, NULL);
245     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
247     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
249     // create a new filter
250     Inkscape::XML::Node *repr;
251     repr = xml_doc->createElement("svg:filter");
252     repr->setAttribute("inkscape:collect", "always");
254     // Append the new filter node to defs
255     SP_OBJECT_REPR(defs)->appendChild(repr);
256     Inkscape::GC::release(repr);
257  
258     // get corresponding object
259     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
261     // Gaussian blur primitive
262     if(radius != 0) {
263         set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height);
265         //create feGaussianBlur node
266         Inkscape::XML::Node *b_repr;
267         b_repr = xml_doc->createElement("svg:feGaussianBlur");
268         b_repr->setAttribute("inkscape:collect", "always");
269         
270         double stdDeviation = radius;
271         if (expansion != 0)
272             stdDeviation /= expansion;
273         
274         //set stdDeviation attribute
275         sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
276      
277         //set feGaussianBlur as child of filter node
278         repr->appendChild(b_repr);
279         Inkscape::GC::release(b_repr);
281         SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
282         g_assert(b != NULL);
283         g_assert(SP_IS_GAUSSIANBLUR(b));
284     }
285     // Blend primitive
286     if(strcmp(blendmode, "normal")) {
287         Inkscape::XML::Node *b_repr;
288         b_repr = xml_doc->createElement("svg:feBlend");
289         b_repr->setAttribute("inkscape:collect", "always");
290         b_repr->setAttribute("mode", blendmode);
291         b_repr->setAttribute("in2", "BackgroundImage");
293         // set feBlend as child of filter node
294         repr->appendChild(b_repr);
295         Inkscape::GC::release(b_repr);
297         // Enable background image buffer for document
298         Inkscape::XML::Node *root = b_repr->root();
299         if (!root->attribute("enable-background")) {
300             root->setAttribute("enable-background", "new");
301         }
303         SPFeBlend *b = SP_FEBLEND(document->getObjectByRepr(b_repr));
304         g_assert(b != NULL);
305         g_assert(SP_IS_FEBLEND(b));
306     }
307     
308     g_assert(f != NULL);
309     g_assert(SP_IS_FILTER(f));
310  
311     return f;
314 /**
315  * Creates a simple filter for the given item with blend and blur primitives, using the
316  * specified mode and radius, respectively
317  */
318 SPFilter *
319 new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius)
321     NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
323     double width;
324     double height;
325     if (r) {
326         width = r->extent(NR::X);
327         height= r->extent(NR::Y);
328     } else {
329         width = height = 0;
330     }
332     NR::Matrix i2d = sp_item_i2d_affine (item);
334     return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.expansion(), i2d.expansionX(), i2d.expansionY(), width, height));
337 /**
338  * Modifies the gaussian blur applied to the item.
339  * If no filters are applied to given item, creates a new blur filter.
340  * If a filter is applied and it contains a blur, modify that blur.
341  * If the filter doesn't contain blur, a blur is added to the filter.
342  * Should there be more references to modified filter, that filter is
343  * duplicated, so that other elements referring that filter are not modified.
344  */
345 /* TODO: this should be made more generic, not just for blurs */
346 SPFilter *
347 modify_filter_gaussian_blur_from_item(SPDocument *document, SPItem *item,
348                                       gdouble radius)
350     if (!item->style || !item->style->filter.set) {
351         //return new_filter_gaussian_blur_from_item(document, item, radius);
352     }
354     SPFilter *filter = SP_FILTER(item->style->getFilter());
355     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
357     // If there are more users for this filter, duplicate it
358     if (SP_OBJECT_HREFCOUNT(filter) > count_filter_hrefs(item, filter)) {
359         Inkscape::XML::Node *repr;
360         repr = SP_OBJECT_REPR(item->style->getFilter())->duplicate(xml_doc);
361         SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
362         SP_OBJECT_REPR(defs)->appendChild(repr);
364         filter = SP_FILTER( document->getObjectByRepr(repr) );
365         Inkscape::GC::release(repr);
366     }
368     // Determine the required standard deviation value
369     NR::Matrix i2d = sp_item_i2d_affine (item);
370     double expansion = i2d.expansion();
371     double stdDeviation = radius;
372     if (expansion != 0)
373         stdDeviation /= expansion;
375     // Get the object size
376     NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
377     double width;
378     double height;
379     if (r) {
380         width = r->extent(NR::X);
381         height= r->extent(NR::Y);
382     } else {
383         width = height = 0;
384     }
386     // Set the filter effects area
387     Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
388     set_filter_area(repr, radius, expansion, i2d.expansionX(),
389                     i2d.expansionY(), width, height);
391     // Search for gaussian blur primitives. If found, set the stdDeviation
392     // of the first one and return.
393     Inkscape::XML::Node *primitive = repr->firstChild();
394     while (primitive) {
395         if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
396             sp_repr_set_svg_double(primitive, "stdDeviation",
397                                    stdDeviation);
398             return filter;
399         }
400         primitive = primitive->next();
401     }
403     // If there were no gaussian blur primitives, create a new one
405     //create feGaussianBlur node
406     Inkscape::XML::Node *b_repr;
407     b_repr = xml_doc->createElement("svg:feGaussianBlur");
408     //b_repr->setAttribute("inkscape:collect", "always");
409     
410     //set stdDeviation attribute
411     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
412     
413     //set feGaussianBlur as child of filter node
414     SP_OBJECT_REPR(filter)->appendChild(b_repr);
415     Inkscape::GC::release(b_repr);
417     return filter;
420 void remove_filter (SPObject *item, bool recursive)
422         SPCSSAttr *css = sp_repr_css_attr_new ();
423         sp_repr_css_unset_property (css, "filter");
424         if (recursive)
425                 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
426         else
427                 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
428       sp_repr_css_attr_unref (css);
431 /**
432  * Removes the first feGaussianBlur from the filter attached to given item.
433  * Should this leave us with an empty filter, remove that filter.
434  */
435 /* TODO: the removed filter primitive may had had a named result image, so
436  * after removing, the filter may be in erroneous state, this situation should
437  * be handled gracefully */
438 void remove_filter_gaussian_blur (SPObject *item)
440     if (item->style && item->style->filter.set && item->style->getFilter()) {
441         // Search for the first blur primitive and remove it. (if found)
442         Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
443         Inkscape::XML::Node *primitive = repr->firstChild();
444         while (primitive) {
445             if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
446                 sp_repr_unparent(primitive);
447                 break;
448             }
449             primitive = primitive->next();
450         }
452         // If there are no more primitives left in this filter, discard it.
453         if (repr->childCount() == 0) {
454             remove_filter(item, false);
455         }
456     }
459 /*
460   Local Variables:
461   mode:c++
462   c-file-style:"stroustrup"
463   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
464   indent-tabs-mode:nil
465   fill-column:99
466   End:
467 */
468 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :