1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
4 */
5 #include "live_effects/lpe-powermask.h"
6 #include "live_effects/lpeobject.h"
7 #include "live_effects/lpeobject-reference.h"
8 #include <2geom/path-intersection.h>
9 #include <2geom/intersection-graph.h>
10 #include "display/curve.h"
11 #include "helper/geom.h"
12 #include "svg/svg.h"
13 #include "svg/svg-color.h"
14 #include "svg/stringstream.h"
15 #include "ui/tools-switch.h"
16 #include "path-chemistry.h"
17 #include "extract-uri.h"
18 #include <bad-uri-exception.h>
19
20 #include "object/sp-mask.h"
21 #include "object/sp-path.h"
22 #include "object/sp-shape.h"
23 #include "object/sp-defs.h"
24 #include "object/sp-item-group.h"
25 #include "object/uri.h"
26
27
28 // TODO due to internal breakage in glibmm headers, this must be last:
29 #include <glibmm/i18n.h>
30
31 namespace Inkscape {
32 namespace LivePathEffect {
33
LPEPowerMask(LivePathEffectObject * lpeobject)34 LPEPowerMask::LPEPowerMask(LivePathEffectObject *lpeobject)
35 : Effect(lpeobject),
36 uri("Store the uri of mask", "", "uri", &wr, this, "false", false),
37 invert(_("Invert mask"), _("Invert mask"), "invert", &wr, this, false),
38 //wrap(_("Wrap mask data"), _("Wrap mask data allowing previous filters"), "wrap", &wr, this, false),
39 hide_mask(_("Hide mask"), _("Hide mask"), "hide_mask", &wr, this, false),
40 background(_("Add background to mask"), _("Add background to mask"), "background", &wr, this, false),
41 background_color(_("Background color and opacity"), _("Set color and opacity of the background"), "background_color", &wr, this, 0xffffffff)
42 {
43 registerParameter(&uri);
44 registerParameter(&invert);
45 registerParameter(&hide_mask);
46 registerParameter(&background);
47 registerParameter(&background_color);
48 previous_color = background_color.get_value();
49 }
50
51 LPEPowerMask::~LPEPowerMask() = default;
52
getId()53 Glib::ustring LPEPowerMask::getId() { return Glib::ustring("mask-powermask-") + Glib::ustring(getLPEObj()->getId()); }
54
55 void
doOnApply(SPLPEItem const * lpeitem)56 LPEPowerMask::doOnApply (SPLPEItem const * lpeitem)
57 {
58 SPLPEItem *item = const_cast<SPLPEItem*>(lpeitem);
59 SPObject * mask = item->getMaskObject();
60 bool hasit = false;
61 if (lpeitem->hasPathEffect() && lpeitem->pathEffectsEnabled()) {
62 PathEffectList path_effect_list(*lpeitem->path_effect_list);
63 for (auto &lperef : path_effect_list) {
64 LivePathEffectObject *lpeobj = lperef->lpeobject;
65 if (!lpeobj) {
66 /** \todo Investigate the cause of this.
67 * For example, this happens when copy pasting an object with LPE applied. Probably because the object is pasted while the effect is not yet pasted to defs, and cannot be found.
68 */
69 g_warning("SPLPEItem::performPathEffect - NULL lpeobj in list!");
70 return;
71 }
72 if (LPETypeConverter.get_key(lpeobj->effecttype) == "powermask") {
73 hasit = true;
74 break;
75 }
76 }
77 }
78 if (!mask || hasit) {
79 item->removeCurrentPathEffect(false);
80 } else {
81 Glib::ustring newmask = getId();
82 Glib::ustring uri = Glib::ustring("url(#") + newmask + Glib::ustring(")");
83 mask->setAttribute("id", newmask);
84 item->setAttribute("mask", uri);
85 }
86 }
87
tryForkMask()88 void LPEPowerMask::tryForkMask()
89 {
90 SPDocument *document = getSPDoc();
91 if (!document || !sp_lpe_item) {
92 return;
93 }
94 SPObject *mask = sp_lpe_item->getMaskObject();
95 SPObject *elemref = document->getObjectById(getId().c_str());
96 if (!elemref && sp_lpe_item && mask) {
97 Glib::ustring newmask = getId();
98 Glib::ustring uri = Glib::ustring("url(#") + newmask + Glib::ustring(")");
99 Inkscape::XML::Document *xml_doc = document->getReprDoc();
100 Inkscape::XML::Node *fork = mask->getRepr()->duplicate(xml_doc);
101 mask = SP_OBJECT(document->getDefs()->appendChildRepr(fork));
102 fork->setAttribute("id", newmask);
103 Inkscape::GC::release(fork);
104 sp_lpe_item->setAttribute("mask", uri);
105 }
106 }
107
108 void
doBeforeEffect(SPLPEItem const * lpeitem)109 LPEPowerMask::doBeforeEffect (SPLPEItem const* lpeitem){
110 //To avoid close of color dialog and better performance on change color
111 tryForkMask();
112 SPObject * mask = SP_ITEM(sp_lpe_item)->getMaskObject();
113 auto uri_str = uri.param_getSVGValue();
114 if (hide_mask && mask) {
115 SP_ITEM(sp_lpe_item)->getMaskRef().detach();
116 } else if (!hide_mask && !mask && !uri_str.empty()) {
117 SP_ITEM(sp_lpe_item)->getMaskRef().try_attach(uri_str.c_str());
118 }
119 mask = SP_ITEM(sp_lpe_item)->getMaskObject();
120 if (mask) {
121 if (previous_color != background_color.get_value()) {
122 previous_color = background_color.get_value();
123 setMask();
124 } else {
125 uri.param_setValue(Glib::ustring(extract_uri(sp_lpe_item->getRepr()->attribute("mask"))), true);
126 SP_ITEM(sp_lpe_item)->getMaskRef().detach();
127 Geom::OptRect bbox = lpeitem->visualBounds();
128 if(!bbox) {
129 return;
130 }
131 uri_str = uri.param_getSVGValue();
132 SP_ITEM(sp_lpe_item)->getMaskRef().try_attach(uri_str.c_str());
133
134 Geom::Rect bboxrect = (*bbox);
135 bboxrect.expandBy(1);
136 mask_box.clear();
137 mask_box = Geom::Path(bboxrect);
138 SPDocument *document = getSPDoc();
139 if (!document || !mask) {
140 return;
141 }
142 DocumentUndo::ScopedInsensitive tmp(document);
143 setMask();
144 }
145 } else if(!hide_mask) {
146 SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem);
147 item->removeCurrentPathEffect(false);
148 }
149 }
150
151 void
setMask()152 LPEPowerMask::setMask(){
153 SPMask *mask = SP_ITEM(sp_lpe_item)->getMaskObject();
154 SPObject *elemref = nullptr;
155 SPDocument *document = getSPDoc();
156 if (!document || !mask) {
157 return;
158 }
159 Inkscape::XML::Document *xml_doc = document->getReprDoc();
160 Inkscape::XML::Node *box = nullptr;
161 Inkscape::XML::Node *filter = nullptr;
162 SPDefs * defs = document->getDefs();
163 Glib::ustring mask_id = getId();
164 Glib::ustring box_id = mask_id + (Glib::ustring)"_box";
165 Glib::ustring filter_id = mask_id + (Glib::ustring)"_inverse";
166 Glib::ustring filter_label = (Glib::ustring)"filter" + mask_id;
167 Glib::ustring filter_uri = (Glib::ustring)"url(#" + filter_id + (Glib::ustring)")";
168 if (!(elemref = document->getObjectById(filter_id))) {
169 filter = xml_doc->createElement("svg:filter");
170 filter->setAttribute("id", filter_id);
171 filter->setAttribute("inkscape:label", filter_label);
172 SPCSSAttr *css = sp_repr_css_attr_new();
173 sp_repr_css_set_property(css, "color-interpolation-filters", "sRGB");
174 sp_repr_css_change(filter, css, "style");
175 sp_repr_css_attr_unref(css);
176 filter->setAttribute("height", "100");
177 filter->setAttribute("width", "100");
178 filter->setAttribute("x", "-50");
179 filter->setAttribute("y", "-50");
180 Inkscape::XML::Node *primitive1 = xml_doc->createElement("svg:feColorMatrix");
181 Glib::ustring primitive1_id = (mask_id + (Glib::ustring)"_primitive1").c_str();
182 primitive1->setAttribute("id", primitive1_id);
183 primitive1->setAttribute("values", "1");
184 primitive1->setAttribute("type", "saturate");
185 primitive1->setAttribute("result", "fbSourceGraphic");
186 Inkscape::XML::Node *primitive2 = xml_doc->createElement("svg:feColorMatrix");
187 Glib::ustring primitive2_id = (mask_id + (Glib::ustring)"_primitive2").c_str();
188 primitive2->setAttribute("id", primitive2_id);
189 primitive2->setAttribute("values", "-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0 ");
190 primitive2->setAttribute("in", "fbSourceGraphic");
191 elemref = defs->appendChildRepr(filter);
192 Inkscape::GC::release(filter);
193 filter->appendChild(primitive1);
194 Inkscape::GC::release(primitive1);
195 filter->appendChild(primitive2);
196 Inkscape::GC::release(primitive2);
197 }
198 Glib::ustring g_data_id = mask_id + (Glib::ustring)"_container";
199 if((elemref = document->getObjectById(g_data_id))){
200 std::vector<SPItem*> item_list = sp_item_group_item_list(SP_GROUP(elemref));
201 for (auto iter : item_list) {
202 Inkscape::XML::Node *mask_node = iter->getRepr();
203 elemref->getRepr()->removeChild(mask_node);
204 mask->getRepr()->appendChild(mask_node);
205 Inkscape::GC::release(mask_node);
206 }
207 elemref->deleteObject(true);
208 }
209 std::vector<SPObject*> mask_list = mask->childList(true);
210 for (auto iter : mask_list) {
211 SPItem * mask_data = SP_ITEM(iter);
212 Inkscape::XML::Node *mask_node = mask_data->getRepr();
213 if (! strcmp(mask_data->getId(), box_id.c_str())){
214 continue;
215 }
216 Glib::ustring mask_data_id = (Glib::ustring)mask_data->getId();
217 SPCSSAttr *css = sp_repr_css_attr_new();
218 if(mask_node->attribute("style")) {
219 sp_repr_css_attr_add_from_string(css, mask_node->attribute("style"));
220 }
221 char const* filter = sp_repr_css_property (css, "filter", nullptr);
222 if(!filter || !strcmp(filter, filter_uri.c_str())) {
223 if (invert && is_visible) {
224 sp_repr_css_set_property (css, "filter", filter_uri.c_str());
225 } else {
226 sp_repr_css_set_property (css, "filter", nullptr);
227 }
228 Glib::ustring css_str;
229 sp_repr_css_write_string(css, css_str);
230 mask_node->setAttribute("style", css_str);
231 }
232 }
233 if ((elemref = document->getObjectById(box_id))) {
234 elemref->deleteObject(true);
235 }
236 if (background && is_visible) {
237 bool exist = true;
238 if (!(elemref = document->getObjectById(box_id))) {
239 box = xml_doc->createElement("svg:path");
240 box->setAttribute("id", box_id);
241 exist = false;
242 }
243 Glib::ustring style;
244 gchar c[32];
245 unsigned const rgb24 = background_color.get_value() >> 8;
246 sprintf(c, "#%06x", rgb24);
247 style = Glib::ustring("fill:") + Glib::ustring(c);
248 Inkscape::SVGOStringStream os;
249 os << SP_RGBA32_A_F(background_color.get_value());
250 style = style + Glib::ustring(";fill-opacity:") + Glib::ustring(os.str());
251 SPCSSAttr *css = sp_repr_css_attr_new();
252 sp_repr_css_attr_add_from_string(css, style.c_str());
253 char const* filter = sp_repr_css_property (css, "filter", nullptr);
254 if(!filter || !strcmp(filter, filter_uri.c_str())) {
255 if (invert && is_visible) {
256 sp_repr_css_set_property (css, "filter", filter_uri.c_str());
257 } else {
258 sp_repr_css_set_property (css, "filter", nullptr);
259 }
260
261 }
262 Glib::ustring css_str;
263 sp_repr_css_write_string(css, css_str);
264 box->setAttributeOrRemoveIfEmpty("style", css_str);
265 box->setAttribute("d", sp_svg_write_path(mask_box));
266 if (!exist) {
267 elemref = mask->appendChildRepr(box);
268 Inkscape::GC::release(box);
269 }
270 box->setPosition(0);
271 } else if ((elemref = document->getObjectById(box_id))) {
272 elemref->deleteObject(true);
273 }
274 mask->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
275 }
276
277 void
doOnVisibilityToggled(SPLPEItem const * lpeitem)278 LPEPowerMask::doOnVisibilityToggled(SPLPEItem const* lpeitem)
279 {
280 doBeforeEffect(lpeitem);
281 }
282
283 void
doEffect(SPCurve * curve)284 LPEPowerMask::doEffect (SPCurve * curve)
285 {
286 }
287
288 void
doOnRemove(SPLPEItem const * lpeitem)289 LPEPowerMask::doOnRemove (SPLPEItem const* lpeitem)
290 {
291 SPMask *mask = lpeitem->getMaskObject();
292 if (mask) {
293 if (keep_paths) {
294 return;
295 }
296 invert.param_setValue(false);
297 //wrap.param_setValue(false);
298 background.param_setValue(false);
299 setMask();
300 SPObject *elemref = nullptr;
301 SPDocument *document = getSPDoc();
302 Glib::ustring mask_id = getId();
303 Glib::ustring filter_id = mask_id + (Glib::ustring)"_inverse";
304 if ((elemref = document->getObjectById(filter_id))) {
305 elemref->deleteObject(true);
306 }
307 }
308 }
309
sp_inverse_powermask(Inkscape::Selection * sel)310 void sp_inverse_powermask(Inkscape::Selection *sel) {
311 if (!sel->isEmpty()) {
312 SPDocument *document = SP_ACTIVE_DOCUMENT;
313 if (!document) {
314 return;
315 }
316 auto selList = sel->items();
317 for(auto i = boost::rbegin(selList); i != boost::rend(selList); ++i) {
318 SPLPEItem* lpeitem = dynamic_cast<SPLPEItem*>(*i);
319 if (lpeitem) {
320 SPMask *mask = lpeitem->getMaskObject();
321 if (mask) {
322 Effect::createAndApply(POWERMASK, SP_ACTIVE_DOCUMENT, lpeitem);
323 Effect* lpe = lpeitem->getCurrentLPE();
324 if (lpe) {
325 lpe->getRepr()->setAttribute("invert", "false");
326 lpe->getRepr()->setAttribute("is_visible", "true");
327 lpe->getRepr()->setAttribute("hide_mask", "false");
328 lpe->getRepr()->setAttribute("background", "true");
329 lpe->getRepr()->setAttribute("background_color", "#ffffffff");
330 }
331 }
332 }
333 }
334 }
335 }
336
sp_remove_powermask(Inkscape::Selection * sel)337 void sp_remove_powermask(Inkscape::Selection *sel) {
338 if (!sel->isEmpty()) {
339 auto selList = sel->items();
340 for (auto i = boost::rbegin(selList); i != boost::rend(selList); ++i) {
341 SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(*i);
342 if (lpeitem) {
343 if (lpeitem->hasPathEffect() && lpeitem->pathEffectsEnabled()) {
344 PathEffectList path_effect_list(*lpeitem->path_effect_list);
345 for (auto &lperef : path_effect_list) {
346 LivePathEffectObject *lpeobj = lperef->lpeobject;
347 if (!lpeobj) {
348 /** \todo Investigate the cause of this.
349 * For example, this happens when copy pasting an object with LPE applied. Probably because
350 * the object is pasted while the effect is not yet pasted to defs, and cannot be found.
351 */
352 g_warning("SPLPEItem::performPathEffect - NULL lpeobj in list!");
353 return;
354 }
355 if (LPETypeConverter.get_key(lpeobj->effecttype) == "powermask") {
356 lpeitem->setCurrentPathEffect(lperef);
357 lpeitem->removeCurrentPathEffect(false);
358 break;
359 }
360 }
361 }
362 }
363 }
364 }
365 }
366
367 }; //namespace LivePathEffect
368 }; /* namespace Inkscape */
369
370 /*
371 Local Variables:
372 mode:c++
373 c-file-style:"stroustrup"
374 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
375 indent-tabs-mode:nil
376 fill-column:99
377 End:
378 */
379 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
380