Code

A simple layout document as to what, why and how is cppification.
[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-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
38 count_filter_hrefs(SPObject *o, SPFilter *filter)
39 {
40     if (!o)
41         return 1;
43     guint i = 0;
45     SPStyle *style = SP_OBJECT_STYLE(o);
46     if (style
47         && style->filter.set
48         && style->getFilter() == filter)
49     {
50         i ++;
51     }
53     for (SPObject *child = o->first_child();
54          child != NULL; child = SP_OBJECT_NEXT(child)) {
55         i += count_filter_hrefs(child, filter);
56     }
58     return i;
59 }
61 /**
62  * Sets a suitable filter effects area according to given blur radius,
63  * expansion and object size.
64  */
65 static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius,
66                             double expansion, double expansionX,
67                             double expansionY, double width, double height)
68 {
69     // TODO: make this more generic, now assumed, that only the blur
70     // being added can affect the required filter area
72     double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1);
73     double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1);
75     if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) {
76         // If not within the default 10% margin (see
77         // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins
78         // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible 
79         // (the opacity at 2.4*radius is about 3e-3)
80         double xmargin = 2.4 * (rx) / width;
81         double ymargin = 2.4 * (ry) / height;
83         // TODO: set it in UserSpaceOnUse instead?
84         sp_repr_set_svg_double(repr, "x", -xmargin);
85         sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin);
86         sp_repr_set_svg_double(repr, "y", -ymargin);
87         sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin);
88     }
89 }
91 SPFilter *new_filter(SPDocument *document)
92 {
93     g_return_val_if_fail(document != NULL, NULL);
95     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
97     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
99     // create a new filter
100     Inkscape::XML::Node *repr;
101     repr = xml_doc->createElement("svg:filter");
103     // Append the new filter node to defs
104     //SP_OBJECT_REPR(defs)->appendChild(repr);
105         defs->appendChild(repr);
106     Inkscape::GC::release(repr);
108     // get corresponding object
109     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
110     
111     
112     g_assert(f != NULL);
113     g_assert(SP_IS_FILTER(f));
115     return f;
118 SPFilterPrimitive *
119 filter_add_primitive(SPFilter *filter, const Inkscape::Filters::FilterPrimitiveType type)
121     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(filter->document);
123     //create filter primitive node
124     Inkscape::XML::Node *repr;
125     repr = xml_doc->createElement(FPConverter.get_key(type).c_str());
127     // set default values
128     switch(type) {
129         case Inkscape::Filters::NR_FILTER_BLEND:
130             repr->setAttribute("blend", "normal");
131             break;
132         case Inkscape::Filters::NR_FILTER_COLORMATRIX:
133             break;
134         case Inkscape::Filters::NR_FILTER_COMPONENTTRANSFER:
135             break;
136         case Inkscape::Filters::NR_FILTER_COMPOSITE:
137             break;
138         case Inkscape::Filters::NR_FILTER_CONVOLVEMATRIX:
139             repr->setAttribute("order", "3 3");
140             repr->setAttribute("kernelMatrix", "0 0 0 0 0 0 0 0 0");
141             break;
142         case Inkscape::Filters::NR_FILTER_DIFFUSELIGHTING:
143             break;
144         case Inkscape::Filters::NR_FILTER_DISPLACEMENTMAP:
145             break;
146         case Inkscape::Filters::NR_FILTER_FLOOD:
147             break;
148         case Inkscape::Filters::NR_FILTER_GAUSSIANBLUR:
149             repr->setAttribute("stdDeviation", "1");
150             break;
151         case Inkscape::Filters::NR_FILTER_IMAGE:
152             break;
153         case Inkscape::Filters::NR_FILTER_MERGE:
154             break;
155         case Inkscape::Filters::NR_FILTER_MORPHOLOGY:
156             break;
157         case Inkscape::Filters::NR_FILTER_OFFSET:
158             repr->setAttribute("dx", "0");
159             repr->setAttribute("dy", "0");
160             break;
161         case Inkscape::Filters::NR_FILTER_SPECULARLIGHTING:
162             break;
163         case Inkscape::Filters::NR_FILTER_TILE:
164             break;
165         case Inkscape::Filters::NR_FILTER_TURBULENCE:
166             break;
167         default:
168             break;
169     }
171     //set primitive as child of filter node
172         // XML tree being used directly while/where it shouldn't be...
173     //filter->getRepr()->appendChild(repr);
174         filter->appendChild(repr);
175     Inkscape::GC::release(repr);
176     
177     // get corresponding object
178     SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
179  
180     g_assert(prim != NULL);
181     g_assert(SP_IS_FILTER_PRIMITIVE(prim));
183     return prim;
186 /**
187  * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height
188  */
189 SPFilter *
190 new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height)
192     g_return_val_if_fail(document != NULL, NULL);
194     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
196     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
198     // create a new filter
199     Inkscape::XML::Node *repr;
200     repr = xml_doc->createElement("svg:filter");
201     //repr->setAttribute("inkscape:collect", "always");
203     set_filter_area(repr, radius, expansion, expansionX, expansionY,
204                     width, height);
206     //create feGaussianBlur node
207     Inkscape::XML::Node *b_repr;
208     b_repr = xml_doc->createElement("svg:feGaussianBlur");
209     //b_repr->setAttribute("inkscape:collect", "always");
210     
211     double stdDeviation = radius;
212     if (expansion != 0)
213         stdDeviation /= expansion;
215     //set stdDeviation attribute
216     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
217     
218     //set feGaussianBlur as child of filter node
219     repr->appendChild(b_repr);
220     Inkscape::GC::release(b_repr);
221     
222     // Append the new filter node to defs
223     //SP_OBJECT_REPR(defs)->appendChild(repr);
224         defs->appendChild(repr);
225     Inkscape::GC::release(repr);
227     // get corresponding object
228     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
229     SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
230     
231     g_assert(f != NULL);
232     g_assert(SP_IS_FILTER(f));
233     g_assert(b != NULL);
234     g_assert(SP_IS_GAUSSIANBLUR(b));
236     return f;
240 /**
241  * Creates a simple filter with a blend primitive and a blur primitive of specified radius for
242  * an item with the given matrix expansion, width and height
243  */
244 SPFilter *
245 new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion,
246                                 double expansionX, double expansionY, double width, double height)
248     g_return_val_if_fail(document != NULL, NULL);
250     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
252     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
254     // create a new filter
255     Inkscape::XML::Node *repr;
256     repr = xml_doc->createElement("svg:filter");
257     repr->setAttribute("inkscape:collect", "always");
259     // Append the new filter node to defs
260     //SP_OBJECT_REPR(defs)->appendChild(repr);
261         defs->appendChild(repr);
262     Inkscape::GC::release(repr);
263  
264     // get corresponding object
265     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
267     // Gaussian blur primitive
268     if(radius != 0) {
269         set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height);
271         //create feGaussianBlur node
272         Inkscape::XML::Node *b_repr;
273         b_repr = xml_doc->createElement("svg:feGaussianBlur");
274         b_repr->setAttribute("inkscape:collect", "always");
275         
276         double stdDeviation = radius;
277         if (expansion != 0)
278             stdDeviation /= expansion;
279         
280         //set stdDeviation attribute
281         sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
282      
283         //set feGaussianBlur as child of filter node
284         repr->appendChild(b_repr);
285         Inkscape::GC::release(b_repr);
287         SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
288         g_assert(b != NULL);
289         g_assert(SP_IS_GAUSSIANBLUR(b));
290     }
291     // Blend primitive
292     if(strcmp(blendmode, "normal")) {
293         Inkscape::XML::Node *b_repr;
294         b_repr = xml_doc->createElement("svg:feBlend");
295         b_repr->setAttribute("inkscape:collect", "always");
296         b_repr->setAttribute("mode", blendmode);
297         b_repr->setAttribute("in2", "BackgroundImage");
299         // set feBlend as child of filter node
300         repr->appendChild(b_repr);
301         Inkscape::GC::release(b_repr);
303         // Enable background image buffer for document
304         Inkscape::XML::Node *root = b_repr->root();
305         if (!root->attribute("enable-background")) {
306             root->setAttribute("enable-background", "new");
307         }
309         SPFeBlend *b = SP_FEBLEND(document->getObjectByRepr(b_repr));
310         g_assert(b != NULL);
311         g_assert(SP_IS_FEBLEND(b));
312     }
313     
314     g_assert(f != NULL);
315     g_assert(SP_IS_FILTER(f));
316  
317     return f;
320 /**
321  * Creates a simple filter for the given item with blend and blur primitives, using the
322  * specified mode and radius, respectively
323  */
324 SPFilter *
325 new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius)
327     Geom::OptRect const r = item->getBboxDesktop(SPItem::GEOMETRIC_BBOX);
329     double width;
330     double height;
331     if (r) {
332         width = r->dimensions()[Geom::X];
333         height= r->dimensions()[Geom::Y];
334     } else {
335         width = height = 0;
336     }
338     Geom::Matrix i2d (item->i2d_affine () );
340     return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.descrim(), i2d.expansionX(), i2d.expansionY(), width, height));
343 /**
344  * Modifies the gaussian blur applied to the item.
345  * If no filters are applied to given item, creates a new blur filter.
346  * If a filter is applied and it contains a blur, modify that blur.
347  * If the filter doesn't contain blur, a blur is added to the filter.
348  * Should there be more references to modified filter, that filter is
349  * duplicated, so that other elements referring that filter are not modified.
350  */
351 /* TODO: this should be made more generic, not just for blurs */
352 SPFilter *
353 modify_filter_gaussian_blur_from_item(SPDocument *document, SPItem *item,
354                                       gdouble radius)
356     if (!item->style || !item->style->filter.set) {
357         return new_filter_simple_from_item(document, item, "normal", radius);
358     }
360     SPFilter *filter = SP_FILTER(item->style->getFilter());
361     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
363     // If there are more users for this filter, duplicate it
364     if (SP_OBJECT_HREFCOUNT(filter) > count_filter_hrefs(item, filter)) {
365         Inkscape::XML::Node *repr;
366         repr = SP_OBJECT_REPR(item->style->getFilter())->duplicate(xml_doc);
367         SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
368         //SP_OBJECT_REPR(defs)->appendChild(repr);
369                 defs->appendChild(repr);
371         filter = SP_FILTER( document->getObjectByRepr(repr) );
372         Inkscape::GC::release(repr);
373     }
375     // Determine the required standard deviation value
376     Geom::Matrix i2d (item->i2d_affine ());
377     double expansion = i2d.descrim();
378     double stdDeviation = radius;
379     if (expansion != 0)
380         stdDeviation /= expansion;
382     // Get the object size
383     Geom::OptRect const r = item->getBboxDesktop(SPItem::GEOMETRIC_BBOX);
384     double width;
385     double height;
386     if (r) {
387         width = r->dimensions()[Geom::X];
388         height= r->dimensions()[Geom::Y];
389     } else {
390         width = height = 0;
391     }
393     // Set the filter effects area
394     Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
395     set_filter_area(repr, radius, expansion, i2d.expansionX(),
396                     i2d.expansionY(), width, height);
398     // Search for gaussian blur primitives. If found, set the stdDeviation
399     // of the first one and return.
400     Inkscape::XML::Node *primitive = repr->firstChild();
401     while (primitive) {
402         if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
403             sp_repr_set_svg_double(primitive, "stdDeviation",
404                                    stdDeviation);
405             return filter;
406         }
407         primitive = primitive->next();
408     }
410     // If there were no gaussian blur primitives, create a new one
412     //create feGaussianBlur node
413     Inkscape::XML::Node *b_repr;
414     b_repr = xml_doc->createElement("svg:feGaussianBlur");
415     //b_repr->setAttribute("inkscape:collect", "always");
416     
417     //set stdDeviation attribute
418     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
419     
420     //set feGaussianBlur as child of filter node
421     SP_OBJECT_REPR(filter)->appendChild(b_repr);
422     Inkscape::GC::release(b_repr);
424     return filter;
427 void remove_filter (SPObject *item, bool recursive)
429         SPCSSAttr *css = sp_repr_css_attr_new ();
430         sp_repr_css_unset_property (css, "filter");
431         if (recursive)
432                 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
433         else
434                 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
435       sp_repr_css_attr_unref (css);
438 /**
439  * Removes the first feGaussianBlur from the filter attached to given item.
440  * Should this leave us with an empty filter, remove that filter.
441  */
442 /* TODO: the removed filter primitive may had had a named result image, so
443  * after removing, the filter may be in erroneous state, this situation should
444  * be handled gracefully */
445 void remove_filter_gaussian_blur (SPObject *item)
447     if (item->style && item->style->filter.set && item->style->getFilter()) {
448         // Search for the first blur primitive and remove it. (if found)
449         Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
450         Inkscape::XML::Node *primitive = repr->firstChild();
451         while (primitive) {
452             if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
453                 sp_repr_unparent(primitive);
454                 break;
455             }
456             primitive = primitive->next();
457         }
459         // If there are no more primitives left in this filter, discard it.
460         if (repr->childCount() == 0) {
461             remove_filter(item, false);
462         }
463     }
466 bool filter_is_single_gaussian_blur(SPFilter *filter)
468     return (SP_OBJECT(filter)->firstChild() && 
469             SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() &&
470             SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild()));
473 double get_single_gaussian_blur_radius(SPFilter *filter)
475     if (SP_OBJECT(filter)->firstChild() && 
476         SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() &&
477         SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild())) {
479         SPGaussianBlur *gb = SP_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild());
480         double x = gb->stdDeviation.getNumber();
481         double y = gb->stdDeviation.getOptNumber();
482         if (x > 0 && y > 0) {
483             return MAX(x, y);
484         }
485         return x;
486     }
487     return 0.0;
492 /*
493   Local Variables:
494   mode:c++
495   c-file-style:"stroustrup"
496   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
497   indent-tabs-mode:nil
498   fill-column:99
499   End:
500 */
501 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :