// SPDX-License-Identifier: GPL-2.0-or-later /* * Various utility methods for filters * * Authors: * Hugo Rodrigues * bulia byak * Niko Kiirala * Jon A. Cruz * Abhishek Sharma * * Copyright (C) 2006-2008 authors * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "filter-chemistry.h" #include #include #include "desktop-style.h" #include "document.h" #include "filter-enums.h" #include "style.h" #include "object/sp-defs.h" #include "object/sp-item.h" #include "object/filters/blend.h" #include "object/filters/gaussian-blur.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 = o->style; if (style && style->filter.set && style->getFilter() == filter) { i ++; } for (auto& child: o->children) { i += count_filter_hrefs(&child, filter); } return i; } SPFilter *new_filter(SPDocument *document) { g_return_val_if_fail(document != nullptr, NULL); SPDefs *defs = document->getDefs(); Inkscape::XML::Document *xml_doc = document->getReprDoc(); // create a new filter Inkscape::XML::Node *repr; repr = xml_doc->createElement("svg:filter"); // Inkscape now supports both sRGB and linear color-interpolation-filters. // But, for the moment, keep sRGB as default value for new filters // (historically set to sRGB and doesn't require conversion between // filter cairo surfaces and other types of cairo surfaces). SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_set_property(css, "color-interpolation-filters", "sRGB"); sp_repr_css_change(repr, css, "style"); sp_repr_css_attr_unref(css); // Append the new filter node to defs defs->appendChild(repr); Inkscape::GC::release(repr); // get corresponding object SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) ); g_assert(f != nullptr); g_assert(SP_IS_FILTER(f)); return f; } SPFilterPrimitive * filter_add_primitive(SPFilter *filter, const Inkscape::Filters::FilterPrimitiveType type) { Inkscape::XML::Document *xml_doc = filter->document->getReprDoc(); //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("mode", "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 // XML tree being used directly while/where it shouldn't be... filter->appendChild(repr); Inkscape::GC::release(repr); // get corresponding object SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE( filter->document->getObjectByRepr(repr) ); g_assert(prim != nullptr); 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) { g_return_val_if_fail(document != nullptr, NULL); SPDefs *defs = document->getDefs(); Inkscape::XML::Document *xml_doc = document->getReprDoc(); // create a new filter Inkscape::XML::Node *repr; repr = xml_doc->createElement("svg:filter"); //repr->setAttribute("inkscape:collect", "always"); /* Inkscape now supports both sRGB and linear color-interpolation-filters. * But, for the moment, keep sRGB as default value for new filters. * historically set to sRGB and doesn't require conversion between * filter cairo surfaces and other types of cairo surfaces. lp:1127103 */ SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_set_property(css, "color-interpolation-filters", "sRGB"); sp_repr_css_change(repr, css, "style"); sp_repr_css_attr_unref(css); //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 b_repr->setAttributeSvgDouble("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 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 != nullptr); g_assert(SP_IS_FILTER(f)); g_assert(b != nullptr); 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 */ static SPFilter * new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdouble radius, double expansion) { g_return_val_if_fail(document != nullptr, NULL); SPDefs *defs = document->getDefs(); Inkscape::XML::Document *xml_doc = document->getReprDoc(); // create a new filter Inkscape::XML::Node *repr; repr = xml_doc->createElement("svg:filter"); repr->setAttribute("inkscape:collect", "always"); /* Inkscape now supports both sRGB and linear color-interpolation-filters. * But, for the moment, keep sRGB as default value for new filters. * historically set to sRGB and doesn't require conversion between * filter cairo surfaces and other types of cairo surfaces. lp:1127103 */ SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_set_property(css, "color-interpolation-filters", "sRGB"); sp_repr_css_change(repr, css, "style"); sp_repr_css_attr_unref(css); // Append the new filter node to defs defs->appendChild(repr); Inkscape::GC::release(repr); // get corresponding object SPFilter *f = SP_FILTER( document->getObjectByRepr(repr) ); // Gaussian blur primitive if(radius != 0) { //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 b_repr->setAttributeSvgDouble("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 != nullptr); 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 != nullptr); g_assert(SP_IS_FEBLEND(b)); } g_assert(f != nullptr); 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) { return new_filter_blend_gaussian_blur(document, mode, radius, item->i2dt_affine().descrim()); } /** * 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 = item->style->getFilter(); if (!filter) { // We reach here when filter.set is true, but the href is not found in the document return new_filter_simple_from_item(document, item, "normal", radius); } Inkscape::XML::Document *xml_doc = document->getReprDoc(); // If there are more users for this filter, duplicate it if (filter->hrefcount > count_filter_hrefs(item, filter)) { Inkscape::XML::Node *repr = item->style->getFilter()->getRepr()->duplicate(xml_doc); SPDefs *defs = document->getDefs(); defs->appendChild(repr); filter = SP_FILTER( document->getObjectByRepr(repr) ); Inkscape::GC::release(repr); } // Determine the required standard deviation value Geom::Affine i2d (item->i2dt_affine ()); double expansion = i2d.descrim(); double stdDeviation = radius; if (expansion != 0) stdDeviation /= expansion; // Set the filter effects area SPFilter *f = item->style->getFilter(); Inkscape::XML::Node *repr = f->getRepr(); // 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) { primitive->setAttributeSvgDouble("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 b_repr->setAttributeSvgDouble("stdDeviation", stdDeviation); //set feGaussianBlur as child of filter node filter->getRepr()->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(item->getRepr(), css, "style"); } else { sp_repr_css_change(item->getRepr(), css, "style"); } sp_repr_css_attr_unref(css); } void remove_hidder_filter (SPObject *item) { SPFilter *filt = item->style->getFilter(); if (filt && filt->getId()) { Glib::ustring filter = filt->getId(); if (!filter.rfind("selectable_hidder_filter", 0)) { remove_filter(item, false); } } } bool has_hidder_filter(SPObject *item) { SPFilter *filt = item->style->getFilter(); if (filt && filt->getId()) { Glib::ustring filter = filt->getId(); if (!filter.rfind("selectable_hidder_filter", 0)) { return true; } } return false; } /** * 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 = item->style->getFilter()->getRepr(); 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); } } } /** * Removes blend primitive from the filter attached to given item. * Get if the filter have a < 1.0 blending filter and if it remove it * @params: the item to remove filtered blend */ /* 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_legacy_blend(SPObject *item) { if (!item) { return; } if (item->style && item->style->filter.set && item->style->getFilter()) { // Search for the first blur primitive and remove it. (if found) size_t blurcount = 0; size_t blendcount = 0; size_t total = 0; // determine whether filter is simple (blend and/or blur) or complex SPFeBlend *blend = nullptr; for (auto &primitive_obj:item->style->getFilter()->children) { SPFilterPrimitive *primitive = dynamic_cast(&primitive_obj); if (primitive) { if (dynamic_cast(primitive)) { blend = dynamic_cast(primitive); ++blendcount; } SPGaussianBlur *spgausian = dynamic_cast(primitive); if (spgausian) { ++blurcount; } ++total; } } if (blend && total == 2 && blurcount == 1) { blend->deleteObject(true); } else if (total == 1 && blurcount != 1) { remove_filter(item, false); } } } /** * Get if the filter have a < 1.0 blending filter * @params: the item to get filtered blend */ SPBlendMode filter_get_legacy_blend(SPObject *item) { auto blend = SP_CSS_BLEND_NORMAL; if (!item) { return blend; } if (item->style && item->style->filter.set && item->style->getFilter()) { // Search for the first blur primitive and remove it. (if found) size_t blurcount = 0; size_t blendcount = 0; size_t total = 0; // determine whether filter is simple (blend and/or blur) or complex for (auto &primitive_obj:item->style->getFilter()->children) { SPFilterPrimitive *primitive = dynamic_cast(&primitive_obj); if (primitive) { SPFeBlend *spblend = dynamic_cast(primitive); if (spblend) { ++blendcount; blend = spblend->blend_mode; } SPGaussianBlur *spgausian = dynamic_cast(primitive); if (spgausian) { ++blurcount; } ++total; } } if (!((blend && total == 2 && blurcount == 1) || total == 1)) { blend = SP_CSS_BLEND_NORMAL; } } return blend; } bool filter_is_single_gaussian_blur(SPFilter *filter) { return (filter->children.size() == 1 && SP_IS_GAUSSIANBLUR(&filter->children.front())); } double get_single_gaussian_blur_radius(SPFilter *filter) { if (filter->children.size() == 1 && SP_IS_GAUSSIANBLUR(&filter->children.front())) { SPGaussianBlur *gb = SP_GAUSSIANBLUR(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++ 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:fileencoding=utf-8:textwidth=99 :