1 /*
2  * Copyright (C) 2018 Damir Porobic <damir.porobic@gmx.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include <QtGui/QPainterPath>
21 #include <QtMath>
22 #include "ShapeHelper.h"
23 
24 namespace kImageAnnotator {
25 
rectTopLeftWithOffset(const QRectF & rect,int offset)26 QPointF ShapeHelper::rectTopLeftWithOffset(const QRectF &rect, int offset)
27 {
28 	auto xOffset = invertOffsetIfLeftSmallerThenRight(rect, offset);
29 	auto yOffset = invertOffsetIfTopSmallerThenBottom(rect, offset);
30 	return { rect.topLeft().x() + xOffset, rect.topLeft().y() + yOffset };
31 }
32 
rectTop(const QRectF & rect)33 QPointF ShapeHelper::rectTop(const QRectF &rect)
34 {
35 	return { rect.center().x(), rect.top() };
36 }
37 
rectTopWithOffset(const QRectF & rect,int offset)38 QPointF ShapeHelper::rectTopWithOffset(const QRectF &rect, int offset)
39 {
40 	offset = invertOffsetIfTopSmallerThenBottom(rect, offset);
41 	return { rect.center().x(), rect.top() + offset };
42 }
43 
rectTopRightWithOffset(const QRectF & rect,int offset)44 QPointF ShapeHelper::rectTopRightWithOffset(const QRectF &rect, int offset)
45 {
46 	auto xOffset = invertOffsetIfLeftSmallerThenRight(rect, -offset);
47 	auto yOffset = invertOffsetIfTopSmallerThenBottom(rect, offset);
48 	return { rect.topRight().x() + xOffset, rect.topRight().y() + yOffset };
49 }
50 
rectRight(const QRectF & rect)51 QPointF ShapeHelper::rectRight(const QRectF &rect)
52 {
53 	return { rect.right(), rect.center().y() };
54 }
55 
rectRightWithOffset(const QRectF & rect,int offset)56 QPointF ShapeHelper::rectRightWithOffset(const QRectF &rect, int offset)
57 {
58 	offset = invertOffsetIfLeftSmallerThenRight(rect, -offset);
59 	return { rect.right() + offset, rect.center().y() };
60 }
61 
rectBottomRightWithOffset(const QRectF & rect,int offset)62 QPointF ShapeHelper::rectBottomRightWithOffset(const QRectF &rect, int offset)
63 {
64 	auto xOffset = invertOffsetIfLeftSmallerThenRight(rect, -offset);
65 	auto yOffset = invertOffsetIfTopSmallerThenBottom(rect, -offset);
66 	return { rect.bottomRight().x() + xOffset, rect.bottomRight().y() + yOffset };
67 }
68 
rectBottom(const QRectF & rect)69 QPointF ShapeHelper::rectBottom(const QRectF &rect)
70 {
71 	return { rect.center().x(), rect.bottom() };
72 }
73 
rectBottomWithOffset(const QRectF & rect,int offset)74 QPointF ShapeHelper::rectBottomWithOffset(const QRectF &rect, int offset)
75 {
76 	offset = invertOffsetIfTopSmallerThenBottom(rect, -offset);
77 	return { rect.center().x(), rect.bottom() + offset };
78 }
79 
rectBottomLeftWithOffset(const QRectF & rect,int offset)80 QPointF ShapeHelper::rectBottomLeftWithOffset(const QRectF &rect, int offset)
81 {
82 	auto xOffset = invertOffsetIfLeftSmallerThenRight(rect, offset);
83 	auto yOffset = invertOffsetIfTopSmallerThenBottom(rect, -offset);
84 	return { rect.bottomLeft().x() + xOffset, rect.bottomLeft().y() + yOffset };
85 }
86 
rectLeft(const QRectF & rect)87 QPointF ShapeHelper::rectLeft(const QRectF &rect)
88 {
89 	return { rect.left(), rect.center().y() };
90 }
91 
rectLeftWithOffset(const QRectF & rect,int offset)92 QPointF ShapeHelper::rectLeftWithOffset(const QRectF &rect, int offset)
93 {
94 	offset = invertOffsetIfLeftSmallerThenRight(rect, offset);
95 	return { rect.left() + offset, rect.center().y() };
96 }
97 
extendLine(const QLineF & line,int extendBy)98 QLineF ShapeHelper::extendLine(const QLineF &line, int extendBy)
99 {
100 	QLineF newLine(line.p2().x(), line.p2().y(), line.p1().x(), line.p1().y());
101 	newLine.setLength(newLine.length() + extendBy / 2);
102 	newLine.setLine(newLine.p2().x(), newLine.p2().y(), newLine.p1().x(), newLine.p1().y());
103 	newLine.setLength(newLine.length() + extendBy / 2);
104 	return newLine;
105 }
106 
rectPointAtIndex(const QRectF & rect,int index)107 QPointF ShapeHelper::rectPointAtIndex(const QRectF &rect, int index)
108 {
109 	if (index == 0) {
110 		return rect.topLeft();
111 	} else if (index == 1) {
112 		return ShapeHelper::rectTop(rect);
113 	} else if (index == 2) {
114 		return rect.topRight();
115 	} else if (index == 3) {
116 		return ShapeHelper::rectRight(rect);
117 	} else if (index == 4) {
118 		return rect.bottomRight();
119 	} else if (index == 5) {
120 		return ShapeHelper::rectBottom(rect);
121 	} else if (index == 6) {
122 		return rect.bottomLeft();
123 	} else if (index == 7) {
124 		return ShapeHelper::rectLeft(rect);
125 	} else {
126 		qCritical("Invalid rectangle index provided, returning empty point.");
127 		return {};
128 	}
129 }
130 
setRectPointAtIndex(const QRectF & rect,int index,const QPointF & pos,bool keepAspectRatio)131 QRectF ShapeHelper::setRectPointAtIndex(const QRectF &rect, int index, const QPointF &pos, bool keepAspectRatio)
132 {
133 	auto updatedRect = rect;
134 	if (index == 0) {
135 		if (!keepAspectRatio) {
136 			updatedRect.setTopLeft(pos);
137 		} else {
138 			const auto xDif = updatedRect.topLeft().x() - pos.x();
139 			const auto yDif = updatedRect.topLeft().y() - pos.y();
140 			const auto min = qMin(xDif, yDif);
141 			const auto newX = updatedRect.topLeft().x() - min;
142 			const auto newY = updatedRect.topLeft().y() - min;
143 			updatedRect.setTopLeft(QPointF(newX, newY));
144 		}
145 	} else if (index == 1) {
146 		updatedRect.setTop(pos.y());
147 	} else if (index == 2) {
148 		if (!keepAspectRatio) {
149 			updatedRect.setTopRight(pos);
150 		} else {
151 			const auto xDif = pos.x() - updatedRect.topRight().x();
152 			const auto yDif = updatedRect.topRight().y() - pos.y();
153 			const auto min = qMin(xDif, yDif);
154 			const auto newX = updatedRect.topRight().x() + min;
155 			const auto newY = updatedRect.topRight().y() - min;
156 			updatedRect.setTopRight(QPointF(newX, newY));
157 		}
158 	} else if (index == 3) {
159 		updatedRect.setRight(pos.x());
160 	} else if (index == 4) {
161 		if (!keepAspectRatio) {
162 			updatedRect.setBottomRight(pos);
163 		} else {
164 			const auto xDif = pos.x() - updatedRect.bottomRight().x();
165 			const auto yDif = pos.y() - updatedRect.bottomRight().y();
166 			const auto min = qMin(xDif, yDif);
167 			const auto newX = updatedRect.bottomRight().x() + min;
168 			const auto newY = updatedRect.bottomRight().y() + min;
169 			updatedRect.setBottomRight(QPointF(newX, newY));
170 		}
171 	} else if (index == 5) {
172 		updatedRect.setBottom(pos.y());
173 	} else if (index == 6) {
174 		if (!keepAspectRatio) {
175 			updatedRect.setBottomLeft(pos);
176 		} else {
177 			const auto xDif = updatedRect.bottomLeft().x() - pos.x();
178 			const auto yDif = pos.y() - updatedRect.bottomLeft().y();
179 			const auto min = qMin(xDif, yDif);
180 			const auto newX = updatedRect.bottomLeft().x() - min;
181 			const auto newY = updatedRect.bottomLeft().y() + min;
182 			updatedRect.setBottomLeft(QPointF(newX, newY));
183 		}
184 	} else if (index == 7) {
185 		updatedRect.setLeft(pos.x());
186 	} else {
187 		qCritical("Invalid rectangle index provided, returning unchanged rectangle.");
188 	}
189 	return updatedRect;
190 }
191 
smoothOut(const QPainterPath & path,int smoothFactor)192 QPainterPath ShapeHelper::smoothOut(const QPainterPath &path, int smoothFactor)
193 {
194 	auto points = getPathPoints(path, smoothFactor);
195 
196 	// Don't proceed if we only have 3 or less points.
197 	if (points.count() < 3) {
198 		return path;
199 	}
200 
201 	return createSmoothPath(points);
202 }
203 
createSmoothPath(const QList<QPointF> & points)204 QPainterPath ShapeHelper::createSmoothPath(const QList<QPointF> &points)
205 {
206 	QPointF point1, point2;
207 	QPainterPath smoothPath;
208 
209 	smoothPath.moveTo(points[0]);
210 	smoothPath.lineTo(points[1]);
211 	for (auto i = 2; i < points.count() - 2; i++) {
212 		point1 = getBeginOfRounding(points[i], points[i + 1]);
213 		smoothPath.quadTo(points[i], point1);
214 		point2 = getEndOfRounding(points[i], points[i + 1]);
215 		smoothPath.lineTo(point2);
216 	}
217 	smoothPath.lineTo(points[points.count() - 1]);
218 	return smoothPath;
219 }
220 
getPathPoints(const QPainterPath & path,int smootFactor)221 QList<QPointF> ShapeHelper::getPathPoints(const QPainterPath &path, int smootFactor)
222 {
223 	QList<QPointF> points;
224 	QPointF p;
225 	for (auto i = 0; i < path.elementCount() - 1; i++) {
226 		p = QPointF(path.elementAt(i).x, path.elementAt(i).y);
227 
228 		// Except for first and last points, check what the distance between two
229 		// points is and if its less the min, don't add them to the list.
230 		if (points.count() > 1 && (i < path.elementCount() - 2) && (MathHelper::distanceBetweenPoints(points.last(), p) < smootFactor)) {
231 			continue;
232 		}
233 		points.append(p);
234 	}
235 	return points;
236 }
237 
invertOffsetIfLeftSmallerThenRight(const QRectF & rect,int xOffset)238 int ShapeHelper::invertOffsetIfLeftSmallerThenRight(const QRectF &rect, int xOffset)
239 {
240 	if (rect.left() < rect.right()) {
241 		xOffset *= -1;
242 	}
243 	return xOffset;
244 }
245 
invertOffsetIfTopSmallerThenBottom(const QRectF & rect,int yOffset)246 int ShapeHelper::invertOffsetIfTopSmallerThenBottom(const QRectF &rect, int yOffset)
247 {
248 	if (rect.top() < rect.bottom()) {
249 		yOffset *= -1;
250 	}
251 	return yOffset;
252 }
253 
getBeginOfRounding(const QPointF & point1,const QPointF & point2)254 QPointF ShapeHelper::getBeginOfRounding(const QPointF &point1, const QPointF &point2)
255 {
256 	QPointF startPoint;
257 	auto rat = getRoundingRate(point1, point2);
258 	startPoint.setX((1.0 - rat) * point1.x() + rat * point2.x());
259 	startPoint.setY((1.0 - rat) * point1.y() + rat * point2.y());
260 	return startPoint;
261 }
262 
getEndOfRounding(const QPointF & point1,const QPointF & point2)263 QPointF ShapeHelper::getEndOfRounding(const QPointF &point1, const QPointF &point2)
264 {
265 	QPointF endPoint;
266 	auto rat = getRoundingRate(point1, point2);
267 	endPoint.setX(rat * point1.x() + (1.0 - rat) * point2.x());
268 	endPoint.setY(rat * point1.y() + (1.0 - rat) * point2.y());
269 	return endPoint;
270 }
271 
getRoundingRate(const QPointF & point1,const QPointF & point2)272 double ShapeHelper::getRoundingRate(const QPointF &point1, const QPointF &point2)
273 {
274 	auto rat = 10.0 / MathHelper::distanceBetweenPoints(point1, point2);
275 	if (rat > 0.5) {
276 		rat = 0.5;
277 	}
278 	return rat;
279 }
280 
intersectionBetweenRectAndLineFromCenter(const QLineF & line,const QRectF & rect)281 QPointF ShapeHelper::intersectionBetweenRectAndLineFromCenter(const QLineF &line, const QRectF &rect)
282 {
283 	QLineF leftLine(rect.bottomLeft(), rect.topLeft());
284 	QLineF topLine(rect.topLeft(), rect.topRight());
285 	QLineF rightLine(rect.topRight(), rect.bottomRight());
286 	QLineF bottomLine(rect.bottomLeft(), rect.bottomRight());
287 
288 	QPointF intersectionPoint;
289 	if(linesIntersect(line, leftLine, intersectionPoint)) {
290 		return intersectionPoint;
291 	} else if(linesIntersect(line, topLine, intersectionPoint)) {
292 		return intersectionPoint;
293 	} else if(linesIntersect(line, rightLine, intersectionPoint)) {
294 		return intersectionPoint;
295 	} else if(linesIntersect(line, bottomLine, intersectionPoint)) {
296 		return intersectionPoint;
297 	}
298 	return intersectionPoint;
299 }
300 
linesIntersect(const QLineF & line1,const QLineF & line2,QPointF & intersection)301 bool ShapeHelper::linesIntersect(const QLineF &line1, const QLineF &line2, QPointF &intersection)
302 {
303 	return line1.intersect(line2, &intersection) == QLineF::BoundedIntersection;
304 }
305 
306 } // namespace kImageAnnotator
307