X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Ffilter-chemistry.cpp;h=63e618dcf8fc2f297a2d98cbb7328ea1f922f191;hb=a401603e1e2635fadeec3b7a3a48de442da93472;hp=100caefbe3db46dc8fd90e8512b64341fb4cb1dd;hpb=262f61c365788f89a49e4218865c3dc737512555;p=inkscape.git diff --git a/src/filter-chemistry.cpp b/src/filter-chemistry.cpp index 100caefbe..63e618dcf 100644 --- a/src/filter-chemistry.cpp +++ b/src/filter-chemistry.cpp @@ -6,8 +6,9 @@ * Authors: * Hugo Rodrigues * bulia byak + * Niko Kiirala * - * Copyright (C) 2006 authors + * Copyright (C) 2006-2008 authors * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -17,35 +18,67 @@ #include "document-private.h" #include "desktop-style.h" +#include "filter-chemistry.h" +#include "filter-enums.h" + +#include "filters/blend.h" #include "sp-filter.h" +#include "sp-filter-reference.h" #include "sp-gaussian-blur.h" #include "svg/css-ostringstream.h" +#include "libnr/nr-matrix-fns.h" #include "xml/repr.h" /** - * Creates a filter with blur primitive of specified radius for an item with the given matrix expansion, width and height + * Count how many times the filter is used by the styles of o and its + * descendants */ -SPFilter * -new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion, double expansionX, double expansionY, double width, double height) +static guint +count_filter_hrefs(SPObject *o, SPFilter *filter) { - g_return_val_if_fail(document != NULL, NULL); + if (!o) + return 1; - SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document); + guint i = 0; - // create a new filter - Inkscape::XML::Node *repr; - repr = sp_repr_new("svg:filter"); - repr->setAttribute("inkscape:collect", "always"); + SPStyle *style = SP_OBJECT_STYLE(o); + if (style + && style->filter.set + && style->getFilter() == filter) + { + i ++; + } - double rx = radius * (expansion / expansionY); - double ry = radius * (expansion / expansionX); + for (SPObject *child = sp_object_first_child(o); + child != NULL; child = SP_OBJECT_NEXT(child)) { + i += count_filter_hrefs(child, filter); + } + + return i; +} - if (width != 0 && height != 0 && (2 * rx > width * 0.1 || 2 * ry > height * 0.1)) { +/** + * 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 - double xmargin = 2 * (rx) / width; - double ymargin = 2 * (ry) / height; + // 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); @@ -53,11 +86,124 @@ new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion 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 Inkscape::Filters::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 Inkscape::Filters::NR_FILTER_BLEND: + repr->setAttribute("blend", "normal"); + break; + case Inkscape::Filters::NR_FILTER_COLORMATRIX: + break; + case Inkscape::Filters::NR_FILTER_COMPONENTTRANSFER: + break; + case Inkscape::Filters::NR_FILTER_COMPOSITE: + break; + case Inkscape::Filters::NR_FILTER_CONVOLVEMATRIX: + repr->setAttribute("order", "3 3"); + repr->setAttribute("kernelMatrix", "0 0 0 0 0 0 0 0 0"); + break; + case Inkscape::Filters::NR_FILTER_DIFFUSELIGHTING: + break; + case Inkscape::Filters::NR_FILTER_DISPLACEMENTMAP: + break; + case Inkscape::Filters::NR_FILTER_FLOOD: + break; + case Inkscape::Filters::NR_FILTER_GAUSSIANBLUR: + repr->setAttribute("stdDeviation", "1"); + break; + case Inkscape::Filters::NR_FILTER_IMAGE: + break; + case Inkscape::Filters::NR_FILTER_MERGE: + break; + case Inkscape::Filters::NR_FILTER_MORPHOLOGY: + break; + case Inkscape::Filters::NR_FILTER_OFFSET: + repr->setAttribute("dx", "0"); + repr->setAttribute("dy", "0"); + break; + case Inkscape::Filters::NR_FILTER_SPECULARLIGHTING: + break; + case Inkscape::Filters::NR_FILTER_TILE: + break; + case Inkscape::Filters::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 = sp_repr_new("svg:feGaussianBlur"); - b_repr->setAttribute("inkscape:collect", "always"); + b_repr = xml_doc->createElement("svg:feGaussianBlur"); + //b_repr->setAttribute("inkscape:collect", "always"); double stdDeviation = radius; if (expansion != 0) @@ -86,19 +232,190 @@ new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion 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 filter with blur primitive of specified radius for the given item + * Creates a simple filter for the given item with blend and blur primitives, using the + * specified mode and radius, respectively */ SPFilter * -new_filter_gaussian_blur_from_item (SPDocument *document, SPItem *item, gdouble radius) +new_filter_simple_from_item (SPDocument *document, SPItem *item, const char *mode, gdouble radius) { - NR::Rect const r = sp_item_bbox_desktop(item); - double width = r.extent(NR::X); - double height = r.extent(NR::Y); + Geom::OptRect const r = sp_item_bbox_desktop(item, SPItem::GEOMETRIC_BBOX); + + double width; + double height; + if (r) { + width = r->dimensions()[Geom::X]; + height= r->dimensions()[Geom::Y]; + } else { + width = height = 0; + } + + Geom::Matrix i2d (sp_item_i2d_affine (item) ); + + return (new_filter_blend_gaussian_blur (document, mode, radius, i2d.descrim(), 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_simple_from_item(document, item, "normal", radius); + } + + SPFilter *filter = SP_FILTER(item->style->getFilter()); + Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document); - NR::Matrix i2d = sp_item_i2d_affine (item); + // 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); - return (new_filter_gaussian_blur (document, radius, i2d.expansion(), i2d.expansionX(), i2d.expansionY(), width, height)); + filter = SP_FILTER( document->getObjectByRepr(repr) ); + Inkscape::GC::release(repr); + } + + // Determine the required standard deviation value + Geom::Matrix i2d (sp_item_i2d_affine (item)); + double expansion = i2d.descrim(); + double stdDeviation = radius; + if (expansion != 0) + stdDeviation /= expansion; + + // Get the object size + Geom::OptRect const r = sp_item_bbox_desktop(item, SPItem::GEOMETRIC_BBOX); + double width; + double height; + if (r) { + width = r->dimensions()[Geom::X]; + height= r->dimensions()[Geom::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) @@ -112,6 +429,60 @@ void remove_filter (SPObject *item, bool recursive) 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); + } + } +} + +bool filter_is_single_gaussian_blur(SPFilter *filter) +{ + return (SP_OBJECT(filter)->firstChild() && + SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() && + SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild())); +} + +double get_single_gaussian_blur_radius(SPFilter *filter) +{ + if (SP_OBJECT(filter)->firstChild() && + SP_OBJECT(filter)->firstChild() == SP_OBJECT(filter)->lastChild() && + SP_IS_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild())) { + + SPGaussianBlur *gb = SP_GAUSSIANBLUR(SP_OBJECT(filter)->firstChild()); + double x = gb->stdDeviation.getNumber(); + double y = gb->stdDeviation.getOptNumber(); + if (x > 0 && y > 0) { + return MAX(x, y); + } + return x; + } + return 0.0; +} + + + /* Local Variables: mode:c++ @@ -121,4 +492,4 @@ void remove_filter (SPObject *item, bool recursive) fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :