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