From cca66b9ec4e494c1d919bff0f71a820d8afab1fa Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:24:48 +0200 Subject: Adding upstream version 1.2.2. Signed-off-by: Daniel Baumann --- src/object/sp-filter.cpp | 634 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 634 insertions(+) create mode 100644 src/object/sp-filter.cpp (limited to 'src/object/sp-filter.cpp') diff --git a/src/object/sp-filter.cpp b/src/object/sp-filter.cpp new file mode 100644 index 0000000..9ae0e16 --- /dev/null +++ b/src/object/sp-filter.cpp @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * SVG implementation. + */ +/* + * Authors: + * Hugo Rodrigues + * Niko Kiirala + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2006,2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "sp-filter.h" + +#include +#include +#include +#include + +#include +#include <2geom/transforms.h> + +#include "bad-uri-exception.h" +#include "attributes.h" +#include "display/nr-filter.h" +#include "document.h" +#include "sp-filter-reference.h" +#include "filters/sp-filter-primitive.h" +#include "uri.h" +#include "xml/repr.h" + +static void filter_ref_changed(SPObject *old_ref, SPObject *ref, SPFilter *filter); +static void filter_ref_modified(SPObject *href, guint flags, SPFilter *filter); + + +SPFilter::SPFilter() + : SPObject(), filterUnits(SP_FILTER_UNITS_OBJECTBOUNDINGBOX), filterUnits_set(FALSE), + primitiveUnits(SP_FILTER_UNITS_USERSPACEONUSE), primitiveUnits_set(FALSE), + filterRes(NumberOptNumber()), + _renderer(nullptr), _image_name(new std::map), _image_number_next(0) +{ + this->href = new SPFilterReference(this); + this->href->changedSignal().connect(sigc::bind(sigc::ptr_fun(filter_ref_changed), this)); + + this->x = 0; + this->y = 0; + this->width = 0; + this->height = 0; + this->auto_region = true; + + this->_image_name->clear(); +} + +SPFilter::~SPFilter() = default; + + +/** + * Reads the Inkscape::XML::Node, and initializes SPFilter variables. For this to get called, + * our name must be associated with a repr via "sp_object_type_register". Best done through + * sp-object-repr.cpp's repr_name_entries array. + */ +void SPFilter::build(SPDocument *document, Inkscape::XML::Node *repr) { + //Read values of key attributes from XML nodes into object. + this->readAttr(SPAttr::STYLE); // struct not derived from SPItem, we need to do this ourselves. + this->readAttr(SPAttr::FILTERUNITS); + this->readAttr(SPAttr::PRIMITIVEUNITS); + this->readAttr(SPAttr::X); + this->readAttr(SPAttr::Y); + this->readAttr(SPAttr::WIDTH); + this->readAttr(SPAttr::HEIGHT); + this->readAttr(SPAttr::AUTO_REGION); + this->readAttr(SPAttr::FILTERRES); + this->readAttr(SPAttr::XLINK_HREF); + this->_refcount = 0; + + SPObject::build(document, repr); + +//is this necessary? + document->addResource("filter", this); +} + +/** + * Drops any allocated memory. + */ +void SPFilter::release() { + if (this->document) { + // Unregister ourselves + this->document->removeResource("filter", this); + } + +//TODO: release resources here + + //release href + if (this->href) { + this->modified_connection.disconnect(); + this->href->detach(); + delete this->href; + this->href = nullptr; + } + + for (std::map::const_iterator i = this->_image_name->begin() ; i != this->_image_name->end() ; ++i) { + g_free(i->first); + } + + delete this->_image_name; + + SPObject::release(); +} + +/** + * Sets a specific value in the SPFilter. + */ +void SPFilter::set(SPAttr key, gchar const *value) { + switch (key) { + case SPAttr::FILTERUNITS: + if (value) { + if (!strcmp(value, "userSpaceOnUse")) { + this->filterUnits = SP_FILTER_UNITS_USERSPACEONUSE; + } else { + this->filterUnits = SP_FILTER_UNITS_OBJECTBOUNDINGBOX; + } + + this->filterUnits_set = TRUE; + } else { + this->filterUnits = SP_FILTER_UNITS_OBJECTBOUNDINGBOX; + this->filterUnits_set = FALSE; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SPAttr::PRIMITIVEUNITS: + if (value) { + if (!strcmp(value, "objectBoundingBox")) { + this->primitiveUnits = SP_FILTER_UNITS_OBJECTBOUNDINGBOX; + } else { + this->primitiveUnits = SP_FILTER_UNITS_USERSPACEONUSE; + } + + this->primitiveUnits_set = TRUE; + } else { + this->primitiveUnits = SP_FILTER_UNITS_USERSPACEONUSE; + this->primitiveUnits_set = FALSE; + } + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SPAttr::X: + this->x.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SPAttr::Y: + this->y.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SPAttr::WIDTH: + this->width.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SPAttr::HEIGHT: + this->height.readOrUnset(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SPAttr::AUTO_REGION: + this->auto_region = (!value || strcmp(value, "false")); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SPAttr::FILTERRES: + this->filterRes.set(value); + this->requestModified(SP_OBJECT_MODIFIED_FLAG); + break; + case SPAttr::XLINK_HREF: + if (value) { + try { + this->href->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + this->href->detach(); + } + } else { + this->href->detach(); + } + break; + default: + // See if any parents need this value. + SPObject::set(key, value); + break; + } +} + + +/** + * Returns the number of references to the filter. + */ +guint SPFilter::getRefCount() { + // NOTE: this is currently updated by sp_style_filter_ref_changed() in style.cpp + return _refcount; +} + +void SPFilter::modified(guint flags) { + // We are not an LPE, do not update filter regions on load. + if (flags & SP_OBJECT_MODIFIED_FLAG) { + update_filter_all_regions(); + } +} + +/** + * Receives update notifications. + */ +void SPFilter::update(SPCtx *ctx, guint flags) { + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + SPItemCtx *ictx = (SPItemCtx *) ctx; + + // Do here since we know viewport (Bounding box case handled during rendering) + // Note: This only works for root viewport since this routine is not called after + // setting a new viewport. A true fix requires a strategy like SPItemView or SPMarkerView. + if(this->filterUnits == SP_FILTER_UNITS_USERSPACEONUSE) { + this->calcDimsFromParentViewport(ictx, true); + } + /* do something to trigger redisplay, updates? */ + + } + + // Update filter primitives in order to update filter primitive area + // (SPObject::ActionUpdate is not actually used) + unsigned childflags = flags; + + if (flags & SP_OBJECT_MODIFIED_FLAG) { + childflags |= SP_OBJECT_PARENT_MODIFIED_FLAG; + } + childflags &= SP_OBJECT_MODIFIED_CASCADE; + std::vector l(this->childList(true, SPObject::ActionUpdate)); + for(SPObject* child: l){ + if( SP_IS_FILTER_PRIMITIVE( child ) ) { + child->updateDisplay(ctx, childflags); + } + sp_object_unref(child); + } + + SPObject::update(ctx, flags); +} + +/** + * Writes its settings to an incoming repr object, if any. + */ +Inkscape::XML::Node* SPFilter::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { + // Original from sp-item-group.cpp + if (flags & SP_OBJECT_WRITE_BUILD) { + if (!repr) { + repr = doc->createElement("svg:filter"); + } + + std::vector l; + for (auto& child: children) { + Inkscape::XML::Node *crepr = child.updateRepr(doc, nullptr, flags); + + if (crepr) { + l.push_back(crepr); + } + } + + for (auto i=l.rbegin();i!=l.rend();++i) { + repr->addChild(*i, nullptr); + Inkscape::GC::release(*i); + } + } else { + for (auto& child: children) { + child.updateRepr(flags); + } + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->filterUnits_set) { + switch (this->filterUnits) { + case SP_FILTER_UNITS_USERSPACEONUSE: + repr->setAttribute("filterUnits", "userSpaceOnUse"); + break; + default: + repr->setAttribute("filterUnits", "objectBoundingBox"); + break; + } + } + + if ((flags & SP_OBJECT_WRITE_ALL) || this->primitiveUnits_set) { + switch (this->primitiveUnits) { + case SP_FILTER_UNITS_OBJECTBOUNDINGBOX: + repr->setAttribute("primitiveUnits", "objectBoundingBox"); + break; + default: + repr->setAttribute("primitiveUnits", "userSpaceOnUse"); + break; + } + } + + if (this->x._set) { + repr->setAttributeSvgDouble("x", this->x.computed); + } else { + repr->removeAttribute("x"); + } + + if (this->y._set) { + repr->setAttributeSvgDouble("y", this->y.computed); + } else { + repr->removeAttribute("y"); + } + + if (this->width._set) { + repr->setAttributeSvgDouble("width", this->width.computed); + } else { + repr->removeAttribute("width"); + } + + if (this->height._set) { + repr->setAttributeSvgDouble("height", this->height.computed); + } else { + repr->removeAttribute("height"); + } + + if (this->filterRes.getNumber()>=0) { + auto tmp = this->filterRes.getValueString(); + repr->setAttribute("filterRes", tmp); + } else { + repr->removeAttribute("filterRes"); + } + + if (this->href->getURI()) { + auto uri_string = this->href->getURI()->str(); + repr->setAttributeOrRemoveIfEmpty("xlink:href", uri_string); + } + + SPObject::write(doc, repr, flags); + + return repr; +} + +/** + * Update the filter's region based on it's detectable href links + * + * Automatic region only updated if auto_region is false + * and filterUnits is not UserSpaceOnUse + */ +void SPFilter::update_filter_all_regions() +{ + if (!this->auto_region || this->filterUnits == SP_FILTER_UNITS_USERSPACEONUSE) + return; + + // Combine all items into one region for updating. + Geom::OptRect opt_r; + for (auto & obj : this->hrefList) { + SPItem *item = dynamic_cast(obj); + opt_r.unionWith(this->get_automatic_filter_region(item)); + } + if (opt_r) { + Geom::Rect region = *opt_r; + this->set_filter_region(region.left(), region.top(), region.width(), region.height()); + } +} + +/** + * Update the filter region based on the object's bounding box + * + * @param item - The item who's coords are used as the basis for the area. + */ +void SPFilter::update_filter_region(SPItem *item) +{ + if (!this->auto_region || this->filterUnits == SP_FILTER_UNITS_USERSPACEONUSE) + return; // No adjustment for dead box + + auto region = this->get_automatic_filter_region(item); + + // Set the filter region into this filter object + this->set_filter_region(region.left(), region.top(), region.width(), region.height()); +} + +/** + * Generate a filter region based on the item and return it. + * + * @param item - The item who's coords are used as the basis for the area. + */ +Geom::Rect SPFilter::get_automatic_filter_region(SPItem *item) +{ + // Calling bbox instead of visualBound() avoids re-requesting filter regions + Geom::OptRect v_box = item->bbox(Geom::identity(), SPItem::VISUAL_BBOX); + Geom::OptRect g_box = item->bbox(Geom::identity(), SPItem::GEOMETRIC_BBOX); + if (!v_box || !g_box) return Geom::Rect(); // No adjustment for dead box + + // Because the filter box is in geometric bounding box units, it must ALSO + // take account of the visualBox, so even if the filter does NOTHING to the + // size of an object, we must add the difference between the geometric and + // visual boxes ourselves or find them cut off by renderers of all kinds. + Geom::Rect inbox = *g_box; + Geom::Rect outbox = *v_box; + for(auto& primitive_obj: this->children) { + auto primitive = dynamic_cast(&primitive_obj); + if (primitive) { + // Update the region with the primitive's options + outbox = primitive->calculate_region(outbox); + } + } + + // Include the original visual bounding-box in the result + outbox.unionWith(v_box); + // Scale outbox to width/height scale of input, this scales the geometric + // into the visual bounding box requiring any changes to it to re-run this. + outbox *= Geom::Translate(-inbox.left(), -inbox.top()); + outbox *= Geom::Scale(1/inbox.width(), 1/inbox.height()); + return outbox; +} + +/** + * Set the filter region attributes from a bounding box + */ +void SPFilter::set_filter_region(double x, double y, double width, double height) +{ + if (width != 0 && height != 0) + { + // TODO: set it in UserSpaceOnUse instead? + auto repr = this->getRepr(); + repr->setAttributeSvgDouble("x", x); + repr->setAttributeSvgDouble("y", y); + repr->setAttributeSvgDouble("width", width); + repr->setAttributeSvgDouble("height", height); + } +} + +/** + * Check each filter primitive for conflicts with this object. + */ +bool SPFilter::valid_for(SPObject const *obj) const +{ + for(auto& primitive_obj: this->children) { + auto primitive = dynamic_cast(&primitive_obj); + if (primitive && !primitive->valid_for(obj)) { + return false; + } + } + return true; +} + +/** + * Gets called when the filter is (re)attached to another filter. + */ +static void +filter_ref_changed(SPObject *old_ref, SPObject *ref, SPFilter *filter) +{ + if (old_ref) { + filter->modified_connection.disconnect(); + } + + if ( SP_IS_FILTER(ref) + && ref != filter ) + { + filter->modified_connection = + ref->connectModified(sigc::bind(sigc::ptr_fun(&filter_ref_modified), filter)); + } + + filter_ref_modified(ref, 0, filter); +} + +static void filter_ref_modified(SPObject */*href*/, guint /*flags*/, SPFilter *filter) +{ + filter->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for child_added event. + */ +void SPFilter::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { + SPObject::child_added(child, ref); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +/** + * Callback for remove_child event. + */ +void SPFilter::remove_child(Inkscape::XML::Node *child) { + SPObject::remove_child(child); + + this->requestModified(SP_OBJECT_MODIFIED_FLAG); +} + +void SPFilter::build_renderer(Inkscape::Filters::Filter *nr_filter) +{ + g_assert(nr_filter != nullptr); + + this->_renderer = nr_filter; + + nr_filter->set_filter_units(this->filterUnits); + nr_filter->set_primitive_units(this->primitiveUnits); + nr_filter->set_x(this->x); + nr_filter->set_y(this->y); + nr_filter->set_width(this->width); + nr_filter->set_height(this->height); + + if (this->filterRes.getNumber() >= 0) { + if (this->filterRes.getOptNumber() >= 0) { + nr_filter->set_resolution(this->filterRes.getNumber(), + this->filterRes.getOptNumber()); + } else { + nr_filter->set_resolution(this->filterRes.getNumber()); + } + } + + nr_filter->clear_primitives(); + for(auto& primitive_obj: this->children) { + if (SP_IS_FILTER_PRIMITIVE(&primitive_obj)) { + SPFilterPrimitive *primitive = SP_FILTER_PRIMITIVE(&primitive_obj); + g_assert(primitive != nullptr); + +// if (((SPFilterPrimitiveClass*) G_OBJECT_GET_CLASS(primitive))->build_renderer) { +// ((SPFilterPrimitiveClass *) G_OBJECT_GET_CLASS(primitive))->build_renderer(primitive, nr_filter); +// } else { +// g_warning("Cannot build filter renderer: missing builder"); +// } // CPPIFY: => FilterPrimitive should be abstract. + primitive->build_renderer(nr_filter); + } + } +} + +int SPFilter::primitive_count() const { + int count = 0; + + for(const auto& primitive_obj: this->children) { + if (SP_IS_FILTER_PRIMITIVE(&primitive_obj)) { + count++; + } + } + + return count; +} + +int SPFilter::get_image_name(gchar const *name) const { + std::map::iterator result = this->_image_name->find(const_cast(name)); + if (result == this->_image_name->end()) return -1; + else return (*result).second; +} + +int SPFilter::set_image_name(gchar const *name) { + int value = this->_image_number_next; + this->_image_number_next++; + gchar *name_copy = strdup(name); + std::pair new_pair(name_copy, value); + const std::pair::iterator,bool> ret = this->_image_name->insert(new_pair); + if (ret.second == false) { + // The element is not inserted (because an element with the same key was already in the map) + // Therefore, free the memory allocated for the new entry: + free(name_copy); + + return (*ret.first).second; + } + return value; +} + +gchar const *SPFilter::name_for_image(int const image) const { + switch (image) { + case Inkscape::Filters::NR_FILTER_SOURCEGRAPHIC: + return "SourceGraphic"; + break; + case Inkscape::Filters::NR_FILTER_SOURCEALPHA: + return "SourceAlpha"; + break; + case Inkscape::Filters::NR_FILTER_BACKGROUNDIMAGE: + return "BackgroundImage"; + break; + case Inkscape::Filters::NR_FILTER_BACKGROUNDALPHA: + return "BackgroundAlpha"; + break; + case Inkscape::Filters::NR_FILTER_STROKEPAINT: + return "StrokePaint"; + break; + case Inkscape::Filters::NR_FILTER_FILLPAINT: + return "FillPaint"; + break; + case Inkscape::Filters::NR_FILTER_SLOT_NOT_SET: + case Inkscape::Filters::NR_FILTER_UNNAMED_SLOT: + return nullptr; + break; + default: + for (std::map::const_iterator i + = this->_image_name->begin() ; + i != this->_image_name->end() ; ++i) { + if (i->second == image) { + return i->first; + } + } + } + return nullptr; +} + +Glib::ustring SPFilter::get_new_result_name() const { + int largest = 0; + + for(const auto& primitive_obj: this->children) { + if (SP_IS_FILTER_PRIMITIVE(&primitive_obj)) { + const Inkscape::XML::Node *repr = primitive_obj.getRepr(); + char const *result = repr->attribute("result"); + int index; + if (result) + { + if (sscanf(result, "result%5d", &index) == 1) + { + if (index > largest) + { + largest = index; + } + } + } + } + } + + return "result" + Glib::Ascii::dtostr(largest + 1); +} + +bool ltstr::operator()(const char* s1, const char* s2) const +{ + return strcmp(s1, s2) < 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 : -- cgit v1.2.3