1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Authors:
4 * Ted Gould <ted@gould.cx>
5 *
6 * Copyright (C) 2008 Authors
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11 #include "desktop.h"
12 #include "selection.h"
13 #include "extension/extension.h"
14 #include "extension/effect.h"
15 #include "extension/system.h"
16 #include "xml/repr.h"
17 #include "xml/simple-node.h"
18 #include "xml/attribute-record.h"
19 #include "object/sp-defs.h"
20
21 #include "filter.h"
22
23 namespace Inkscape {
24 namespace Extension {
25 namespace Internal {
26 namespace Filter {
27
Filter()28 Filter::Filter() :
29 Inkscape::Extension::Implementation::Implementation(),
30 _filter(nullptr) {
31 return;
32 }
33
Filter(gchar const * filter)34 Filter::Filter(gchar const * filter) :
35 Inkscape::Extension::Implementation::Implementation(),
36 _filter(filter) {
37 return;
38 }
39
~Filter()40 Filter::~Filter () {
41 if (_filter != nullptr) {
42 _filter = nullptr;
43 }
44
45 return;
46 }
47
load(Inkscape::Extension::Extension *)48 bool Filter::load(Inkscape::Extension::Extension * /*module*/)
49 {
50 return true;
51 }
52
newDocCache(Inkscape::Extension::Extension *,Inkscape::UI::View::View *)53 Inkscape::Extension::Implementation::ImplementationDocumentCache *Filter::newDocCache(Inkscape::Extension::Extension * /*ext*/,
54 Inkscape::UI::View::View * /*doc*/)
55 {
56 return nullptr;
57 }
58
get_filter_text(Inkscape::Extension::Extension *)59 gchar const *Filter::get_filter_text(Inkscape::Extension::Extension * /*ext*/)
60 {
61 return _filter;
62 }
63
64 Inkscape::XML::Document *
get_filter(Inkscape::Extension::Extension * ext)65 Filter::get_filter (Inkscape::Extension::Extension * ext) {
66 gchar const * filter = get_filter_text(ext);
67 return sp_repr_read_mem(filter, strlen(filter), nullptr);
68 }
69
70 void
merge_filters(Inkscape::XML::Node * to,Inkscape::XML::Node * from,Inkscape::XML::Document * doc,gchar const * srcGraphic,gchar const * srcGraphicAlpha)71 Filter::merge_filters( Inkscape::XML::Node * to, Inkscape::XML::Node * from,
72 Inkscape::XML::Document * doc,
73 gchar const * srcGraphic, gchar const * srcGraphicAlpha)
74 {
75 if (from == nullptr) return;
76
77 // copy attributes
78 for ( const auto & iter : from->attributeList()) {
79 gchar const * attr = g_quark_to_string(iter.key);
80 //printf("Attribute List: %s\n", attr);
81 if (!strcmp(attr, "id")) continue; // nope, don't copy that one!
82 to->setAttribute(attr, from->attribute(attr));
83
84 if (!strcmp(attr, "in") || !strcmp(attr, "in2") || !strcmp(attr, "in3")) {
85 if (srcGraphic != nullptr && !strcmp(from->attribute(attr), "SourceGraphic")) {
86 to->setAttribute(attr, srcGraphic);
87 }
88
89 if (srcGraphicAlpha != nullptr && !strcmp(from->attribute(attr), "SourceAlpha")) {
90 to->setAttribute(attr, srcGraphicAlpha);
91 }
92 }
93 }
94
95 // for each child call recursively
96 for (Inkscape::XML::Node * from_child = from->firstChild();
97 from_child != nullptr ; from_child = from_child->next()) {
98 Glib::ustring name = "svg:";
99 name += from_child->name();
100
101 Inkscape::XML::Node * to_child = doc->createElement(name.c_str());
102 to->appendChild(to_child);
103 merge_filters(to_child, from_child, doc, srcGraphic, srcGraphicAlpha);
104
105 if (from_child == from->firstChild() && !strcmp("filter", from->name()) && srcGraphic != nullptr && to_child->attribute("in") == nullptr) {
106 to_child->setAttribute("in", srcGraphic);
107 }
108 Inkscape::GC::release(to_child);
109 }
110 }
111
112 #define FILTER_SRC_GRAPHIC "fbSourceGraphic"
113 #define FILTER_SRC_GRAPHIC_ALPHA "fbSourceGraphicAlpha"
114
effect(Inkscape::Extension::Effect * module,Inkscape::UI::View::View * document,Inkscape::Extension::Implementation::ImplementationDocumentCache *)115 void Filter::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document,
116 Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
117 {
118 Inkscape::XML::Document *filterdoc = get_filter(module);
119 if (filterdoc == nullptr) {
120 return; // could not parse the XML source of the filter; typically parser will stderr a warning
121 }
122
123 //printf("Calling filter effect\n");
124 Inkscape::Selection * selection = ((SPDesktop *)document)->selection;
125
126 // TODO need to properly refcount the items, at least
127 std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
128
129 Inkscape::XML::Document * xmldoc = document->doc()->getReprDoc();
130 Inkscape::XML::Node * defsrepr = document->doc()->getDefs()->getRepr();
131
132 for(auto spitem : items) {
133 Inkscape::XML::Node *node = spitem->getRepr();
134
135 SPCSSAttr * css = sp_repr_css_attr(node, "style");
136 gchar const * filter = sp_repr_css_property(css, "filter", nullptr);
137
138 if (filter == nullptr) {
139
140 Inkscape::XML::Node * newfilterroot = xmldoc->createElement("svg:filter");
141 merge_filters(newfilterroot, filterdoc->root(), xmldoc);
142 defsrepr->appendChild(newfilterroot);
143 document->doc()->resources_changed_signals[g_quark_from_string("filter")].emit();
144
145 Glib::ustring url = "url(#"; url += newfilterroot->attribute("id"); url += ")";
146
147
148 Inkscape::GC::release(newfilterroot);
149
150 sp_repr_css_set_property(css, "filter", url.c_str());
151 sp_repr_css_set(node, css, "style");
152 } else {
153 if (strncmp(filter, "url(#", strlen("url(#")) || filter[strlen(filter) - 1] != ')') {
154 // This is not url(#id) -- we can't handle it
155 continue;
156 }
157
158 gchar * lfilter = g_strndup(filter + 5, strlen(filter) - 6);
159 Inkscape::XML::Node * filternode = nullptr;
160 for (Inkscape::XML::Node * child = defsrepr->firstChild(); child != nullptr; child = child->next()) {
161 if (!strcmp(lfilter, child->attribute("id"))) {
162 filternode = child;
163 break;
164 }
165 }
166 g_free(lfilter);
167
168 // no filter
169 if (filternode == nullptr) {
170 g_warning("no assigned filter found!");
171 continue;
172 }
173
174 if (filternode->lastChild() == nullptr) {
175 // empty filter, we insert
176 merge_filters(filternode, filterdoc->root(), xmldoc);
177 } else {
178 // existing filter, we merge
179 filternode->lastChild()->setAttribute("result", FILTER_SRC_GRAPHIC);
180 Inkscape::XML::Node * alpha = xmldoc->createElement("svg:feColorMatrix");
181 alpha->setAttribute("result", FILTER_SRC_GRAPHIC_ALPHA);
182 alpha->setAttribute("in", FILTER_SRC_GRAPHIC); // not required, but we're being explicit
183 alpha->setAttribute("values", "0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0");
184
185 filternode->appendChild(alpha);
186
187 merge_filters(filternode, filterdoc->root(), xmldoc, FILTER_SRC_GRAPHIC, FILTER_SRC_GRAPHIC_ALPHA);
188
189 Inkscape::GC::release(alpha);
190 }
191 }
192 }
193
194 return;
195 }
196
197 #include "extension/internal/clear-n_.h"
198
199 void
filter_init(gchar const * id,gchar const * name,gchar const * submenu,gchar const * tip,gchar const * filter)200 Filter::filter_init (gchar const * id, gchar const * name, gchar const * submenu, gchar const * tip, gchar const * filter)
201 {
202 // clang-format off
203 gchar * xml_str = g_strdup_printf(
204 "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
205 "<name>%s</name>\n"
206 "<id>org.inkscape.effect.filter.%s</id>\n"
207 "<effect>\n"
208 "<object-type>all</object-type>\n"
209 "<effects-menu>\n"
210 "<submenu name=\"" N_("Filters") "\" />\n"
211 "<submenu name=\"%s\"/>\n"
212 "</effects-menu>\n"
213 "<menu-tip>%s</menu-tip>\n"
214 "</effect>\n"
215 "</inkscape-extension>\n", name, id, submenu, tip);
216 // clang-format on
217 Inkscape::Extension::build_from_mem(xml_str, new Filter(filter));
218 g_free(xml_str);
219 return;
220 }
221
222 }; /* namespace Filter */
223 }; /* namespace Internal */
224 }; /* namespace Extension */
225 }; /* namespace Inkscape */
226
227 /*
228 Local Variables:
229 mode:c++
230 c-file-style:"stroustrup"
231 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
232 indent-tabs-mode:nil
233 fill-column:99
234 End:
235 */
236 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
237