Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
booleans-builder.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
6 * Authors:
7 * Martin Owens
8 *
9 * Copyright (C) 2022 Authors
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include "booleans-builder.h"
14
15#include "desktop.h"
16#include "document.h"
17#include "style.h"
18
23#include "display/drawing.h"
24#include "object/object-set.h"
25#include "object/sp-item.h"
26#include "object/sp-image.h"
27#include "object/sp-use.h"
28#include "object/sp-clippath.h"
29#include "object/sp-namedview.h"
30#include "object/sp-defs.h"
31#include "object/sp-root.h"
32#include "ui/widget/canvas.h"
33#include "svg/svg.h"
34
35namespace Inkscape {
36
37static constexpr std::array<uint32_t, 6> fill_lite = {0x00000055, 0x0291ffff, 0x8eceffff, 0x0291ffff, 0xf299d6ff, 0xff0db3ff};
38static constexpr std::array<uint32_t, 6> fill_dark = {0xffffff55, 0x0291ffff, 0x8eceffff, 0x0291ffff, 0xf299d6ff, 0xff0db3ff};
39
41 : _set(set)
42{
43 // Current state of all the items
45
46 if (_set->desktop()) {
48 _group = make_canvasitem<CanvasItemGroup>(root);
49
50 // Build some image screen items
51 for (auto &subitem : _work_items) {
52 if (!subitem->is_image())
53 continue;
54 // Somehow show the image to the user.
55 }
56
57 auto nv = _set->desktop()->getNamedView();
58 desk_modified_connection = nv->connectModified([this](SPObject *obj, guint flags) {
60 });
62 }
63}
64
66
70void BooleanBuilder::redraw_item(CanvasItemBpath &bpath, bool selected, TaskType task, bool image)
71{
72 int i = (int)task * 2 + (int)selected;
73 auto fill = _dark ? fill_dark[i] : fill_lite[i];
74 if (image) {
75 // Make image items less opaque
76 fill = (fill | 0xff) - 0xcc;
77 }
79 bpath.set_stroke(task == TaskType::NONE ? 0x000000dd : 0xffffffff);
80 bpath.set_stroke_width(task == TaskType::NONE ? 1.0 : 3.0);
81}
82
87{
88 if (!_set->desktop()) {
89 return;
90 }
91
93
94 _screen_items.clear();
95
96 for (auto &subitem : _work_items) {
97 // Construct BPath from each subitem!
98 auto bpath = make_canvasitem<Inkscape::CanvasItemBpath>(_group.get(), subitem->get_pathv(), false);
99 redraw_item(*bpath, subitem->getSelected(), TaskType::NONE, subitem->is_image());
100 _screen_items.push_back({ subitem, std::move(bpath), true });
101 }
102
103 // Selectively handle the undo actions being enabled / disabled
104 enable_undo_actions(_set->document(), _undo.size(), _redo.size());
105}
106
108{
109 for (auto &pair : _screen_items) {
110 if (pair.vis->contains(point, 2.0))
111 return &pair;
112 }
113 return nullptr;
114}
115
119bool BooleanBuilder::highlight(const Geom::Point &point, bool add)
120{
121 if (has_task())
122 return true;
123
124 bool done = false;
125 for (auto &si : _screen_items) {
126 bool hover = !done && si.vis->contains(point, 2.0);
127 redraw_item(*si.vis, si.work->getSelected(), hover ? (add ? TaskType::ADD : TaskType::DELETE) : TaskType::NONE, si.work->is_image());
128 if (hover)
129 si.vis->raise_to_top();
130 done = done || hover;
131 }
132 return done;
133}
134
139{
140 for (auto &subitem : _work_items) {
141 if (subitem->get_root() == root && subitem->is_image()) {
142 return true;
143 }
144 }
145 return false;
146}
147
151bool BooleanBuilder::task_select(const Geom::Point &point, bool add_task)
152{
153 if (has_task())
154 task_cancel();
155 if (auto si = get_item(point)) {
156 _add_task = add_task;
157 _work_task = std::make_shared<SubItem>(*si->work);
158 _work_task->setSelected(true);
159 _screen_task = make_canvasitem<Inkscape::CanvasItemBpath>(_group.get(), _work_task->get_pathv(), false);
160 redraw_item(*_screen_task, true, add_task ? TaskType::ADD : TaskType::DELETE, _work_task->is_image());
161 si->vis->set_visible(false);
162 si->visible = false;
163 redraw_item(*si->vis, false, TaskType::NONE, _work_task->is_image());
164 return true;
165 }
166 return false;
167}
168
170{
171 if (!has_task())
172 return false;
173 if (auto si = get_item(point)) {
174 // Invisible items are already processed.
175 if (si->visible) {
176 si->vis->set_visible(false);
177 si->visible = false;
178 *_work_task += *si->work;
179 _screen_task->set_bpath(_work_task->get_pathv(), false);
180 return true;
181 }
182 }
183 return false;
184}
185
187{
188 _work_task.reset();
189 _screen_task.reset();
190 for (auto &si : _screen_items) {
191 si.vis->set_visible(true);
192 si.visible = true;
193 }
194}
195
197{
198 if (!has_task())
199 return;
200
201 // Manage undo/redo
202 _undo.emplace_back(std::move(_work_items));
203 _redo.clear();
204
205 // A. Delete all items from _work_items that aren't visible
206 _work_items.clear();
207 for (auto &si : _screen_items) {
208 if (si.visible) {
209 _work_items.emplace_back(si.work);
210 }
211 }
212 if (_add_task) {
213 // B. Add _work_task to _work_items for union tasks
214 _work_items.emplace_back(std::move(_work_task));
215 }
216
217 // C. Reset everything
218 redraw_items();
219 _work_task.reset();
220 _screen_task.reset();
221}
222
226std::vector<SPObject *> BooleanBuilder::shape_commit(bool all, bool replace)
227{
228 std::vector<SPObject *> ret;
229 std::map<SPItem *, SPItem *> used_images;
230 auto doc = _set->document();
231 auto items = _set->items_vector();
232 auto defs = doc->getDefs();
233 auto xml_doc = doc->getReprDoc();
234
235 // Only commit anything if we have changes, return selection.
236 if (!has_changes() && !all) {
237 ret.insert(ret.begin(), items.begin(), items.end());
238 return ret;
239 }
240
241 // Count number of selected items.
242 int selected = 0;
243 for (auto const &subitem : _work_items) {
244 selected += (int)subitem->getSelected();
245 }
246
247 for (auto const &subitem : _work_items) {
248 // Either this object is selected, or no objects are selected at all.
249 if (!subitem->getSelected() && selected)
250 continue;
251 auto root = subitem->get_root();
252 auto item = subitem->get_item();
253 auto style = subitem->getStyle();
254 // For the rare occasion the user generates from a hole (no item)
255 if (!root) {
256 root = *items.begin();
257 style = root->style;
258 }
259 if (!root) {
260 g_warning("Can't generate itemless object in boolean-builder.");
261 continue;
262 }
263 auto parent = cast<SPItem>(root->parent);
264 if (!parent) {
265 g_warning("Can't generate top-most object in boolean-builder.");
266 continue;
267 }
268
269 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
270 repr->setAttribute("d", sp_svg_write_path(subitem->get_pathv() * parent->dt2i_affine()));
271 repr->setAttribute("style", style->writeIfDiff(parent->style));
272
273 // Images and clipped clones are re/clipped instead of path-constructs
274 if ((is<SPImage>(item) || is<SPUse>(item)) && item->getId()) {
275 if (is<SPImage>(item)) {
276 // An image may have been contained without groups or layers with transforms
277 // moving it to the defs would lose this information. So we add it in now.
278 auto tr = i2anc_affine(item, parent);
279
280 // Make a copy of the image when not replacing it
281 if (!used_images.contains(item)) {
282 auto orig = item;
283 if (item->parent != defs && !replace) {
284 auto copy_repr = item->getRepr()->duplicate(xml_doc);
285 item = cast<SPItem>(defs->appendChildRepr(copy_repr));
286 }
288 used_images[orig] = item;
289 } else {
290 // Make sure the id we use below is the copy, or the original dependiong on replace
291 item = used_images[item];
292 }
293 }
294
295 // Consume existing repr as the clipPath and replace with clone of image
296 Geom::Affine clone_tr = Geom::identity();
297 std::vector<Inkscape::XML::Node *> paths = {repr};
298 std::string clip_id = SPClipPath::create(paths, doc);
299 std::string href_id = std::string("#") + item->getId();
300
301 if (is<SPUse>(item)) {
302 href_id = item->getAttribute("xlink:href");
303 clone_tr = i2anc_affine(item, parent);
304 // Remove the original clone's transform from the new clip object
305 repr->setAttribute("transform", sp_svg_transform_write(clone_tr.inverse()));
306 }
307
308 repr = xml_doc->createElement("svg:use");
309 repr->setAttribute("x", "0");
310 repr->setAttribute("y", "0");
311 repr->setAttribute("xlink:href", href_id);
312 repr->setAttribute("clip-path", std::string("url(#") + clip_id + ")");
313 repr->setAttribute("transform", sp_svg_transform_write(clone_tr));
314 }
315
316 parent->getRepr()->addChild(repr, root->getRepr());
317 ret.emplace_back(doc->getObjectByRepr(repr));
318 }
319 _work_items.clear();
320
321 for (auto &[orig, item] : used_images) {
322 // Images that are used in a fragment are moved,
323 if (item->parent != defs && replace) {
324 auto img_repr = item->getRepr();
325 sp_repr_unparent(img_repr);
326 defs->getRepr()->appendChild(img_repr);
327 }
328 }
329
330 // Apart from the used images, everything else is to be deleted.
331 if (replace) {
332 for (auto item : items) {
333 if (!used_images.contains(item)) {
334 sp_object_ref(item, nullptr);
335 }
336 }
337 for (auto item : items) {
338 if (!used_images.contains(item)) {
340 sp_object_unref(item, nullptr);
341 }
342 }
343 }
344
345 return ret;
346}
347
349{
350 if (_undo.empty())
351 return;
352
353 // Cancel any task;
354 task_cancel();
355
356 // Shuffle the undo stack
357 _redo.emplace_back(std::move(_work_items));
358 _work_items = std::move(_undo.back());
359 _undo.pop_back();
360
361 // Redraw the screen items
362 redraw_items();
363}
364
366{
367 if (_redo.empty())
368 return;
369
370 // Cancel any task;
371 task_cancel();
372
373 // Shuffle the undo stack
374 _undo.emplace_back(std::move(_work_items));
375 _work_items = std::move(_redo.back());
376 _redo.pop_back();
377
378 // Redraw the screen items
379 redraw_items();
380}
381
382} // namespace Inkscape
void enable_undo_actions(SPDocument *document, bool undo, bool redo)
Actions for Undo/Redo tied to document.
Inkscape canvas widget.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Two-dimensional point that doubles as a vector.
Definition point.h:66
CanvasItemPtr< CanvasItemGroup > _group
bool task_select(const Geom::Point &point, bool add_task=true)
Select the shape under the cursor.
std::vector< ItemPair > _screen_items
bool task_add(const Geom::Point &point)
void redraw_item(CanvasItemBpath &bpath, bool selected, TaskType task, bool image)
Control the visual appearence of this particular bpath.
std::vector< std::vector< WorkItem > > _redo
ItemPair * get_item(const Geom::Point &point)
sigc::scoped_connection desk_modified_connection
BooleanBuilder(ObjectSet *obj, bool flatten=false)
void redraw_items()
Update to visuals with the latest subitem list.
std::vector< WorkItem > _work_items
bool contains_image(SPItem *root) const
Returns true if this root item contains an image work item.
std::vector< std::vector< WorkItem > > _undo
std::vector< SPObject * > shape_commit(bool all=false, bool replace=true)
Commit the changes to the document (finish)
bool highlight(const Geom::Point &point, bool add_task=true)
Highlight any shape under the mouse at this point.
void set_fill(uint32_t rgba, SPWindRule fill_rule)
Set the fill color and fill rule.
virtual void set_stroke(uint32_t rgba)
void set_stroke_width(double width)
Set the stroke width.
SPDesktop * desktop()
Returns the desktop the selection is bound to.
Definition object-set.h:390
SPDocument * document()
Returns the document the selection is bound to.
Definition object-set.h:397
std::vector< SPItem * > items_vector()
Definition object-set.h:261
static WorkItems build_flatten(std::vector< SPItem * > &&items)
Take a list of items and flatten into a list of SubItems.
static WorkItems build_mosaic(std::vector< SPItem * > &&items)
Take a list of items and fracture into a list of SubItems ready for use inside the booleans interacti...
CanvasItemGroup * get_canvas_item_root() const
Definition canvas.cpp:532
Interface for refcounted XML nodes.
Definition node.h:80
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * duplicate(Document *doc) const =0
Create a duplicate of this node.
static char const * create(std::vector< Inkscape::XML::Node * > &reprs, SPDocument *document)
Inkscape::UI::Widget::Canvas * getCanvas() const
Definition desktop.h:190
SPNamedView * getNamedView() const
Definition desktop.h:191
Base class for visual SVG elements.
Definition sp-item.h:109
Colors::Color getDeskColor() const
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Definition sp-object.h:785
char const * getId() const
Returns the objects current ID string.
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
SPObject * parent
Definition sp-object.h:189
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getAttribute(char const *name) const
sigc::connection connectModified(sigc::slot< void(SPObject *, unsigned int)> slot)
Connects to the modification notification signal.
Definition sp-object.h:705
RootCluster root
Geom::Point orig
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
SVG drawing for display.
std::unique_ptr< Magick::Image > image
SPItem * item
vector< vector< Point > > paths
Definition metro.cpp:36
Affine identity()
Create an identity matrix.
Definition affine.h:210
double get_perceptual_lightness(Color const &color)
Return a value for how the light the color appears to be using HSLUV.
Definition utils.cpp:174
Helper class to stream background task notifications as a series of messages.
static constexpr std::array< uint32_t, 6 > fill_lite
static constexpr std::array< uint32_t, 6 > fill_dark
void flatten(Geom::PathVector &pathv, FillRule fill_rule)
void sp_repr_unparent(Inkscape::XML::Node *repr)
Remove repr from children of its parent node.
Definition repr.h:107
GList * items
SVG <image> implementation.
Geom::Affine i2anc_affine(SPObject const *object, SPObject const *ancestor)
Definition sp-item.cpp:1794
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
SPRoot: SVG <svg> implementation.
@ SP_WIND_RULE_POSITIVE
Definition style-enums.h:27
SPStyle - a style object for SPItem objects.
std::string sp_svg_transform_write(Geom::Affine const &transform)
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109