Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
booleans-subitems.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
6 * Authors:
7 * Martin Owens
8 * PBS
9 *
10 * Copyright (C) 2022 Authors
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include "booleans-subitems.h"
15
16#include <random>
17
18#include <boost/range/adaptor/reversed.hpp>
19
20#include "style.h"
23#include "object/sp-image.h"
24#include "object/sp-text.h"
25#include "object/sp-use.h"
26#include "path/path-boolop.h"
27
28namespace Inkscape {
29
30// Todo: (Wishlist) Remove this function when no longer necessary to remove boolops artifacts.
32{
34
35 for (auto &path : pathv) {
36 if (path.closed() && !is_path_empty(path)) {
37 result.push_back(std::move(path));
38 }
39 }
40
41 return result;
42}
43
54
59{
60 return is<SPImage>(item) || is<SPUse>(item);
61}
62
66struct PathvectorItem {
67 PathvectorItem(Geom::PathVector path, SPItem* item_root, SPItem* item_actual)
68 : pathv(std::move(path))
69 , root(item_root)
70 , item(item_actual)
71 {}
72 Geom::PathVector pathv;
73 SPItem* root;
74 SPItem* item;
75};
76using PathvectorItems = std::vector<PathvectorItem>;
77
79{
80 if (is<SPGroup>(item)) {
81 for (auto &child : item->children | boost::adaptors::reversed) {
82 if (auto child_item = cast<SPItem>(&child)) {
83 extract_pathvectors_recursive(root, child_item, result, child_item->transform * transform);
84 }
85 }
86 } else if (auto img = cast<SPImage>(item)) {
87 if (auto clip = img->getClipPathVector(root)) {
88 // This needs to use the clipping region because get_curve is empty in this case
89 result.emplace_back(*clip * transform, root, item);
90 } else {
91 result.emplace_back(*img->get_curve() * transform, root, item);
92 }
93 } else if (auto shape = cast<SPShape>(item)) {
94 if (auto curve = shape->curve()) {
95 result.emplace_back(*curve * transform, root, item);
96 }
97 } else if (auto text = cast<SPText>(item)) {
98 result.emplace_back(text->getNormalizedBpath() * transform, root, item);
99 } else if (auto use = cast<SPUse>(item)) {
100 auto clip = use->getClipPathVector(root);
101 if (clip && is<SPImage>(use->get_original())) {
102 // A clipped clone of an image is consumed as a single object
103 result.emplace_back(*clip * transform, root, item);
104 } else if (use->child) {
105 extract_pathvectors_recursive(root, use->child, result, use->child->transform * Geom::Translate(use->x.computed, use->y.computed) * transform);
106 }
107 }
108}
109
114WorkItems SubItem::build_mosaic(std::vector<SPItem*> &&items)
115{
116 // Sort so that topmost items come first.
117 std::sort(items.begin(), items.end(), [] (auto a, auto b) {
118 return sp_object_compare_position_bool(b, a);
119 });
120
121 // Extract all individual pathvectors within the collection of items,
122 // keeping track of their associated item and style, again sorted topmost-first.
123 PathvectorItems augmented;
124
125 for (auto item : items) {
126 // Get the correctly-transformed pathvectors, together with their corresponding styles.
128 }
129
130 // We want the images to be the first items in augmented, so they get the priority for the style.
131 // Bubble them to the front, otherwise preserving order.
132 std::stable_partition(augmented.begin(), augmented.end(), [] (auto &pvi) {
133 return is<SPImage>(pvi.item) || is<SPUse>(pvi.item);
134 });
135
136 // Compute a slightly expanded bounding box, collect together all lines, and cut the former by the latter.
138 Geom::PathVector lines;
139
140 for (auto &pvi : augmented) {
141 bounds |= pvi.pathv.boundsExact();
142 for (auto &path : pvi.pathv) {
143 lines.push_back(path);
144 }
145 }
146
147 if (!bounds) {
148 return {};
149 }
150
151 constexpr double expansion = 10.0;
152 bounds->expandBy(expansion);
153
154 auto bounds_pathv = Geom::PathVector(Geom::Path(*bounds));
155 auto pieces = pathvector_cut(bounds_pathv, lines);
156
157 // Construct the SubItems, attempting to guess the corresponding augmented item for each piece.
159
160 auto gen = std::default_random_engine(std::random_device()());
161 auto ranf = [&] { return std::uniform_real_distribution()(gen); };
162 auto randpt = [&] { return Geom::Point(ranf(), ranf()); };
163
164 for (auto &piece : pieces) {
165 // Skip the big enclosing piece that is touching the outer boundary.
166 if (auto rect = piece.boundsExact()) {
167 if ( Geom::are_near(rect->top(), bounds->top(), expansion / 2)
168 || Geom::are_near(rect->bottom(), bounds->bottom(), expansion / 2)
169 || Geom::are_near(rect->left(), bounds->left(), expansion / 2)
170 || Geom::are_near(rect->right(), bounds->right(), expansion / 2))
171 {
172 continue;
173 }
174 }
175
176 // Remove junk paths that are open and/or tiny.
177 for (auto it = piece.begin(); it != piece.end(); ) {
178 if (!it->closed() || is_path_empty(*it)) {
179 it = piece.erase(it);
180 } else {
181 ++it;
182 }
183 }
184
185 // Skip empty pathvectors.
186 if (piece.empty()) {
187 continue;
188 }
189
190 // Determine the corresponding augmented item.
191 // Fixme: (Wishlist) This is done unreliably and hackily, but livarot/2geom seemingly offer no alternative.
192 std::unordered_map<PathvectorItem*, int> hits;
193
194 auto rect = piece.boundsExact();
195
196 auto add_hit = [&] (Geom::Point const &pt) {
197 // Find an augmented item containing the point.
198 for (auto &pvi : augmented) {
199 auto fill_rule = pvi.item->style->fill_rule.computed;
200 auto winding = pvi.pathv.winding(pt);
201 if (fill_rule == SP_WIND_RULE_NONZERO ? winding : winding % 2) {
202 hits[&pvi]++;
203 return;
204 }
205 }
206
207 // If none exists, register a background hit with nullptr.
208 hits[nullptr]++;
209 };
210
211 for (int total_hits = 0, patience = 1000; total_hits < 20 && patience > 0; patience--) {
212 // Attempt to generate a point strictly inside the piece.
213 auto pt = rect->min() + randpt() * rect->dimensions();
214 if (piece.winding(pt)) {
215 add_hit(pt);
216 total_hits++;
217 }
218 }
219
220 // Pick the augmented item with the most hits.
221 PathvectorItem *found = nullptr;
222 int max_hits = 0;
223
224 for (auto &[a, h] : hits) {
225 if (h > max_hits) {
226 max_hits = h;
227 found = a;
228 }
229 }
230
231 // Add the SubItem.
232 auto root = found ? found->root : nullptr;
233 auto item = found ? found->item : nullptr;
234 auto style = item ? item->style : nullptr;
235 result.emplace_back(std::make_shared<SubItem>(std::move(piece), root, item, style));
236 }
237
238 return result;
239}
240
245{
246 // Sort so that topmost items come first.
247 std::sort(items.begin(), items.end(), [] (auto a, auto b) {
248 return sp_object_compare_position_bool(b, a);
249 });
250
252 Geom::PathVector unioned;
253
254 for (auto item : items) {
255 // Get the correctly-transformed pathvectors, together with their corresponding styles.
256 PathvectorItems extracted;
258
259 for (auto &[pathv, root, subitem] : extracted) {
260 // Close any non closed objects
261 for (auto &it : pathv) {
262 if (!it.closed()) {
263 it.close();
264 }
265 }
266
267 // Skip pathvectors that are empty
268 if (pathv.empty()) {
269 continue;
270 }
271
272 // Flatten the remaining pathvector according to its fill rule.
273 auto fillrule = subitem->style->fill_rule.computed;
274 flatten(pathv, to_livarot(fillrule));
275
276 // Remove the union so far from the shape, then add the shape to the union so far.
277 Geom::PathVector uniq;
278
279 if (unioned.empty()) {
280 uniq = pathv;
281 unioned = std::move(pathv);
282 } else {
284 unioned = sp_pathvector_boolop(unioned, pathv, bool_op_union, fill_nonZero, fill_nonZero);
285 }
286
287 // Add the new SubItem.
288 result.emplace_back(std::make_shared<SubItem>(std::move(uniq), root, subitem, subitem->style));
289 }
290 }
291
292 return result;
293}
294
298bool SubItem::contains(Geom::Point const &pt) const
299{
300 return _paths.winding(pt) % 2 != 0;
301}
302
303} // namespace Inkscape
FillRule to_livarot(SPWindRule fill_rule)
Definition LivarotDefs.h:96
@ fill_nonZero
Definition LivarotDefs.h:70
@ bool_op_diff
Definition LivarotDefs.h:80
@ bool_op_union
Definition LivarotDefs.h:78
Geom::IntRect bounds
Definition canvas.cpp:182
3x3 matrix representing an affine transformation.
Definition affine.h:70
C right() const
Return rightmost coordinate of the rectangle (+X is to the right).
C top() const
Return top coordinate of the rectangle (+Y is downwards).
void expandBy(C amount)
Expand the rectangle in both directions by the specified amount.
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C bottom() const
Return bottom coordinate of the rectangle (+Y is downwards).
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
int winding(Point const &p) const
Determine the winding number at the specified point.
Sequence of contiguous curves, aka spline.
Definition path.h:353
Two-dimensional point that doubles as a vector.
Definition point.h:66
Translation by a vector.
Definition transforms.h:115
When an item is broken, each broken part is represented by the SubItem class.
SubItem & operator+=(SubItem const &other)
Union operator, merges two subitems when requested by the user The left hand side will retain priorit...
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...
static bool _get_is_image(SPItem const *item)
Test if this sub item is a special image type.
Geom::PathVector _paths
bool contains(Geom::Point const &pt) const
Return true if this subitem contains the give point.
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1829
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
ChildrenList children
Definition sp-object.h:907
RootCluster root
Css & result
SPItem * item
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Helper class to stream background task notifications as a series of messages.
static Geom::PathVector clean_pathvector(Geom::PathVector &&pathv)
std::vector< PathvectorItem > PathvectorItems
static void extract_pathvectors_recursive(SPItem *root, SPItem *item, PathvectorItems &result, Geom::Affine const &transform)
std::vector< WorkItem > WorkItems
bool is_path_empty(Geom::Path const &path)
Check for an empty path.
STL namespace.
static T clip(T const &v, T const &a, T const &b)
void flatten(Geom::PathVector &pathv, FillRule fill_rule)
Geom::PathVector sp_pathvector_boolop(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, BooleanOp bop, FillRule fra, FillRule frb)
Perform a boolean operation on two pathvectors.
Geom::PathVector flattened(Geom::PathVector const &pathv, FillRule fill_rule)
Flatten a pathvector according to the given fill rule.
std::vector< Geom::PathVector > pathvector_cut(Geom::PathVector const &pathv, Geom::PathVector const &lines)
Cut a pathvector along a collection of lines into several smaller pathvectors.
Boolean operations.
Ocnode * child[8]
Definition quantize.cpp:33
GList * items
SVG <image> implementation.
Definition curve.h:24
@ SP_WIND_RULE_NONZERO
Definition style-enums.h:24
SPStyle - a style object for SPItem objects.
int winding(PathVector ps, Point p)
Definition uncross.cpp:57