Code

Filter effects dialog:
[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());
124     repr->setAttribute("inkscape:collect", "always");
126     // set default values
127     switch(type) {
128         case NR::NR_FILTER_BLEND:
129             repr->setAttribute("blend", "normal");
130             break;
131         case NR::NR_FILTER_COLORMATRIX:
132             break;
133         case NR::NR_FILTER_COMPONENTTRANSFER:
134             break;
135         case NR::NR_FILTER_COMPOSITE:
136             break;
137         case NR::NR_FILTER_CONVOLVEMATRIX:
138             break;
139         case NR::NR_FILTER_DIFFUSELIGHTING:
140             break;
141         case NR::NR_FILTER_DISPLACEMENTMAP:
142             break;
143         case NR::NR_FILTER_FLOOD:
144             break;
145         case NR::NR_FILTER_GAUSSIANBLUR:
146             repr->setAttribute("stdDeviation", "1");
147             break;
148         case NR::NR_FILTER_IMAGE:
149             break;
150         case NR::NR_FILTER_MERGE:
151             break;
152         case NR::NR_FILTER_MORPHOLOGY:
153             break;
154         case NR::NR_FILTER_OFFSET:
155             repr->setAttribute("dx", "0");
156             repr->setAttribute("dy", "0");
157             break;
158         case NR::NR_FILTER_SPECULARLIGHTING:
159             break;
160         case NR::NR_FILTER_TILE:
161             break;
162         case NR::NR_FILTER_TURBULENCE:
163             break;
164         default:
165             break;
166     }
168     //set primitive as child of filter node
169     filter->repr->appendChild(repr);
170     Inkscape::GC::release(repr);
171     
172     // get corresponding object
173     SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) );
174  
175     g_assert(prim != NULL);
176     g_assert(SP_IS_FILTER_PRIMITIVE(prim));
178     return prim;
181 /**
182  * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height
183  */
184 SPFilter *
185 new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height)
187     g_return_val_if_fail(document != NULL, NULL);
189     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
191     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
193     // create a new filter
194     Inkscape::XML::Node *repr;
195     repr = xml_doc->createElement("svg:filter");
196     //repr->setAttribute("inkscape:collect", "always");
198     set_filter_area(repr, radius, expansion, expansionX, expansionY,
199                     width, height);
201     //create feGaussianBlur node
202     Inkscape::XML::Node *b_repr;
203     b_repr = xml_doc->createElement("svg:feGaussianBlur");
204     //b_repr->setAttribute("inkscape:collect", "always");
205     
206     double stdDeviation = radius;
207     if (expansion != 0)
208         stdDeviation /= expansion;
210     //set stdDeviation attribute
211     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
212     
213     //set feGaussianBlur as child of filter node
214     repr->appendChild(b_repr);
215     Inkscape::GC::release(b_repr);
216     
217     // Append the new filter node to defs
218     SP_OBJECT_REPR(defs)->appendChild(repr);
219     Inkscape::GC::release(repr);
221     // get corresponding object
222     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
223     SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
224     
225     g_assert(f != NULL);
226     g_assert(SP_IS_FILTER(f));
227     g_assert(b != NULL);
228     g_assert(SP_IS_GAUSSIANBLUR(b));
230     return f;
234 /**
235  * Creates a simple filter with a blend primitive and a blur primitive of specified radius for
236  * an item with the given matrix expansion, width and height
237  */
238 SPFilter *
239 new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion,
240                                 double expansionX, double expansionY, double width, double height)
242     g_return_val_if_fail(document != NULL, NULL);
244     SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
246     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
248     // create a new filter
249     Inkscape::XML::Node *repr;
250     repr = xml_doc->createElement("svg:filter");
251     repr->setAttribute("inkscape:collect", "always");
253     // Append the new filter node to defs
254     SP_OBJECT_REPR(defs)->appendChild(repr);
255     Inkscape::GC::release(repr);
256  
257     // get corresponding object
258     SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) );
260     // Gaussian blur primitive
261     if(radius != 0) {
262         set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height);
264         //create feGaussianBlur node
265         Inkscape::XML::Node *b_repr;
266         b_repr = xml_doc->createElement("svg:feGaussianBlur");
267         b_repr->setAttribute("inkscape:collect", "always");
268         
269         double stdDeviation = radius;
270         if (expansion != 0)
271             stdDeviation /= expansion;
272         
273         //set stdDeviation attribute
274         sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
275      
276         //set feGaussianBlur as child of filter node
277         repr->appendChild(b_repr);
278         Inkscape::GC::release(b_repr);
280         SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) );
281         g_assert(b != NULL);
282         g_assert(SP_IS_GAUSSIANBLUR(b));
283     }
284     // Blend primitive
285     if(strcmp(blendmode, "normal")) {
286         Inkscape::XML::Node *b_repr;
287         b_repr = xml_doc->createElement("svg:feBlend");
288         b_repr->setAttribute("inkscape:collect", "always");
289         b_repr->setAttribute("mode", blendmode);
290         b_repr->setAttribute("in2", "BackgroundImage");
292         // set feBlend as child of filter node
293         repr->appendChild(b_repr);
294         Inkscape::GC::release(b_repr);
296         // Enable background image buffer for document
297         Inkscape::XML::Node *root = b_repr->root();
298         if (!root->attribute("enable-background")) {
299             root->setAttribute("enable-background", "new");
300         }
302         SPFeBlend *b = SP_FEBLEND(document->getObjectByRepr(b_repr));
303         g_assert(b != NULL);
304         g_assert(SP_IS_FEBLEND(b));
305     }
306     
307     g_assert(f != NULL);
308     g_assert(SP_IS_FILTER(f));
309  
310     return f;
313 /**
314  * Creates a simple filter for the given item with blend and blur primitives, using the
315  * specified mode and radius, respectively
316  */
317 SPFilter *
318 new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius)
320     NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
322     double width;
323     double height;
324     if (r) {
325         width = r->extent(NR::X);
326         height= r->extent(NR::Y);
327     } else {
328         width = height = 0;
329     }
331     NR::Matrix i2d = sp_item_i2d_affine (item);
333     return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.expansion(), i2d.expansionX(), i2d.expansionY(), width, height));
336 /**
337  * Modifies the gaussian blur applied to the item.
338  * If no filters are applied to given item, creates a new blur filter.
339  * If a filter is applied and it contains a blur, modify that blur.
340  * If the filter doesn't contain blur, a blur is added to the filter.
341  * Should there be more references to modified filter, that filter is
342  * duplicated, so that other elements referring that filter are not modified.
343  */
344 /* TODO: this should be made more generic, not just for blurs */
345 SPFilter *
346 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_gaussian_blur_from_item(document, item, radius);
351     }
353     SPFilter *filter = SP_FILTER(item->style->getFilter());
354     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
356     // If there are more users for this filter, duplicate it
357     if (SP_OBJECT_HREFCOUNT(filter) > count_filter_hrefs(item, filter)) {
358         Inkscape::XML::Node *repr;
359         repr = SP_OBJECT_REPR(item->style->getFilter())->duplicate(xml_doc);
360         SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
361         SP_OBJECT_REPR(defs)->appendChild(repr);
363         filter = SP_FILTER( document->getObjectByRepr(repr) );
364         Inkscape::GC::release(repr);
365     }
367     // Determine the required standard deviation value
368     NR::Matrix i2d = sp_item_i2d_affine (item);
369     double expansion = i2d.expansion();
370     double stdDeviation = radius;
371     if (expansion != 0)
372         stdDeviation /= expansion;
374     // Get the object size
375     NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item);
376     double width;
377     double height;
378     if (r) {
379         width = r->extent(NR::X);
380         height= r->extent(NR::Y);
381     } else {
382         width = height = 0;
383     }
385     // Set the filter effects area
386     Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
387     set_filter_area(repr, radius, expansion, i2d.expansionX(),
388                     i2d.expansionY(), width, height);
390     // Search for gaussian blur primitives. If found, set the stdDeviation
391     // of the first one and return.
392     Inkscape::XML::Node *primitive = repr->firstChild();
393     while (primitive) {
394         if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
395             sp_repr_set_svg_double(primitive, "stdDeviation",
396                                    stdDeviation);
397             return filter;
398         }
399         primitive = primitive->next();
400     }
402     // If there were no gaussian blur primitives, create a new one
404     //create feGaussianBlur node
405     Inkscape::XML::Node *b_repr;
406     b_repr = xml_doc->createElement("svg:feGaussianBlur");
407     //b_repr->setAttribute("inkscape:collect", "always");
408     
409     //set stdDeviation attribute
410     sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation);
411     
412     //set feGaussianBlur as child of filter node
413     SP_OBJECT_REPR(filter)->appendChild(b_repr);
414     Inkscape::GC::release(b_repr);
416     return filter;
419 void remove_filter (SPObject *item, bool recursive)
421         SPCSSAttr *css = sp_repr_css_attr_new ();
422         sp_repr_css_unset_property (css, "filter");
423         if (recursive)
424                 sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style");
425         else
426                 sp_repr_css_change (SP_OBJECT_REPR(item), css, "style");
427       sp_repr_css_attr_unref (css);
430 /**
431  * Removes the first feGaussianBlur from the filter attached to given item.
432  * Should this leave us with an empty filter, remove that filter.
433  */
434 /* TODO: the removed filter primitive may had had a named result image, so
435  * after removing, the filter may be in erroneous state, this situation should
436  * be handled gracefully */
437 void remove_filter_gaussian_blur (SPObject *item)
439     if (item->style && item->style->filter.set && item->style->getFilter()) {
440         // Search for the first blur primitive and remove it. (if found)
441         Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter());
442         Inkscape::XML::Node *primitive = repr->firstChild();
443         while (primitive) {
444             if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) {
445                 sp_repr_unparent(primitive);
446                 break;
447             }
448             primitive = primitive->next();
449         }
451         // If there are no more primitives left in this filter, discard it.
452         if (repr->childCount() == 0) {
453             remove_filter(item, false);
454         }
455     }
458 /*
459   Local Variables:
460   mode:c++
461   c-file-style:"stroustrup"
462   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
463   indent-tabs-mode:nil
464   fill-column:99
465   End:
466 */
467 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :