1 /*
2 * Copyright (c) 2005-2019 Libor Pecháček.
3 * Copyright 2020 Kai Pastor
4 *
5 * This file is part of CoVe
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "Polygons.h"
22
23 #include <algorithm>
24 #include <cerrno>
25 #include <cmath>
26 #include <cstdlib>
27 #include <cstring>
28 #include <iosfwd>
29 #include <limits>
30 #include <memory>
31 #include <stdexcept>
32 #include <utility>
33
34 #include <QtGlobal>
35 #include <QByteArray>
36 #include <QPointF>
37 #include <QString>
38
39 #include <QImage>
40 #include <QRectF>
41
42 #include "ProgressObserver.h"
43
44 // IWYU pragma: no_forward_declare QPointF
45
46 #define JOIN_DEBUG 0
47 #define JOIN_DEBUG_PRINT(...) \
48 do \
49 { \
50 if (JOIN_DEBUG) qDebug(__VA_ARGS__); \
51 } while (0)
52
53 using namespace std;
54
55 namespace cove {
56 const int Polygons::NPOINTS_MAX = 350;
57
58 //@{
59 //! \ingroup libvectorizer
60
61 /*! \class Polygons
62 * \brief This class encapsulates vectorization of 4-connected 1px wide lines
63 * in given image. */
64
65 /*! \enum Polygons::DIRECTION
66 Internal enum for directions having values EAST, WEST, NORTH, SOUTH and NONE.
67 */
68
69 /*! \class Polygons::PATH_POINT
70 \brief One point of the path.
71
72 A coordinate pair and a direction. Direction is the direction where the
73 path follows from this point to the next point. The last point of the path
74 has direction NONE.
75 */
76
77 /*! \var int Polygons::PATH_POINT::x
78 \brief The x-coordinate. */
79
80 /*! \var int Polygons::PATH_POINT::y
81 \brief The y-coordinate. */
82
83 /*! \var DIRECTION Polygons::PATH_POINT::d
84 \brief The direction to the next point. The last point has direction NONE.
85 */
86
87 /*! \class PolygonList
88 \brief List of polygons in an image.
89
90 Instance of STL template vector.*/
91
92 /*! \class Polygons::PathList
93 \brief List of paths in an image.
94
95 Instance of STL template vector.*/
96
97 // -PATH-----------------------
98 /*! \class Polygons::Path
99 \brief Represents path in an image.
100
101 The path in an image is a set of neighboring points making up a line. Each
102 point has exactly two neighbors. The only exception is an unclosed path
103 where the end points have only one neighbor.
104
105 The path is later being examined for straight segments by \a
106 Polygons::getPathPolygon.
107 */
108 /*! Default constructor. */
Path()109 Polygons::Path::Path()
110 : pathClosed(false)
111 {
112 }
113
114 /*! \brief Sets the path-closedness flag.
115 *
116 * Path is closed when and only when its first point is identical with its
117 * last point. I.e. it forms a cycle in graph.*/
setClosed(bool closed)118 void Polygons::Path::setClosed(bool closed)
119 {
120 pathClosed = closed;
121 }
122
123 /*! \brief Returns value of the path-closedness flag.
124
125 \see setClosed
126 */
isClosed() const127 bool Polygons::Path::isClosed() const
128 {
129 return pathClosed;
130 }
131
132 // -POLYGON--------------------
133 /*! \class Polygon
134 \brief Represents polygon in an image.
135
136 The polygon is a set of straight lines. The first point represents the
137 beginning of the polygon, following points represent vertices of the polygon
138 and the last point is the end in case the polygon is not closed.
139
140 The polygon is usually being built as a result of path decomposition into
141 straight segments.
142 */
143
144 /*! Returns bounding rectangle of the polygon. */
boundingRect() const145 QRectF Polygon::boundingRect() const
146 {
147 return { minX, minY, maxX - minX, maxY - minY };
148 }
149
150 /*! Updates bounding rectangle of the polygon. */
recheckBounds()151 void Polygon::recheckBounds()
152 {
153 minX = std::numeric_limits<qreal>::max();
154 minY = std::numeric_limits<qreal>::max();
155 maxX = std::numeric_limits<qreal>::min();
156 maxY = std::numeric_limits<qreal>::min();
157 for (auto const& p : *this)
158 {
159 if (p.x() > maxX) maxX = p.x();
160 if (p.x() < minX) minX = p.x();
161 if (p.y() > maxY) maxY = p.y();
162 if (p.y() < minY) minY = p.y();
163 }
164 }
165
166 /*! \brief Sets the polygon-closedness flag.
167 *
168 * Polygon is closed when and only when its first point is identical with its
169 * last point. */
setClosed(bool closed)170 void Polygon::setClosed(bool closed)
171 {
172 polygonClosed = closed;
173 }
174
175 /*! \brief Returns value of the polygon-closedness flag.
176
177 \see setClosed
178 */
isClosed() const179 bool Polygon::isClosed() const
180 {
181 return polygonClosed;
182 }
183
184 //-POLYGONS-------------------------------------------------------
Polygons()185 Polygons::Polygons()
186 : specklesize(9)
187 , maxdist(9)
188 , distdirratio(0.8)
189 {
190 }
191
setSpeckleSize(int val)192 void Polygons::setSpeckleSize(int val)
193 {
194 specklesize = val;
195 }
196
setMaxDistance(double val)197 void Polygons::setMaxDistance(double val)
198 {
199 maxdist = val;
200 }
201
setSimpleOnly(bool val)202 void Polygons::setSimpleOnly(bool val)
203 {
204 simpleonly = val;
205 }
206
setDistDirRatio(double val)207 void Polygons::setDistDirRatio(double val)
208 {
209 distdirratio = val;
210 }
211
speckleSize() const212 int Polygons::speckleSize() const
213 {
214 return specklesize;
215 }
216
maxDistance() const217 double Polygons::maxDistance() const
218 {
219 return maxdist;
220 }
221
simpleOnly() const222 bool Polygons::simpleOnly() const
223 {
224 return simpleonly;
225 }
226
distDirRatio() const227 double Polygons::distDirRatio() const
228 {
229 return distdirratio;
230 }
231
232 /*! \brief Looks for any pixel in image.
233
234 Updates reference variables xp, yp so that they contain next black pixel
235 position in the image. The scanning starts from point [xp, yp]. Returns
236 true when a black pixel was found, false otherwise.
237 \param[in] image Input image.
238 \param[in,out] xp x coordinate where to start, will be modified so that it
239 contains x coordinate of next black pixel
240 \param[in,out] yp y coordinate, similar to xp
241 \return true when xp, yp contain valid coordinates of black pixel, false
242 otherwise */
findNextPixel(const QImage & image,int & xp,int & yp)243 bool Polygons::findNextPixel(const QImage& image, int& xp, int& yp)
244 {
245 int x = xp;
246 for (int y = yp; y < image.height(); y++)
247 {
248 for (; x < image.width(); x++)
249 {
250 if (image.pixelIndex(x, y))
251 {
252 // we have found one
253 xp = x;
254 yp = y;
255 return true;
256 }
257 }
258 x = 0;
259 }
260 // there are no pixels left
261 return false;
262 }
263
264 /*! \brief Follows path in an image.
265
266 Follows path in an image and records it into \a path.
267 \param[in] image Image to be examined.
268 \param[in,out] x X-ccordinate of the first pixel of the path.
269 \param[in,out] y Y-ccordinate of the first pixel of the path.
270 \param[in,out] path Pointer to Polygons::Path where the pixel coordinates will
271 be stored. The variable can be nullptr in which case no coordinates are recorded.
272 */
followPath(const QImage & image,int & x,int & y,Path * path)273 void Polygons::followPath(const QImage& image, int& x, int& y, Path* path)
274 {
275 const int origX = x, origY = y;
276 int newx = 0, newy = 0;
277 int imWidth = image.width(), imHeight = image.height();
278 DIRECTION direction = NONE, newDirection;
279 int canProceed;
280 bool firstCycle = true;
281
282 for (;;)
283 {
284 canProceed = 0;
285 newDirection = NONE;
286
287 if (x < imWidth - 1 && image.pixelIndex(x + 1, y) && direction != WEST)
288 {
289 newx = x + 1;
290 newy = y;
291 newDirection = EAST;
292 canProceed++;
293 }
294 if (x && image.pixelIndex(x - 1, y) && direction != EAST)
295 {
296 newx = x - 1;
297 newy = y;
298 newDirection = WEST;
299 canProceed++;
300 }
301 if (y < imHeight - 1 && image.pixelIndex(x, y + 1) &&
302 direction != NORTH)
303 {
304 newx = x;
305 newy = y + 1;
306 newDirection = SOUTH;
307 canProceed++;
308 }
309 if (y && image.pixelIndex(x, y - 1) && direction != SOUTH)
310 {
311 newx = x;
312 newy = y - 1;
313 newDirection = NORTH;
314 canProceed++;
315 }
316
317 if (path)
318 {
319 path->push_back({x, y});
320 }
321
322 // is the path closed? (we are in the starting point)
323 if (canProceed && newx == origX && newy == origY)
324 {
325 if (path) path->setClosed(true);
326 break;
327 }
328
329 if (canProceed == 1 || (firstCycle && canProceed == 2))
330 {
331 x = newx;
332 y = newy;
333 direction = newDirection;
334 firstCycle = false;
335 }
336 else
337 break;
338 }
339 }
340
341 /*! Find a path in image. The method traverses the path to its end (or around
342 * when cyclic), then stores its pixel coordinates into returned Path.
343 * \param image the image where to look for path
344 \param initX pixel coordinates where to start traversal
345 \param initY see initX
346 \return path found */
recordPath(const QImage & image,const int initX,const int initY)347 Polygons::Path Polygons::recordPath(const QImage& image, const int initX,
348 const int initY)
349 {
350 int x = initX, y = initY;
351 // find the end of the path
352 followPath(image, x, y);
353 // record path into newPath
354 Path newPath;
355 followPath(image, x, y, &newPath);
356 // and happily return it...
357 return newPath;
358 }
359
360 /*! Draw into the image so that original path disappears. */
removePathFromImage(QImage & image,const Path & path)361 void Polygons::removePathFromImage(QImage& image, const Path& path)
362 {
363 for (auto const& i : path)
364 {
365 image.setPixel(i.x, i.y, 0); // delete the pixel
366 }
367 }
368
369 /*! Finds all paths in image and returns them in PathList. */
370 Polygons::PathList
decomposeImageIntoPaths(const QImage & sourceImage,ProgressObserver * progressObserver) const371 Polygons::decomposeImageIntoPaths(const QImage& sourceImage,
372 ProgressObserver* progressObserver) const
373 {
374 PathList pathList;
375 QImage image = sourceImage;
376 int height = sourceImage.height(),
377 progressHowOften = (height > 100) ? height / 45 : 1;
378 int x = 0, y = 0;
379 bool cancel = false;
380 while (!cancel && findNextPixel(image, x, y))
381 {
382 Path pathFound = recordPath(image, x, y);
383 removePathFromImage(image, pathFound);
384 if (pathFound.size() > specklesize) pathList.push_back(pathFound);
385 if (progressObserver && !(y % progressHowOften))
386 {
387 // FIXME hardcoded value 25!!!
388 progressObserver->setPercentage(y * 25 / height);
389 cancel = progressObserver->isInterruptionRequested();
390 }
391 }
392
393 return !cancel ? pathList : PathList();
394 }
395
396 /*! Identifies straight segments of path and returns them as list of vertices
397 * between them. Prepares data for libpotrace, then calls its functions. */
398 PolygonList
getPathPolygons(const Polygons::PathList & constpaths,ProgressObserver * progressObserver) const399 Polygons::getPathPolygons(const Polygons::PathList& constpaths,
400 ProgressObserver* progressObserver) const
401 {
402 path_t* p;
403 path_t* plist = nullptr; /* linked list of path objects */
404 path_t** hook = &plist; /* used to speed up appending to linked list */
405
406 PolygonList polylist;
407 polylist.reserve(constpaths.size());
408
409 // convert Polygons::Paths into Potrace paths
410 bool cancel = false;
411 int tpolys = constpaths.size(), cntr = 0;
412 int progressHowOften = (tpolys > 100) ? tpolys / 35 : 1;
413 for (PathList::const_iterator pathsiterator = constpaths.begin();
414 !cancel && pathsiterator != constpaths.end(); ++pathsiterator)
415 {
416 int len = pathsiterator->size();
417 point_t* pt = reinterpret_cast<point_t*>(malloc(len * sizeof(point_t))); // NOLINT
418 p = path_new();
419 if (!p || !pt)
420 {
421 throw runtime_error("memory allocation failed");
422 }
423 privpath_t* pp = p->priv;
424 pp->pt = pt;
425 pp->len = len;
426 pp->closed = pathsiterator->isClosed();
427 p->area = 100; // no speckle
428 p->sign = '+';
429
430 len = 0;
431 for (auto const& i : *pathsiterator)
432 {
433 pt[len].x = i.x;
434 pt[len].y = i.y;
435 len++;
436 }
437
438 list_insert_beforehook(p, hook);
439
440 if (progressObserver && !((++cntr) % progressHowOften))
441 {
442 progressObserver->setPercentage(25 + cntr * 25 / tpolys);
443 cancel = progressObserver->isInterruptionRequested();
444 }
445 }
446
447 // create polygons for all paths
448 #define TRY(x) \
449 if (x) goto try_error // NOLINT
450 list_forall(p, plist)
451 {
452 TRY(calc_sums(p->priv));
453 TRY(calc_lon(p->priv));
454 TRY(bestpolygon(p->priv));
455 TRY(adjust_vertices(p->priv));
456 }
457
458 // do joining...
459 if (maxdist > 0) joinPolygons(plist, progressObserver);
460
461 // convert potrace line segments into CoVe Polygons
462 // working on p->priv->curve
463 cntr = 0;
464 list_forall(p, plist)
465 {
466 privpath_t* pp = p->priv;
467 int n = pp->curve.n;
468
469 Polygon polygon;
470 polygon.reserve(n);
471
472 for (auto vertex = pp->curve.vertex, last = pp->curve.vertex + n; vertex != last; ++vertex)
473 {
474 polygon.emplace_back(vertex->x, vertex->y);
475 }
476 polygon.recheckBounds();
477 polygon.setClosed(pp->curve.closed);
478
479 polylist.push_back(std::move(polygon));
480
481 if (progressObserver && !((++cntr) % progressHowOften))
482 {
483 progressObserver->setPercentage(90 + cntr * 10 / tpolys);
484 // cancel = progressObserver->isInterruptionRequested();
485 }
486 }
487
488 list_forall_unlink(p, plist)
489 {
490 path_free(p);
491 }
492
493 return polylist;
494
495 try_error:
496 qWarning("process_path failed with errno %d", errno);
497 path_free(p);
498 return {};
499 }
500
501 /*! Euclidean distance of two QPointFs. */
distance(const QPointF & a,const QPointF & b)502 double Polygons::distance(const QPointF& a, const QPointF& b)
503 {
504 auto const x = a.x() - b.x();
505 auto const y = a.y() - b.y();
506 return std::sqrt(x * x + y * y);
507 }
508
509 /*! \brief Returns set of sequences of straight line segments in the image.
510
511 Decomposes image into paths \a decomposeImageIntoPaths and every path longer
512 than \a speckleSize is transformed into straight line segments by \a
513 getPathPolygon.
514 \param[in] image Input image to be analyzed.
515 \param[in] speckleSize Minimum length of path to be vectorized. */
516 PolygonList
createPolygonsFromImage(const QImage & image,ProgressObserver * progressObserver) const517 Polygons::createPolygonsFromImage(const QImage& image,
518 ProgressObserver* progressObserver) const
519 {
520 PathList p = decomposeImageIntoPaths(image, progressObserver);
521 if (p.empty()) return PolygonList();
522 return getPathPolygons(p, progressObserver);
523 }
524
525 /*! \brief Return list of polygons where close enough polygons are joined.
526
527 Close enough means that polygons have ends closer than maxDist.
528 \param[in] fromPL Input polygon list.
529 \param[in] maxDist Maximum distance of two ends that will be joined.
530 \param[in] distDirRatio Parameter controlling a not yet implemented feature. */
531
532 //! Fast computation of distance square of two points. It is used in
533 // comparisons where the monotonic transformation makes no problem. It saves
534 // one call to sqrt(3).
distSqr(const dpoint_t * a,const dpoint_t * b) const535 inline double Polygons::distSqr(const dpoint_t* a, const dpoint_t* b) const
536 {
537 auto p = a->x - b->x, q = a->y - b->y;
538 return p * p + q * q;
539 }
540
joinEndA(Polygons::JOINTYPE j) const541 inline Polygons::JOINEND Polygons::joinEndA(Polygons::JOINTYPE j) const
542 {
543 return (j == FF || j == FB) ? FRONT : ((j == BF || j == BB) ? BACK : NOEND);
544 }
545
joinEndB(Polygons::JOINTYPE j) const546 inline Polygons::JOINEND Polygons::joinEndB(Polygons::JOINTYPE j) const
547 {
548 return (j == FF || j == BF) ? FRONT : ((j == FB || j == BB) ? BACK : NOEND);
549 }
550
oppositeEnd(Polygons::JOINEND j) const551 inline Polygons::JOINEND Polygons::oppositeEnd(Polygons::JOINEND j) const
552 {
553 return (j == BACK) ? FRONT : ((j == FRONT) ? BACK : NOEND);
554 }
555
endsToType(Polygons::JOINEND ea,Polygons::JOINEND eb) const556 inline Polygons::JOINTYPE Polygons::endsToType(Polygons::JOINEND ea,
557 Polygons::JOINEND eb) const
558 {
559 if (ea == FRONT)
560 {
561 if (eb == FRONT) return FF;
562 if (eb == BACK) return FB;
563 }
564 if (ea == BACK)
565 {
566 if (eb == FRONT) return BF;
567 if (eb == BACK) return BB;
568 }
569 return NOJOIN;
570 }
571
572 /*
573 Distance function, evaluates possible joins with respect to
574 distance/direction ratio parameter.
575
576 a - point before end of line 1
577 b - end of line 1
578 c - end of line 2
579 d - point before end of line 2
580
581 v2
582 b----------c
583 / \
584 v1 / \ v3
585 / \
586 a d
587
588 precondition: || v2 || < maxdist
589 postcondition: return value is from <0,1>
590 */
dstfun(const dpoint_t * a,const dpoint_t * b,const dpoint_t * c,const dpoint_t * d) const591 inline double Polygons::dstfun(const dpoint_t* a, const dpoint_t* b,
592 const dpoint_t* c, const dpoint_t* d) const
593 {
594 dpoint_t v1, v2, v3;
595 v1.x = b->x - a->x;
596 v1.y = b->y - a->y;
597 v2.x = c->x - b->x;
598 v2.y = c->y - b->y;
599 v3.x = d->x - c->x;
600 v3.y = d->y - c->y;
601 auto dotprod12 = v1.x * v2.x + v1.y * v2.y;
602 auto dotprod23 = v2.x * v3.x + v2.y * v3.y;
603 auto norm1 = std::sqrt(v1.x * v1.x + v1.y * v1.y);
604 auto norm2 = std::sqrt(v2.x * v2.x + v2.y * v2.y);
605 auto norm3 = std::sqrt(v3.x * v3.x + v3.y * v3.y);
606 // direction cosine - 1 when the vectors have identical direction, -1 when
607 // opposite, 0 when orthogonal
608 auto dircos12 = dotprod12 / (norm1 * norm2);
609 auto dircos23 = dotprod23 / (norm2 * norm3);
610
611 return (1 - distdirratio) * (dircos12 + dircos23 + 2) / 4 +
612 distdirratio * (1 - norm2 / maxdist);
613 }
614
615 #define jt2string(j) \
616 ((j) == FF \
617 ? "FF" \
618 : ((j) == FB \
619 ? "FB" \
620 : ((j) == BF \
621 ? "BF" \
622 : ((j) == BB ? "BB" : ((j) == NOJOIN ? "NOJOIN" \
623 : "!!invalid")))))
624
625 // FIXME !!! - remove the recursion, then lower the NPOINTS_MAX to its optimal
626 // value 35
627 // stack is not always big enough...
splitlist(JOINENDPOINTLIST & pl,JOINOPLIST & ops,const dpoint_t & min,const dpoint_t & max,bool vertical,ProgressObserver * progressObserver,const double pBase,const double piece) const628 bool Polygons::splitlist(JOINENDPOINTLIST& pl, JOINOPLIST& ops,
629 const dpoint_t& min, const dpoint_t& max,
630 bool vertical, ProgressObserver* progressObserver,
631 const double pBase, const double piece) const
632 {
633 dpoint_t min1, max1, min2, max2;
634 JOINENDPOINTLIST pl1, pl2;
635
636 pl1.reserve(pl.size() / 2);
637 pl2.reserve(pl.size() / 2);
638
639 if (vertical)
640 {
641 min1.x = min.x;
642 max1.x = min2.x = min.x + (max.x - min.x) / 2;
643 max2.x = max.x;
644 min1.y = min2.y = min.y;
645 max1.y = max2.y = max.y;
646 }
647 else
648 {
649 min1.y = min.y;
650 max1.y = min2.y = min.y + (max.y - min.y) / 2;
651 max2.y = max.y;
652 min1.x = min2.x = min.x;
653 max1.x = max2.x = max.x;
654 }
655
656 if (vertical)
657 {
658 double x1bound = max1.x + maxdist / 2, x2bound = min2.x - maxdist / 2;
659 for (JOINENDPOINTLIST::const_iterator i = pl.begin(); i != pl.end();
660 ++i)
661 {
662 if (i->coords.x <= x1bound) pl1.push_back(*i);
663 if (i->coords.x >= x2bound) pl2.push_back(*i);
664 }
665 }
666 else
667 {
668 double y1bound = max1.y + maxdist / 2, y2bound = min2.y - maxdist / 2;
669 for (JOINENDPOINTLIST::const_iterator i = pl.begin(); i != pl.end();
670 ++i)
671 {
672 if (i->coords.y <= y1bound) pl1.push_back(*i);
673 if (i->coords.y >= y2bound) pl2.push_back(*i);
674 }
675 }
676
677 JOIN_DEBUG_PRINT(
678 "[%f, %f, %f, %f] -> [%f, %f, %f, %f]v[%f, %f, %f, %f] %d=>%d+%d",
679 min.x, min.y, max.x, max.y, min1.x, min1.y, max1.x, max1.y, min2.x,
680 min2.y, max2.x, max2.y, int(pl.size()), int(pl1.size()),
681 int(pl2.size()));
682
683 pl.clear();
684
685 vertical = !vertical;
686 return compdists(pl1, ops, min1, max1, vertical, progressObserver, pBase,
687 piece > 1 ? piece / 2 : 1) &&
688 compdists(pl2, ops, min2, max2, vertical, progressObserver,
689 pBase + piece / 2.0, piece > 1 ? piece / 2 : 1);
690 }
691
compdists(JOINENDPOINTLIST & pl,JOINOPLIST & ops,const dpoint_t & min,const dpoint_t & max,bool vertical,ProgressObserver * progressObserver,const double pBase,const double piece) const692 bool Polygons::compdists(JOINENDPOINTLIST& pl, JOINOPLIST& ops,
693 const dpoint_t& min, const dpoint_t& max,
694 bool vertical, ProgressObserver* progressObserver,
695 const double pBase, const double piece) const
696 {
697 int cntr = 0, npoints = pl.size();
698 int progressHowOften = (npoints / piece >= 1) ? int(npoints / piece) : 1;
699 bool cancel = false;
700
701 // split the list whenever it's too long
702 if (npoints > NPOINTS_MAX)
703 {
704 JOIN_DEBUG_PRINT("splitting");
705 splitlist(pl, ops, min, max, vertical, progressObserver, pBase, piece);
706 }
707 else
708 {
709 double maxDistSqr = maxdist * maxdist;
710 vector<bool> alreadyUsed(npoints, false);
711 vector<bool>::iterator ai = alreadyUsed.begin(), aj;
712
713 JOIN_DEBUG_PRINT("computing set of %d points", npoints);
714 for (JOINENDPOINTLIST::const_iterator i = pl.begin();
715 i != pl.end() && !cancel; ++i, ++ai)
716 {
717 privcurve_t* curve = &i->path->priv->curve;
718 const dpoint_t* start = &i->coords;
719 int nJoins = 0;
720
721 // Closed curves cannot be joined. Points outside the bounding
722 // rectangle
723 // are not considered for joining.
724 if (curve->closed || start->x < min.x || start->x > max.x ||
725 start->y < min.y || start->y > max.y)
726 continue;
727
728 aj = ai + 1;
729 for (JOINENDPOINTLIST::const_iterator j = i + 1; j != pl.end();
730 ++j, ++aj)
731 {
732 if (distSqr(start, &j->coords) < maxDistSqr)
733 {
734 dpoint_t *a, *b, *c, *d;
735 privcurve_t* pp_curve = &j->path->priv->curve;
736
737 switch (i->end)
738 {
739 case FRONT:
740 b = &curve->vertex[0];
741 a = b + 1;
742 break;
743 case BACK:
744 b = &curve->vertex[curve->n - 1];
745 a = b - 1;
746 break;
747 default:
748 throw logic_error("NOEND in JOINENDPOINT list");
749 }
750 switch (j->end)
751 {
752 case FRONT:
753 c = &pp_curve->vertex[0];
754 d = c + 1;
755 break;
756 case BACK:
757 c = &pp_curve->vertex[pp_curve->n - 1];
758 d = c - 1;
759 break;
760 default:
761 throw logic_error("NOEND in JOINENDPOINT list");
762 }
763
764 ops.push_back(JOINOP(float(dstfun(a, b, c, d)
765 // self-connection penalization
766 - (i->path == j->path)),
767 endsToType(i->end, j->end), i->path,
768 j->path));
769 nJoins++;
770 *aj = true;
771 }
772 }
773
774 if (nJoins == 1 && !*ai)
775 {
776 // simple connection
777 ops.back().simple = true;
778 }
779
780 if (progressObserver && !(++cntr % progressHowOften))
781 {
782 progressObserver->setPercentage(
783 int(cntr * piece / npoints + pBase));
784 cancel = progressObserver->isInterruptionRequested();
785 }
786 if (cancel) return false;
787 }
788 }
789 if (progressObserver)
790 {
791 progressObserver->setPercentage(int(piece + pBase));
792 }
793 return true;
794 }
795
796 //! \brief Joining of polygons.
797 // \alert Side effect: plist variable can be changed when removing the fist
798 // element on list.
joinPolygons(path_t * & plist,ProgressObserver * progressObserver) const799 bool Polygons::joinPolygons(path_t*& plist,
800 ProgressObserver* progressObserver) const
801 {
802 JOINOPLIST ops;
803 JOINENDPOINTLIST pointlist;
804 path_t* p;
805 int nops, cntr, progressHowOften;
806 bool cancel = false;
807 double inf = std::numeric_limits<double>::infinity();
808 dpoint_t min = {inf, inf}, max = {-inf, -inf};
809
810 list_forall(p, plist)
811 {
812 privcurve_t* p_curve = &p->priv->curve;
813 int p_n = p_curve->n;
814 dpoint_t f = p_curve->vertex[0], b = p_curve->vertex[p_n - 1];
815 pointlist.push_back(JOINENDPOINT(f, FRONT, p));
816 pointlist.push_back(JOINENDPOINT(b, BACK, p));
817 if (f.x > max.x) max.x = f.x;
818 if (f.x < min.x) min.x = f.x;
819 if (f.y > max.y) max.y = f.y;
820 if (f.y < min.y) min.y = f.y;
821 if (b.x > max.x) max.x = b.x;
822 if (b.x < min.x) min.x = b.x;
823 if (b.y > max.y) max.y = b.y;
824 if (b.y < min.y) min.y = b.y;
825 }
826
827 compdists(pointlist, ops, min, max, true, progressObserver, 50, 12);
828
829 sort(ops.begin(), ops.end(), greater_weight());
830
831 nops = ops.size();
832 cntr = 0;
833 progressHowOften = (nops > 28) ? nops / 28 : 1;
834 for (unsigned i = 0; i < ops.size() && !cancel; ++i)
835 {
836 auto& currOp = ops[i];
837
838 if (progressObserver && !((++cntr) % progressHowOften))
839 {
840 progressObserver->setPercentage(cntr * 28 / nops + 62);
841 cancel = progressObserver->isInterruptionRequested();
842 }
843
844 if (simpleonly && !currOp.simple) continue;
845
846 JOINEND jeA = joinEndA(currOp.joinType);
847 JOINEND jeB = joinEndB(currOp.joinType);
848
849 QString oshead, os;
850 if (JOIN_DEBUG)
851 {
852 oshead = QString("%1. %2 %3")
853 .arg(i)
854 .arg(jt2string(currOp.joinType))
855 .arg(currOp.weight);
856 }
857
858 if (i)
859 {
860 bool cantJoin = false;
861
862 // check how previous operations interfere with the current one
863 for (unsigned r = 0; r < i && !cantJoin; ++r)
864 {
865 auto& checkedOp = ops[r];
866
867 if (simpleonly && !checkedOp.simple) continue;
868
869 int oslen;
870 if (JOIN_DEBUG)
871 {
872 os = QString(" %1. %2 %3 ")
873 .arg(r)
874 .arg(jt2string(checkedOp.joinType))
875 .arg(checkedOp.weight);
876 oslen = os.length();
877 }
878
879 // Curve A never reverses. There can only the "supposed to join"
880 // end disappear.
881 if ((currOp.a == checkedOp.a &&
882 joinEndA(checkedOp.joinType) == jeA) ||
883 (currOp.b == checkedOp.a &&
884 joinEndA(checkedOp.joinType) == jeB) ||
885 // Also curve B end can disappear
886 (currOp.a == checkedOp.b &&
887 joinEndB(checkedOp.joinType) == jeA) ||
888 (currOp.b == checkedOp.b &&
889 joinEndB(checkedOp.joinType) == jeB))
890 {
891 if (JOIN_DEBUG) os += "end destroyed; ";
892 cantJoin = true;
893 }
894 // In case the curve is operation's curve B it becomes a part of
895 // curve A
896 // and it can be reversed (see below)
897 else if (currOp.a == checkedOp.b)
898 {
899 // Curve B always becomes a part of curve A
900 currOp.a = checkedOp.a;
901 if (JOIN_DEBUG) os += "A curve merged";
902
903 // in case of FF and BB joins, curve B becomes reversed
904 if (joinEndA(checkedOp.joinType) ==
905 joinEndB(checkedOp.joinType))
906 {
907 jeA = oppositeEnd(jeA);
908 if (JOIN_DEBUG) os += " and reversed";
909 }
910 if (JOIN_DEBUG) os += "; ";
911 }
912 else if (currOp.b == checkedOp.b)
913 {
914 currOp.b = checkedOp.a;
915 if (JOIN_DEBUG) os += "B curve merged";
916
917 if (joinEndA(checkedOp.joinType) ==
918 joinEndB(checkedOp.joinType))
919 {
920 jeB = oppositeEnd(jeB);
921 if (JOIN_DEBUG) os += " and reversed";
922 }
923 if (JOIN_DEBUG) os += "; ";
924 }
925
926 if (JOIN_DEBUG && oslen != os.length())
927 {
928 if (!oshead.isEmpty())
929 {
930 qDebug("%s", oshead.toLatin1().constData());
931 oshead = QString();
932 }
933 qDebug("%s", os.toLatin1().constData());
934 }
935 }
936
937 if (cantJoin)
938 {
939 // remove the unrealized operation from list
940 ops.erase(ops.begin() + i);
941 i--;
942 continue;
943 }
944 }
945
946 // pointers to s path parts were updated earlier
947 // update the join type
948 currOp.joinType = endsToType(jeA, jeB);
949
950 // are we operating on two ends of a single segment?
951 // if yes, give preference to joining with another segment to
952 // prevent loop formation
953 if (currOp.a == currOp.b)
954 {
955 if (JOIN_DEBUG && !oshead.isEmpty())
956 qDebug("%s", oshead.toLatin1().constData());
957
958 if (!currOp.rescheduled)
959 {
960 JOIN_DEBUG_PRINT(" curve closing delayed");
961 currOp.rescheduled = true;
962 ops.push_back(currOp);
963 ops.erase(ops.begin() + i);
964 i--;
965 }
966 else
967 {
968 JOIN_DEBUG_PRINT(" curve closed, jointype %s",
969 jt2string(currOp.joinType));
970 currOp.a->priv->curve.closed = 1;
971 }
972 continue;
973 }
974
975 // perform the actual join
976 privcurve_t* a_curve = &currOp.a->priv->curve;
977 privcurve_t* b_curve = &currOp.b->priv->curve;
978
979 privcurve_t newcurve;
980 privcurve_init(&newcurve, a_curve->n + b_curve->n);
981
982 switch (currOp.joinType)
983 {
984 case FF: // reverse the segment prior to joining
985 for (int d = 0; d < b_curve->n; d++)
986 newcurve.vertex[d] = b_curve->vertex[b_curve->n - d - 1];
987 Q_FALLTHROUGH();
988 case FB:
989 if (currOp.joinType == FB)
990 for (int d = 0; d < b_curve->n; d++)
991 newcurve.vertex[d] = b_curve->vertex[d];
992 for (int d = 0; d < a_curve->n; d++)
993 newcurve.vertex[d + b_curve->n] = a_curve->vertex[d];
994 break;
995 case BB:
996 for (int d = 0; d < b_curve->n; d++)
997 newcurve.vertex[d + a_curve->n] =
998 b_curve->vertex[b_curve->n - d - 1];
999 Q_FALLTHROUGH();
1000 case BF:
1001 if (currOp.joinType == BF)
1002 for (int d = 0; d < b_curve->n; d++)
1003 newcurve.vertex[d + a_curve->n] = b_curve->vertex[d];
1004 for (int d = 0; d < a_curve->n; d++)
1005 newcurve.vertex[d] = a_curve->vertex[d];
1006 break;
1007 case NOJOIN:
1008 throw logic_error("NOJOIN operation in JOINOP list");
1009 default:
1010 throw logic_error("invalid JOINOP");
1011 }
1012
1013 privcurve_free_members(a_curve);
1014 memcpy(a_curve, &newcurve, sizeof(privcurve_t));
1015
1016 path_t* ib = currOp.b;
1017 list_unlink(path_t, plist, ib);
1018 if (!ib) throw logic_error("unlinked ib not in list");
1019 path_free(ib);
1020 }
1021
1022 return !cancel;
1023 }
1024 } // cove
1025
1026 //@}
1027