Krita Source Code Documentation
Loading...
Searching...
No Matches
PerspectiveEllipseAssistant.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2022 Srirupa Datta <srirupa.sps@gmail.com>
3 */
4
7
8
9#include <klocalizedstring.h>
10#include "kis_debug.h"
11#include <QPainter>
12#include <QPainterPath>
13#include <QLinearGradient>
14#include <QTransform>
15
16#include <kis_canvas2.h>
18#include "kis_algebra_2d.h"
19#include <Eigen/Eigenvalues>
20
21#include <math.h>
22#include<QDebug>
23#include <QtMath>
24
25#include <functional>
26
27
28// ################################## Ellipse in Polygon (in Perspective) #######################################
29
31{
32public:
33
35
36 // nomenclature:
37 // "final ellipse" - ellipse that the user wants
38 // "rerotated ellipse" - "final ellipse" that has been rotated (transformed) to have axes parallel to X and Y axes
39 // (called "rerotated" because now the rotation angle is 0)
40 // "canonical ellipse" - "final ellipse" that has been rotated to have axes parallel to X and Y axes,
41 // *and* moved so that the center is in point (0, 0)
42 // --- every "ellipse" above also means "coordination system for that ellipse"
43
44 // "ellipse formula" - ax^2 + bxy + cy^2 + dx + ey + f = 0
45 // "vertices" - points on axes
46
47 // functions
48
56 bool updateToPolygon(QVector<QPointF> _polygon);
57
64 bool setSimpleEllipseVertices(Ellipse& ellipse) const;
65
66 bool isValid() const { return m_valid; }
67
76 static bool formulaRepresentsAnEllipse(double a, double b, double c);
77
78 // unused for now; will be used to move the ellipse towards any vanishing point
79 // might need more info about vanishing points (for example, might need all points)
80 // moveTowards(QPointF vanishingPoint, QPointF cursorStartPoint, QPointF cursorEndPoint);
81
82 // ----- data -----
83 // keep the known-size-vectors the same size!
84
86 QTransform originalTransform; // original square-to-polygon transform, including perspective
87
88
89 QVector<double> finalFormula; // final ellipse formula using ax^2 + bxy + cy^2 + dx + ey + f = 0
90 QVector<double> rerotatedFormula; // rerotated ellipse formula using ax^2 + bxy + cy^2 + dx + ey + f = 0
91
92 double finalAxisAngle {0.0}; // theta - angle of the final ellipse's X axis
93 double finalAxisReverseAngleCos {0.0}; // cos(-theta) -> used for calculating rerotatedFormula
94 double finalAxisReverseAngleSin {0.0}; // sin(-theta) -> used for calculating rerotatedFormula
95
96 QVector<double> finalEllipseCenter; // always just two values; QPointF could have too low of a precision for calculations
97
98 double axisXLength {0.0}; // all "final", "rerotated" and "canonical" ellipses have the same axes lengths
99 double axisYLength {0.0};
100
101 QVector<QPointF> finalVertices; // used to draw ellipses and project the cursor points
102
103protected:
104
105 void setFormula(QVector<double>& formula, double a, double b, double c, double d, double e, double f);
106 void setPoint(QVector<double>& point, double x, double y);
107
108
109 bool m_valid {false};
110
111};
112
113
115{
116 finalFormula.clear();
117 rerotatedFormula.clear();
118 finalFormula << 1 << 0 << 1 << 0 << 0 << 0;
119 rerotatedFormula << 1 << 0 << 1 << 0 << 0 << 0;
120
121 finalEllipseCenter.clear();
122 finalEllipseCenter << 0 << 0;
123
124 finalVertices.clear();
125 finalVertices << QPointF(-1, 0) << QPointF(1, 0) << QPointF(0, 1);
126}
127
129{
130 QTransform transform;
131
132 m_valid = false; // let's make it false in case we return in the middle of the work
133 polygon = _polygon; // the assistant needs to know the polygon even when it doesn't allow for a correct ellipse
134
135 // this calculates the perspective transform that represents the current quad (polygon)
136 // this is the transform that changes the original (0, 0, 1, 1) square to the quad
137 // that means that our "original" ellipse is the circle in that square (with center in (0.5, 0.5), and radius 0.5)
138 if (!QTransform::squareToQuad(polygon, transform)) {
139 return false;
140 }
141
142 originalTransform = transform;
143
144 // using the perspective transform, we can calculate some points on the ellipse
145 // any points from the original ellipse would work here
146 // but pt1-4 are just the simplest ones to write
147 // and pR is another one easy to calculate (common point between the original ellipse and a line `y = x`)
148 QPointF pt1 = originalTransform.map(QPointF(0.5, 1.0));
149 QPointF pt2 = originalTransform.map(QPointF(1.0, 0.5));
150 QPointF pt3 = originalTransform.map(QPointF(0.5, 0.0));
151 QPointF pt4 = originalTransform.map(QPointF(0.0, 0.5));
152 // a point on the ellipse and on the `y = x` line
153 QPointF ptR = originalTransform.map(QPointF(0.5 - 1/(2*sqrt(2)), 0.5 - 1/(2*sqrt(2))));
154
155
156 // using the points from above (pt1-4 and ptR) we can construct a linear equation for the final ellipse formula
157 // the general ellipse formula is: `ax^2 + bxy + cy^2 + dx + ey + f = 0`
158 // but since a cannot ever be 0, we can temporarily reduce the formula to be `x^2 + Bxy + Cy^2 + Dx + Ey + F = 0`
159 // where B = b/a etc.
160 Eigen::MatrixXd A(5, 5);
161 A << ptR.x() * ptR.y(), ptR.y() * ptR.y(), ptR.x(), ptR.y(), 1.0,
162 pt1.x() * pt1.y(), pt1.y() * pt1.y(), pt1.x(), pt1.y(), 1.0,
163 pt2.x() * pt2.y(), pt2.y() * pt2.y(), pt2.x(), pt2.y(), 1.0,
164 pt3.x() * pt3.y(), pt3.y() * pt3.y(), pt3.x(), pt3.y(), 1.0,
165 pt4.x() * pt4.y(), pt4.y() * pt4.y(), pt4.x(), pt4.y(), 1.0;
166
167 Eigen::VectorXd bVector(5);
168 bVector << - ptR.x() * ptR.x(), - pt1.x() * pt1.x(), - pt2.x() * pt2.x(), - pt3.x() * pt3.x(), - pt4.x() * pt4.x();
169
170 Eigen::VectorXd xSolution = A.fullPivLu().solve(bVector);
171
172 // generic ellipse formula coefficients for the final formula
173 // assigned to new variables to better see the calculations
174 // (even with "x" as a solution vector variable, it would be difficult to spot error when everything looks like x(2)*x(4)/x(3)*x(1) etc.)
175 qreal a = 1;
176 qreal b = xSolution(0);
177
178 qreal c = xSolution(1);
179 qreal d = xSolution(2);
180
181 qreal e = xSolution(3);
182 qreal f = xSolution(4);
183
184 // check if this is an ellipse
185 if (!formulaRepresentsAnEllipse(a, b, c)) {
186 return false;
187 }
188
189 setFormula(finalFormula, a, b, c, d, e, f);
190
191 // x = (be - 2cd)/(4c - b^2)
192 // y = (bd - 2e)/(4c - b^2)
193 finalEllipseCenter.clear();
194 finalEllipseCenter << ((double)b*e - 2*c*d)/(4*c - b*b) << ((double)b*d - 2*e)/(4*c - b*b);
195 finalAxisAngle = qAtan2(b, a - c)/2;
196
197 // use finalAxisAngle to find the cos and sin
198 // and replace the final coordinate system with the rerotated one
199 qreal K = qCos(-finalAxisAngle);
200 qreal L = qSin(-finalAxisAngle);
201
202 // this allows to calculate the formula for the rerotated ellipse
203 qreal aprim = K*K*a - K*L*b + L*L*c;
204 qreal bprim = 2*K*L*a + K*K*b - L*L*b - 2*K*L*c;
205 qreal cprim = L*L*a + K*L*b + K*K*c;
206 qreal dprim = K*d - L*e;
207 qreal eprim = L*d + K*e;
208 qreal fprim = f;
209
210 if (!formulaRepresentsAnEllipse(aprim, bprim, cprim)) {
211 return false;
212 }
213
216
217 setFormula(rerotatedFormula, aprim, bprim, cprim, dprim, eprim, fprim);
218
219 // third attempt at new center:
220 // K' = K
221 // L' = -L
222 // note that this will be in a different place, because the ellipse wasn't moved to have center in (0, 0), but still rotate around point (0,0)
223 // and any point that is not (0, 0), when rotated around (0, 0) with an angle that isn't 0, 360 etc. degrees, will end up in a different place
224 QPointF rerotatedCenter = QPointF(K*finalEllipseCenter[0] - L*finalEllipseCenter[1], K*finalEllipseCenter[1] + L*finalEllipseCenter[0]);
225
226 qreal rx = sqrt(qPow(rerotatedCenter.x(), 2) + qPow(rerotatedCenter.y(), 2)*cprim/aprim - fprim/aprim);
227 qreal ry = sqrt(rx*rx*aprim/cprim);
228
229 axisXLength = rx;
230 axisYLength = ry;
231
232#if 0 // debug
233 // they should be very close to cprim, dprim etc., when multiplied by aprim (since this only gives us a formula where aprim_recreated would be equal to 1)
234 qreal cprim_recreated = (rx*rx)/(ry*ry);
235 qreal dprim_recreated = -2*rerotatedCenter.x();
236 qreal eprim_recreated = -2*rerotatedCenter.y()*(rx*rx)/(ry*ry);
237 qreal fprim_recreated = qPow(rerotatedCenter.x(), 2) + qPow(rerotatedCenter.y(), 2)*(rx*rx)/(ry*ry) - (rx*rx);
238
239 if (debug) qCritical() << "recreated equation (with 1): " << 1 << 0 << cprim_recreated << dprim_recreated << eprim_recreated << fprim_recreated;
240 if (debug) qCritical() << "recreated equation: (actual)" << aprim << 0 << aprim*cprim_recreated << aprim*dprim_recreated << aprim*eprim_recreated << aprim*fprim_recreated;
241
242 qreal eps = 0.00001;
243 auto fuzzyCompareWithEps = [eps] (qreal a, qreal b) { return abs(a - b) < eps; };
244
245 KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyCompareWithEps(aprim*cprim_recreated, cprim));
246 KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyCompareWithEps(aprim*dprim_recreated, dprim));
247 KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyCompareWithEps(aprim*eprim_recreated, eprim));
248 KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyCompareWithEps(aprim*fprim_recreated, fprim));
249
250#endif
251
252 auto convertToPreviousCoordsSystem = [K, L] (QPointF p) { return QPointF(K*p.x() + L*p.y(), K*p.y() - L*p.x()); };
253
254 // they most probably don't need a higher precision than float
255 // (though they are used to calculate the brush position...)
256 QPointF leftVertexRerotated = rerotatedCenter + QPointF(-rx, 0);
257 QPointF rightVertedRerotated = rerotatedCenter + QPointF(rx, 0);
258 QPointF topVertedRerotated = rerotatedCenter + QPointF(0, ry);
259
260 QPointF leftVertexFinal = convertToPreviousCoordsSystem(leftVertexRerotated);
261 QPointF rightVertexFinal = convertToPreviousCoordsSystem(rightVertedRerotated);
262 QPointF topVertexFinal = convertToPreviousCoordsSystem(topVertedRerotated);
263
264 QVector<QPointF> result;
265 result << leftVertexFinal << rightVertexFinal << topVertexFinal;
266
267 finalVertices = result;
268
269 m_valid = true;
270 return true;
271}
272
274{
275 if (finalVertices.size() > 2) {
276 return ellipse.set(finalVertices[0], finalVertices[1], finalVertices[2]);
277 }
278 return false;
279}
280
281bool EllipseInPolygon::formulaRepresentsAnEllipse(double a, double b, double c)
282{
283 return (b*b - 4*a*c) < 0;
284}
285
286void EllipseInPolygon::setFormula(QVector<double> &formula, double a, double b, double c, double d, double e, double f)
287{
288 if (formula.size() != 6) {
289 formula.clear();
290 formula << a << b << c << d << e << f;
291 } else {
292 formula[0] = a;
293 formula[1] = b;
294 formula[2] = c;
295 formula[3] = d;
296 formula[4] = e;
297 formula[5] = f;
298 }
299}
300
301void EllipseInPolygon::setPoint(QVector<double> &point, double x, double y)
302{
303 if (point.size() != 2) {
304 point.clear();
305 point << x << y;
306 } else {
307 point[0] = x;
308 point[1] = y;
309 }
310}
311
312
313// ################################## Perspective Ellipse Assistant #######################################
314
315
329
332 , KisPaintingAssistant("perspective ellipse", i18n("Perspective Ellipse assistant"))
333 , d(new Private())
334{
335
336}
337
339
340PerspectiveEllipseAssistant::PerspectiveEllipseAssistant(const PerspectiveEllipseAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
341 : KisAbstractPerspectiveGrid(rhs.parent())
342 , KisPaintingAssistant(rhs, handleMap)
343 , d(new Private())
344{
345 updateCache();
346}
347
348KisPaintingAssistantSP PerspectiveEllipseAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
349{
350 return KisPaintingAssistantSP(new PerspectiveEllipseAssistant(*this, handleMap));
351}
352
353QPointF PerspectiveEllipseAssistant::project(const QPointF& pt, const QPointF& strokeBegin)
354{
355 Q_UNUSED(strokeBegin);
356 Q_ASSERT(isAssistantComplete());
357
358 d->ellipseInPolygon.setSimpleEllipseVertices(d->simpleEllipse);
359
360 return d->simpleEllipse.project(pt);
361}
362
363QPointF PerspectiveEllipseAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin, const bool /*snapToAny*/, qreal /*moveThresholdPt*/)
364{
365 return project(pt, strokeBegin);
366}
367
368void PerspectiveEllipseAssistant::adjustLine(QPointF &point, QPointF &strokeBegin)
369{
370 point = QPointF();
371 strokeBegin = QPointF();
372}
373
374void PerspectiveEllipseAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
375{
376 gc.save();
377 gc.resetTransform();
378
379 bool isEditing = false;
380
381 if (canvas) {
382 isEditing = canvas->paintingAssistantsDecoration()->isEditingAssistants();
383 }
384
385 QTransform initialTransform = converter->documentToWidgetTransform();
386
387 // need to update ellipse cache
388 updateCache();
389
390 QPolygonF poly = d->ellipseInPolygon.polygon;
391 QTransform transform = d->ellipseInPolygon.originalTransform; // unused, but computed for caching purposes
392
393
394 if (isEllipseValid() && assistantVisible==true) {
395 // draw vanishing points
396 if (d->cache.vanishingPoint1) {
397 drawX(gc, initialTransform.map(d->cache.vanishingPoint1.get()));
398 }
399 if (d->cache.vanishingPoint2) {
400 drawX(gc, initialTransform.map(d->cache.vanishingPoint2.get()));
401 }
402 }
403
404 // draw ellipse and axes
405 if (isEllipseValid() && (assistantVisible || previewVisible || isEditing)) { // ensure that you only draw the ellipse if it's valid - otherwise it would just show some outdated one
406 QPointF mousePos = effectiveBrushPosition(converter, canvas);
407 gc.setTransform(initialTransform);
408 gc.setTransform(d->simpleEllipse.getTransform().inverted(), true);
409
410 QPainterPath path;
411 path.addEllipse(QPointF(0.0, 0.0), d->simpleEllipse.semiMajor(), d->simpleEllipse.semiMinor());
412
413 if (assistantVisible || isEditing) {
414 drawPath(gc, path, isSnappingActive());
415 } else if (previewVisible && isSnappingActive() && boundingRect().contains(initialTransform.inverted().map(mousePos.toPoint()), false)) {
416 drawPreview(gc, path);
417 }
418
419 if (isEditing) {
420 QPainterPath axes;
421 axes.moveTo(QPointF(-d->simpleEllipse.semiMajor(), 0));
422 axes.lineTo(QPointF(d->simpleEllipse.semiMajor(), 0));
423
424 axes.moveTo(QPointF(0, -d->simpleEllipse.semiMinor()));
425 axes.lineTo(QPointF(0, d->simpleEllipse.semiMinor()));
426
427 gc.save();
428
429 QPen p(gc.pen());
430 p.setCosmetic(true);
431 p.setStyle(Qt::DotLine);
432 QColor color = effectiveAssistantColor();
433 if (!isSnappingActive()) {
434 color.setAlpha(color.alpha()*0.2);
435 }
436 p.setWidthF(1.5);
437 p.setColor(color);
438 gc.setPen(p);
439
440 gc.drawPath(axes);
441
442 gc.restore();
443 }
444
445 gc.setTransform(converter->documentToWidgetTransform());
446 gc.setTransform(d->ellipseInPolygon.originalTransform, true);
447
448 // drawing original axes ("lines to touching points")
449 QPointF pt1 = QPointF(0.5, 1.0);
450 QPointF pt2 = QPointF(1.0, 0.5);
451 QPointF pt3 = QPointF(0.5, 0.0);
452 QPointF pt4 = QPointF(0.0, 0.5);
453
454 QPainterPath touchingLine;
455
456 touchingLine.moveTo(pt1);
457 touchingLine.lineTo(pt3);
458
459 touchingLine.moveTo(pt2);
460 touchingLine.lineTo(pt4);
461
462 if (assistantVisible) {
463 drawPath(gc, touchingLine, isSnappingActive());
464 }
465 }
466
467
468 gc.setTransform(converter->documentToWidgetTransform());
469
470 if (assistantVisible || isEditing) {
471 if (!isEllipseValid()) {
472 // color red for an invalid transform, but not for an incomplete one
473 if(isAssistantComplete()) {
474 QPainterPath path;
475 QPolygonF polyAllConnected;
476 // that will create a triangle with a point inside connected to all vertices of the triangle
477 polyAllConnected << *handles()[0] << *handles()[1] << *handles()[2] << *handles()[3] << *handles()[0] << *handles()[2] << *handles()[1] << *handles()[3];
478 path.addPolygon(polyAllConnected);
479 drawError(gc, path);
480 } else {
481 QPainterPath path;
482 path.addPolygon(poly);
483 drawPath(gc, path, isSnappingActive());
484 }
485 } else {
486 gc.setPen(QColor(0, 0, 0, 125));
487 gc.setTransform(transform, true);
488 QPainterPath path;
489 for (int y = 0; y <= 1; ++y)
490 {
491 QLineF line = QLineF(QPointF(0.0, y), QPointF(1.0, y));
492 KisAlgebra2D::cropLineToRect(line, gc.window(), false, false);
493 path.moveTo(line.p1());
494 path.lineTo(line.p2());
495 }
496 for (int x = 0; x <= 1; ++x)
497 {
498 QLineF line = QLineF(QPointF(x, 0.0), QPointF(x, 1.0));
499 KisAlgebra2D::cropLineToRect(line, gc.window(), false, false);
500 path.moveTo(line.p1());
501 path.lineTo(line.p2());
502 }
503
504 drawPath(gc, path, isSnappingActive());
505 }
506 }
507
508 gc.restore();
509 KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
510
511}
512
513
514void PerspectiveEllipseAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
515{
516 Q_UNUSED(converter);
517 Q_UNUSED(gc);
518 Q_UNUSED(assistantVisible);
519}
520
522{
523 if (!isAssistantComplete()) {
525 }
526
527 if (d->ellipseInPolygon.setSimpleEllipseVertices(d->simpleEllipse)) {
528 return d->simpleEllipse.boundingRect().adjusted(-2, -2, 2, 2).toAlignedRect();
529 } else {
530 return QRect();
531 }
532}
533
535{
536 QPointF centroid(0, 0);
537 for (int i = 0; i < 4; ++i) {
538 centroid += *handles()[i];
539 }
540
541 return centroid * 0.25;
542}
543
545{
546 return isAssistantComplete() && d->ellipseInPolygon.isValid();
547}
548
550{
551 // handles -> points -> polygon
552 d->cacheValid = false;
553 // check the cached points, whether they are the same as handles
554 if (d->cachedPoints.size() == handles().size()) {
555 for (int i = 0; i < handles().size(); ++i) {
556 if (d->cachedPoints[i] != *handles()[i]) break;
557 if (i == handles().size() - 1) {
558 // that means the cache is up to date, because the loop was still going
559 d->cacheValid = true;
560 return;
561 }
562 }
563 }
564
565 d->cachedPoints = QVector<QPointF>();
566 for (int i = 0; i < handles().size(); ++i) {
567 d->cachedPoints << *handles()[i];
568 }
569
570
571 QPolygonF poly = QPolygonF(d->cachedPoints);
572
573 if (!PerspectiveBasedAssistantHelper::getTetragon(handles(), isAssistantComplete(), poly)) { // this function changes poly to some "standardized" version, or a triangle when it cannot be achieved
574
575 poly = QPolygonF(d->cachedPoints);
576 poly << d->cachedPoints[0];
577 d->ellipseInPolygon.updateToPolygon(poly);
578 d->cacheValid = true;
579 return;
580 }
581
582 d->ellipseInPolygon.updateToPolygon(poly);
583 if (d->ellipseInPolygon.isValid()) {
584 d->ellipseInPolygon.setSimpleEllipseVertices(d->simpleEllipse);
585 }
586
588 d->cacheValid = true;
589
590}
591
593{
594 return handles().size() >= 4;
595}
596
597bool PerspectiveEllipseAssistant::contains(const QPointF &point) const
598{
599
600 QPolygonF poly;
602 return poly.containsPoint(point, Qt::OddEvenFill);
603}
604
605qreal PerspectiveEllipseAssistant::distance(const QPointF &point) const
606{
607 KIS_SAFE_ASSERT_RECOVER_NOOP(d->cacheValid);
609}
610
612{
613 return isSnappingActive();
614}
615
619
623
625{
626 return "perspective ellipse";
627}
628
630{
631 return i18n("Perspective Ellipse");
632}
633
638
const Params2D p
static bool formulaRepresentsAnEllipse(double a, double b, double c)
formulaRepresentsAnEllipse parameters are first three coefficients from a formula: ax^2 + bxy + cy^2 ...
QVector< double > finalEllipseCenter
bool updateToPolygon(QVector< QPointF > _polygon)
updateToPolygon This function makes all the necessary calculations and updates all data,...
void setFormula(QVector< double > &formula, double a, double b, double c, double d, double e, double f)
void setPoint(QVector< double > &point, double x, double y)
QVector< double > rerotatedFormula
bool setSimpleEllipseVertices(Ellipse &ellipse) const
setSimpleEllipseVertices sets vertices of this ellipse to the "simple ellipse" class to be drawn and ...
bool set(const QPointF &m1, const QPointF &m2, const QPointF &p)
Definition Ellipse.cc:23
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration() const
virtual QRect boundingRect() const
void drawX(QPainter &painter, const QPointF &pt)
QPointF effectiveBrushPosition(const KisCoordinatesConverter *converter, KisCanvas2 *canvas) const
Query the effective brush position to be used for preview lines. This is intended to be used for pain...
void drawError(QPainter &painter, const QPainterPath &path)
void drawPath(QPainter &painter, const QPainterPath &path, bool drawActive=true)
void drawPreview(QPainter &painter, const QPainterPath &path)
virtual void transform(const QTransform &transform)
virtual void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, bool cached, KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true)
const QList< KisPaintingAssistantHandleSP > & handles() const
static void updateCacheData(CacheData &cache, const QPolygonF &poly)
static qreal distanceInGrid(const QList< KisPaintingAssistantHandleSP > &handles, bool isAssistantComplete, const QPointF &point)
static bool getTetragon(const QList< KisPaintingAssistantHandleSP > &handles, bool isAssistantComplete, QPolygonF &outPolygon)
KisPaintingAssistant * createPaintingAssistant() const override
PerspectiveBasedAssistantHelper::CacheData cache
void adjustLine(QPointF &point, QPointF &strokeBegin) override
QPointF adjustPosition(const QPointF &point, const QPointF &strokeBegin, const bool snapToAny, qreal moveThresholdPt) override
bool contains(const QPointF &point) const override
void drawCache(QPainter &gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override
performance layer where the graphics can be drawn from a cache instead of generated every render upda...
QPointF getDefaultEditorPosition() const override
QPointF project(const QPointF &pt, const QPointF &strokeBegin)
KisPaintingAssistantSP clone(QMap< KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP > &handleMap) const override
void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, bool cached, KisCanvas2 *canvas, bool assistantVisible=true, bool previewVisible=true) override
qreal distance(const QPointF &point) const override
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
const qreal eps
QSharedPointer< KisPaintingAssistant > KisPaintingAssistantSP
Definition kis_types.h:189
void cropLineToRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond)