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