Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / filter-chemistry.cpp
1 /*
2  * Various utility methods for filters
3  *
4  * Authors:
5  *   Hugo Rodrigues
6  *   bulia byak
7  *   Niko Kiirala
8  *   Jon A. Cruz <jon@joncruz.org>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 2006-2008 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 "filters/blend.h"
25 #include "sp-filter.h"
26 #include "sp-filter-reference.h"
27 #include "sp-gaussian-blur.h"
28 #include "svg/css-ostringstream.h"
29 #include "libnr/nr-matrix-fns.h"
31 #include "xml/repr.h"
33 /**
34  * Count how many times the filter is used by the styles of o and its
35  * descendants
36  */
37 static guint 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 = o->firstChild(); child; child = child->getNext() ) {
53         i += count_filter_hrefs(child, filter);
54     }
56     return i;
57 }
59 /**
60  * Sets a suitable filter effects area according to given blur radius,
61  * expansion and object size.
62  */
63 static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius,
64                             double expansion, double expansionX,
65                             double expansionY, double width, double height)
66 {
67     // TODO: make this more generic, now assumed, that only the blur
68     // being added can affect the required filter area
70     double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1);
71     double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1);
73     if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) {
74         // If not within the default 10% margin (see
75         // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins
76         // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible 
77         // (the opacity at 2.4*radius is about 3e-3)
78         double xmargin = 2.4 * (rx) / width;
79         double ymargin = 2.4 * (ry) / height;
81         // TODO: set it in UserSpaceOnUse instead?
82         sp_repr_set_svg_double(repr, "x", -xmargin);
83         sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin);
84         sp_repr_set_svg_double(repr, "y", -ymargin);
85         sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin);
86     }
87 }
89 SPFilter *new_filter(SPDocument *document)
90 {
91     g_return_val_if_fail(document != NULL, NULL);
93     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
95     Inkscape::XML::Document *xml_doc = document->getReprDoc();
97     // create a new filter
98     Inkscape::XML::Node *repr;
99     repr = xml_doc->createElement("svg:filter");
101     // Append the new filter node to defs
102     defs->appendChild(repr);
103     Inkscape::GC::release(repr);
105     // get corresponding object
106     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
107     
108     
109     g_assert(f != NULL);
110     g_assert(SP_IS_FILTER(f));
112     return f;
115 SPFilterPrimitive *
116 filter_add_primitive(SPFilter *filter, const Inkscape::Filters::FilterPrimitiveType type)
118     Inkscape::XML::Document *xml_doc = filter->document->getReprDoc();
120     //create filter primitive node
121     Inkscape::XML::Node *repr;
122     repr = xml_doc->createElement(FPConverter.get_key(type).c_str());
124     // set default values
125     switch(type) {
126         case Inkscape::Filters::NR_FILTER_BLEND:
127             repr->setAttribute("blend", "normal");
128             break;
129         case Inkscape::Filters::NR_FILTER_COLORMATRIX:
130             break;
131         case Inkscape::Filters::NR_FILTER_COMPONENTTRANSFER:
132             break;
133         case Inkscape::Filters::NR_FILTER_COMPOSITE:
134             break;
135         case Inkscape::Filters::NR_FILTER_CONVOLVEMATRIX:
136             repr->setAttribute("order", "3 3");
137             repr->setAttribute("kernelMatrix", "0 0 0 0 0 0 0 0 0");
138             break;
139         case Inkscape::Filters::NR_FILTER_DIFFUSELIGHTING:
140             break;
141         case Inkscape::Filters::NR_FILTER_DISPLACEMENTMAP:
142             break;
143         case Inkscape::Filters::NR_FILTER_FLOOD:
144             break;
145         case Inkscape::Filters::NR_FILTER_GAUSSIANBLUR:
146             repr->setAttribute("stdDeviation", "1");
147             break;
148         case Inkscape::Filters::NR_FILTER_IMAGE:
149             break;
150         case Inkscape::Filters::NR_FILTER_MERGE:
151             break;
152         case Inkscape::Filters::NR_FILTER_MORPHOLOGY:
153             break;
154         case Inkscape::Filters::NR_FILTER_OFFSET:
155             repr->setAttribute("dx", "0");
156             repr->setAttribute("dy", "0");
157             break;
158         case Inkscape::Filters::NR_FILTER_SPECULARLIGHTING:
159             break;
160         case Inkscape::Filters::NR_FILTER_TILE:
161             break;
162         case Inkscape::Filters::NR_FILTER_TURBULENCE:
163             break;
164         default:
165             break;
166     }
168     //set primitive as child of filter node
169     // XML tree being used directly while/where it shouldn't be...
170     filter->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 = document->getReprDoc();
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     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 = document->getReprDoc();
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     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     Geom::OptRect const r = item->getBboxDesktop(SPItem::GEOMETRIC_BBOX);
323     double width;
324     double height;
325     if (r) {
326         width = r->dimensions()[Geom::X];
327         height= r->dimensions()[Geom::Y];
328     } else {
329         width = height = 0;
330     }
332     Geom::Matrix i2d (item->i2d_affine () );
334     return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.descrim(), 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 *modify_filter_gaussian_blur_from_item(SPDocument *document, SPItem *item,
347                                                 gdouble radius)
349     if (!item->style || !item->style->filter.set) {
350         return new_filter_simple_from_item(document, item, "normal", radius);
351     }
353     SPFilter *filter = SP_FILTER(item->style->getFilter());
354     Inkscape::XML::Document *xml_doc = document->getReprDoc();
356     // If there are more users for this filter, duplicate it
357     if (filter->hrefcount > count_filter_hrefs(item, filter)) {
358         Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter())->duplicate(xml_doc);
359         SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
360         defs->appendChild(repr);
362         filter = SP_FILTER( document->getObjectByRepr(repr) );
363         Inkscape::GC::release(repr);
364     }
366     // Determine the required standard deviation value
367     Geom::Matrix i2d (item->i2d_affine ());
368     double expansion = i2d.descrim();
369     double stdDeviation = radius;
370     if (expansion != 0)
371         stdDeviation /= expansion;
373     // Get the object size
374     Geom::OptRect const r = item->getBboxDesktop(SPItem::GEOMETRIC_BBOX);
375     double width;
376     double height;
377     if (r) {
378         width = r->dimensions()[Geom::X];
379         height= r->dimensions()[Geom::Y];
380     } else {
381         width = height = 0;
382     }
384     // Set the filter effects area
385     Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
386     set_filter_area(repr, radius, expansion, i2d.expansionX(),
387                     i2d.expansionY(), width, height);
389     // Search for gaussian blur primitives. If found, set the stdDeviation
390     // of the first one and return.
391     Inkscape::XML::Node *primitive = repr->firstChild();
392     while (primitive) {
393         if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
394             sp_repr_set_svg_double(primitive, "stdDeviation",
395                                    stdDeviation);
396             return filter;
397         }
398         primitive = primitive->next();
399     }
401     // If there were no gaussian blur primitives, create a new one
403     //create feGaussianBlur node
404     Inkscape::XML::Node *b_repr;
405     b_repr = xml_doc->createElement("svg:feGaussianBlur");
406     //b_repr->setAttribute("inkscape:collect", "always");
407     
408     //set stdDeviation attribute
409     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
410     
411     //set feGaussianBlur as child of filter node
412     SP_OBJECT_REPR(filter)->appendChild(b_repr);
413     Inkscape::GC::release(b_repr);
415     return filter;
418 void remove_filter (SPObject *item, bool recursive)
420         SPCSSAttr *css = sp_repr_css_attr_new ();
421         sp_repr_css_unset_property (css, "filter");
422         if (recursive)
423                 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
424         else
425                 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
426       sp_repr_css_attr_unref (css);
429 /**
430  * Removes the first feGaussianBlur from the filter attached to given item.
431  * Should this leave us with an empty filter, remove that filter.
432  */
433 /* TODO: the removed filter primitive may had had a named result image, so
434  * after removing, the filter may be in erroneous state, this situation should
435  * be handled gracefully */
436 void remove_filter_gaussian_blur (SPObject *item)
438     if (item->style && item->style->filter.set && item->style->getFilter()) {
439         // Search for the first blur primitive and remove it. (if found)
440         Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
441         Inkscape::XML::Node *primitive = repr->firstChild();
442         while (primitive) {
443             if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
444                 sp_repr_unparent(primitive);
445                 break;
446             }
447             primitive = primitive->next();
448         }
450         // If there are no more primitives left in this filter, discard it.
451         if (repr->childCount() == 0) {
452             remove_filter(item, false);
453         }
454     }
457 bool filter_is_single_gaussian_blur(SPFilter *filter)
459     return (SP_OBJECT(filter)->firstChild() && 
460             SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() &&
461             SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild()));
464 double get_single_gaussian_blur_radius(SPFilter *filter)
466     if (SP_OBJECT(filter)->firstChild() && 
467         SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() &&
468         SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild())) {
470         SPGaussianBlur *gb = SP_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild());
471         double x = gb->stdDeviation.getNumber();
472         double y = gb->stdDeviation.getOptNumber();
473         if (x > 0 && y > 0) {
474             return MAX(x, y);
475         }
476         return x;
477     }
478     return 0.0;
483 /*
484   Local Variables:
485   mode:c++
486   c-file-style:"stroustrup"
487   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
488   indent-tabs-mode:nil
489   fill-column:99
490   End:
491 */
492 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :