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