#define __SP_FILTER_CHEMISTRY_C__ /* * Various utility methods for filters * * Authors: * Hugo Rodrigues * bulia byak * Niko Kiirala * * Copyright (C) 2006,2007 authors * * Released under GNU GPL, read the file 'COPYING' for more information */ #include "style.h" #include "document-private.h" #include "desktop-style.h" #include "filter-chemistry.h" #include "filter-enums.h" #include "sp-feblend.h" #include "sp-filter.h" #include "sp-filter-reference.h" #include "sp-gaussian-blur.h" #include "svg/css-ostringstream.h" #include "xml/repr.h" /** * Count how many times the filter is used by the styles of o and its * descendants */ static guint count_filter_hrefs(SPObject *o, SPFilter *filter) { if (!o) return 1; guint i = 0; SPStyle *style = SP_OBJECT_STYLE(o); if (style && style->filter.set && style->getFilter() == filter) { i ++; } for (SPObject *child = sp_object_first_child(o); child != NULL; child = SP_OBJECT_NEXT(child)) { i += count_filter_hrefs(child, filter); } return i; } /** * Sets a suitable filter effects area according to given blur radius, * expansion and object size. */ static void set_filter_area(Inkscape::XML::Node *repr, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height) { // TODO: make this more generic, now assumed, that only the blur // being added can affect the required filter area double rx = radius * (expansionY != 0 ? (expansion / expansionY) : 1); double ry = radius * (expansionX != 0 ? (expansion / expansionX) : 1); if (width != 0 && height != 0 && (2.4 * rx > width * 0.1 || 2.4 * ry > height * 0.1)) { // If not within the default 10% margin (see // http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion), specify margins // The 2.4 is an empirical coefficient: at that distance the cutoff is practically invisible // (the opacity at 2.4*radius is about 3e-3) double xmargin = 2.4 * (rx) / width; double ymargin = 2.4 * (ry) / height; // TODO: set it in UserSpaceOnUse instead? sp_repr_set_svg_double(repr, "x", -xmargin); sp_repr_set_svg_double(repr, "width", 1 + 2 * xmargin); sp_repr_set_svg_double(repr, "y", -ymargin); sp_repr_set_svg_double(repr, "height", 1 + 2 * ymargin); } } SPFilter *new_filter(SPDocument *document) { g_return_val_if_fail(document != NULL, NULL); SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document); // create a new filter Inkscape::XML::Node *repr; repr = xml_doc->createElement("svg:filter"); // Append the new filter node to defs SP_OBJECT_REPR(defs)->appendChild(repr); Inkscape::GC::release(repr); // get corresponding object SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) ); g_assert(f != NULL); g_assert(SP_IS_FILTER(f)); return f; } SPFilterPrimitive * filter_add_primitive(SPFilter *filter, const NR::FilterPrimitiveType type) { Inkscape::XML::Document *xml_doc = sp_document_repr_doc(filter->document); //create filter primitive node Inkscape::XML::Node *repr; repr = xml_doc->createElement(FPConverter.get_key(type).c_str()); // set default values switch(type) { case NR::NR_FILTER_BLEND: repr->setAttribute("blend", "normal"); break; case NR::NR_FILTER_COLORMATRIX: break; case NR::NR_FILTER_COMPONENTTRANSFER: break; case NR::NR_FILTER_COMPOSITE: break; case NR::NR_FILTER_CONVOLVEMATRIX: repr->setAttribute("order", "3 3"); repr->setAttribute("kernelMatrix", "0 0 0 0 0 0 0 0 0"); break; case NR::NR_FILTER_DIFFUSELIGHTING: break; case NR::NR_FILTER_DISPLACEMENTMAP: break; case NR::NR_FILTER_FLOOD: break; case NR::NR_FILTER_GAUSSIANBLUR: repr->setAttribute("stdDeviation", "1"); break; case NR::NR_FILTER_IMAGE: break; case NR::NR_FILTER_MERGE: break; case NR::NR_FILTER_MORPHOLOGY: break; case NR::NR_FILTER_OFFSET: repr->setAttribute("dx", "0"); repr->setAttribute("dy", "0"); break; case NR::NR_FILTER_SPECULARLIGHTING: break; case NR::NR_FILTER_TILE: break; case NR::NR_FILTER_TURBULENCE: break; default: break; } //set primitive as child of filter node filter->repr->appendChild(repr); Inkscape::GC::release(repr); // get corresponding object SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) ); g_assert(prim != NULL); g_assert(SP_IS_FILTER_PRIMITIVE(prim)); return prim; } /** * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height */ SPFilter * new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height) { g_return_val_if_fail(document != NULL, NULL); SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document); // create a new filter Inkscape::XML::Node *repr; repr = xml_doc->createElement("svg:filter"); //repr->setAttribute("inkscape:collect", "always"); set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height); //create feGaussianBlur node Inkscape::XML::Node *b_repr; b_repr = xml_doc->createElement("svg:feGaussianBlur"); //b_repr->setAttribute("inkscape:collect", "always"); double stdDeviation = radius; if (expansion != 0) stdDeviation /= expansion; //set stdDeviation attribute sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation); //set feGaussianBlur as child of filter node repr->appendChild(b_repr); Inkscape::GC::release(b_repr); // Append the new filter node to defs SP_OBJECT_REPR(defs)->appendChild(repr); Inkscape::GC::release(repr); // get corresponding object SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) ); SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) ); g_assert(f != NULL); g_assert(SP_IS_FILTER(f)); g_assert(b != NULL); g_assert(SP_IS_GAUSSIANBLUR(b)); return f; } /** * Creates a simple filter with a blend primitive and a blur primitive of specified radius for * an item with the given matrix expansion, width and height */ SPFilter * new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height) { g_return_val_if_fail(document != NULL, NULL); SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document); // create a new filter Inkscape::XML::Node *repr; repr = xml_doc->createElement("svg:filter"); repr->setAttribute("inkscape:collect", "always"); // Append the new filter node to defs SP_OBJECT_REPR(defs)->appendChild(repr); Inkscape::GC::release(repr); // get corresponding object SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) ); // Gaussian blur primitive if(radius != 0) { set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height); //create feGaussianBlur node Inkscape::XML::Node *b_repr; b_repr = xml_doc->createElement("svg:feGaussianBlur"); b_repr->setAttribute("inkscape:collect", "always"); double stdDeviation = radius; if (expansion != 0) stdDeviation /= expansion; //set stdDeviation attribute sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation); //set feGaussianBlur as child of filter node repr->appendChild(b_repr); Inkscape::GC::release(b_repr); SPGaussianBlur *b = SP_GAUSSIANBLUR( document->getObjectByRepr(b_repr) ); g_assert(b != NULL); g_assert(SP_IS_GAUSSIANBLUR(b)); } // Blend primitive if(strcmp(blendmode, "normal")) { Inkscape::XML::Node *b_repr; b_repr = xml_doc->createElement("svg:feBlend"); b_repr->setAttribute("inkscape:collect", "always"); b_repr->setAttribute("mode", blendmode); b_repr->setAttribute("in2", "BackgroundImage"); // set feBlend as child of filter node repr->appendChild(b_repr); Inkscape::GC::release(b_repr); // Enable background image buffer for document Inkscape::XML::Node *root = b_repr->root(); if (!root->attribute("enable-background")) { root->setAttribute("enable-background", "new"); } SPFeBlend *b = SP_FEBLEND(document->getObjectByRepr(b_repr)); g_assert(b != NULL); g_assert(SP_IS_FEBLEND(b)); } g_assert(f != NULL); g_assert(SP_IS_FILTER(f)); return f; } /** * Creates a simple filter for the given item with blend and blur primitives, using the * specified mode and radius, respectively */ SPFilter * new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius) { NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item); double width; double height; if (r) { width = r->extent(NR::X); height= r->extent(NR::Y); } else { width = height = 0; } NR::Matrix i2d = sp_item_i2d_affine (item); return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.expansion(), i2d.expansionX(), i2d.expansionY(), width, height)); } /** * Modifies the gaussian blur applied to the item. * If no filters are applied to given item, creates a new blur filter. * If a filter is applied and it contains a blur, modify that blur. * If the filter doesn't contain blur, a blur is added to the filter. * Should there be more references to modified filter, that filter is * duplicated, so that other elements referring that filter are not modified. */ /* TODO: this should be made more generic, not just for blurs */ SPFilter * modify_filter_gaussian_blur_from_item(SPDocument *document, SPItem *item, gdouble radius) { if (!item->style || !item->style->filter.set) { //return new_filter_gaussian_blur_from_item(document, item, radius); } SPFilter *filter = SP_FILTER(item->style->getFilter()); Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document); // If there are more users for this filter, duplicate it if (SP_OBJECT_HREFCOUNT(filter) > count_filter_hrefs(item, filter)) { Inkscape::XML::Node *repr; repr = SP_OBJECT_REPR(item->style->getFilter())->duplicate(xml_doc); SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); SP_OBJECT_REPR(defs)->appendChild(repr); filter = SP_FILTER( document->getObjectByRepr(repr) ); Inkscape::GC::release(repr); } // Determine the required standard deviation value NR::Matrix i2d = sp_item_i2d_affine (item); double expansion = i2d.expansion(); double stdDeviation = radius; if (expansion != 0) stdDeviation /= expansion; // Get the object size NR::Maybe<NR::Rect> const r = sp_item_bbox_desktop(item); double width; double height; if (r) { width = r->extent(NR::X); height= r->extent(NR::Y); } else { width = height = 0; } // Set the filter effects area Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter()); set_filter_area(repr, radius, expansion, i2d.expansionX(), i2d.expansionY(), width, height); // Search for gaussian blur primitives. If found, set the stdDeviation // of the first one and return. Inkscape::XML::Node *primitive = repr->firstChild(); while (primitive) { if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) { sp_repr_set_svg_double(primitive, "stdDeviation", stdDeviation); return filter; } primitive = primitive->next(); } // If there were no gaussian blur primitives, create a new one //create feGaussianBlur node Inkscape::XML::Node *b_repr; b_repr = xml_doc->createElement("svg:feGaussianBlur"); //b_repr->setAttribute("inkscape:collect", "always"); //set stdDeviation attribute sp_repr_set_svg_double(b_repr, "stdDeviation", stdDeviation); //set feGaussianBlur as child of filter node SP_OBJECT_REPR(filter)->appendChild(b_repr); Inkscape::GC::release(b_repr); return filter; } void remove_filter (SPObject *item, bool recursive) { SPCSSAttr *css = sp_repr_css_attr_new (); sp_repr_css_unset_property (css, "filter"); if (recursive) sp_repr_css_change_recursive(SP_OBJECT_REPR(item), css, "style"); else sp_repr_css_change (SP_OBJECT_REPR(item), css, "style"); sp_repr_css_attr_unref (css); } /** * Removes the first feGaussianBlur from the filter attached to given item. * Should this leave us with an empty filter, remove that filter. */ /* TODO: the removed filter primitive may had had a named result image, so * after removing, the filter may be in erroneous state, this situation should * be handled gracefully */ void remove_filter_gaussian_blur (SPObject *item) { if (item->style && item->style->filter.set && item->style->getFilter()) { // Search for the first blur primitive and remove it. (if found) Inkscape::XML::Node *repr = SP_OBJECT_REPR(item->style->getFilter()); Inkscape::XML::Node *primitive = repr->firstChild(); while (primitive) { if (strcmp("svg:feGaussianBlur", primitive->name()) == 0) { sp_repr_unparent(primitive); break; } primitive = primitive->next(); } // If there are no more primitives left in this filter, discard it. if (repr->childCount() == 0) { remove_filter(item, false); } } } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :