Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_perspective_transform_strategy.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
3 * SPDX-FileCopyrightText: 2022 Carsten Hartenfels <carsten.hartenfels@pm.me>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
9
10#include <QPointF>
11#include <QPainter>
12#include <QPainterPath>
13#include <QMatrix4x4>
14#include <QVector2D>
15
16#include <Eigen/Dense>
17
20#include "tool_transform_args.h"
22#include "krita_utils.h"
23#include "kis_cursor.h"
24#include "kis_transform_utils.h"
26
27namespace {
28enum StrokeFunction {
29 DRAG_HANDLE = 0,
30 DRAG_X_VANISHING_POINT,
31 DRAG_Y_VANISHING_POINT,
32 MOVE,
33 NONE
34};
35
36enum HandleIndexes {
37 HANDLE_TOP_LEFT = 0,
38 HANDLE_TOP_RIGHT,
39 HANDLE_BOTTOM_LEFT,
40 HANDLE_BOTTOM_RIGHT,
41 HANDLE_MIDDLE_TOP,
42 HANDLE_MIDDLE_BOTTOM,
43 HANDLE_MIDDLE_LEFT,
44 HANDLE_MIDDLE_RIGHT,
45 HANDLE_COUNT,
46};
47}
48
50{
52 const KisCoordinatesConverter *_converter,
53 ToolTransformArgs &_currentArgs,
55 : q(_q),
56 converter(_converter),
57 currentArgs(_currentArgs),
58 transaction(_transaction),
59 imageTooBig(false),
60 isTransforming(false)
61 {
62 }
63
65
67
69
74
75
78
81
82 QTransform handlesTransform;
83
85
86 StrokeFunction function {NONE};
87
88 struct HandlePoints {
89 bool xVanishingExists {false};
90 bool yVanishingExists {false};
91
92 QPointF xVanishing;
93 QPointF yVanishing;
94 };
96
97 QTransform transform;
98
102
103 bool imageTooBig {false};
104
105 QPointF clickPos;
107 bool isTransforming {false};
108
109 QCursor getScaleCursor(const QPointF &handlePt);
110 QCursor getShearCursor(const QPointF &start, const QPointF &end);
113
114 void transformIntoArgs(const Eigen::Matrix3f &t);
115 QTransform transformFromArgs();
116};
117
119 KoSnapGuide *snapGuide,
120 ToolTransformArgs &currentArgs,
122 : KisSimplifiedActionPolicyStrategy(converter, snapGuide),
123 m_d(new Private(this, converter, currentArgs, transaction))
124{
125}
126
130
132{
133 srcHandlePoints.resize(HANDLE_COUNT);
134 srcHandlePoints[HANDLE_TOP_LEFT] = transaction.originalTopLeft();
135 srcHandlePoints[HANDLE_TOP_RIGHT] = transaction.originalTopRight();
136 srcHandlePoints[HANDLE_BOTTOM_LEFT] = transaction.originalBottomLeft();
137 srcHandlePoints[HANDLE_BOTTOM_RIGHT] = transaction.originalBottomRight();
138 srcHandlePoints[HANDLE_MIDDLE_TOP] = transaction.originalMiddleTop();
139 srcHandlePoints[HANDLE_MIDDLE_BOTTOM] = transaction.originalMiddleBottom();
140 srcHandlePoints[HANDLE_MIDDLE_LEFT] = transaction.originalMiddleLeft();
141 srcHandlePoints[HANDLE_MIDDLE_RIGHT] = transaction.originalMiddleRight();
142
143 dstHandlePoints.clear();
144 Q_FOREACH (const QPointF &pt, srcHandlePoints) {
145 dstHandlePoints << transform.map(pt);
146 }
147
148 QMatrix4x4 realMatrix(transform);
149 QVector4D v;
150
151 v = QVector4D(1, 0, 0, 0);
152 v = realMatrix * v;
154 transformedHandles.xVanishing = v.toVector2DAffine().toPointF();
155
156 v = QVector4D(0, 1, 0, 0);
157 v = realMatrix * v;
159 transformedHandles.yVanishing = v.toVector2DAffine().toPointF();
160}
161
162void KisPerspectiveTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive, bool altModifierActive)
163{
164 Q_UNUSED(perspectiveModifierActive);
165 Q_UNUSED(shiftModifierActive);
166 Q_UNUSED(altModifierActive);
167
168 QPolygonF transformedPolygon = m_d->transform.map(QPolygonF(m_d->transaction.originalRect()));
169 StrokeFunction defaultFunction = transformedPolygon.containsPoint(mousePos, Qt::OddEvenFill) ? MOVE : NONE;
171 handleChooser(mousePos, defaultFunction);
172
173 qreal handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
174
175 if (!m_d->transformedHandles.xVanishing.isNull()) {
176 handleChooser.addFunction(m_d->transformedHandles.xVanishing,
177 handleRadius, DRAG_X_VANISHING_POINT);
178 }
179
180 if (!m_d->transformedHandles.yVanishing.isNull()) {
181 handleChooser.addFunction(m_d->transformedHandles.yVanishing,
182 handleRadius, DRAG_Y_VANISHING_POINT);
183 }
184
185 m_d->currentDraggingHandlePoint = -1;
186 for (int i = 0; i < m_d->dstHandlePoints.size(); i++) {
187 if (handleChooser.addFunction(m_d->dstHandlePoints[i],
188 handleRadius, DRAG_HANDLE)) {
189
190 m_d->currentDraggingHandlePoint = i;
191 }
192 }
193
194 m_d->function = handleChooser.function();
195}
196
198{
199 QCursor cursor;
200
201 switch (m_d->function) {
202 case NONE:
203 cursor = KisCursor::arrowCursor();
204 break;
205 case MOVE:
206 cursor = KisCursor::moveCursor();
207 break;
208 case DRAG_HANDLE:
209 case DRAG_X_VANISHING_POINT:
210 case DRAG_Y_VANISHING_POINT:
212 break;
213 }
214
215 return cursor;
216}
217
218void KisPerspectiveTransformStrategy::paint(QPainter &gc, const KoColorDisplayRendererInterface *displayRendererInterface)
219{
220 gc.save();
221
222 gc.setOpacity(m_d->transaction.basePreviewOpacity());
223 gc.setTransform(m_d->paintingTransform, true);
224 gc.drawImage(m_d->paintingOffset, originalImage());
225
226 gc.restore();
227
228 // Draw Handles
229 QPainterPath handles;
230
231 handles.moveTo(m_d->transaction.originalTopLeft());
232 handles.lineTo(m_d->transaction.originalTopRight());
233 handles.lineTo(m_d->transaction.originalBottomRight());
234 handles.lineTo(m_d->transaction.originalBottomLeft());
235 handles.lineTo(m_d->transaction.originalTopLeft());
236
237
238 auto addHandleRectFunc =
239 [&](const QPointF &pt) {
240 handles.addRect(
242 m_d->handlesTransform,
243 m_d->transaction.originalRect(), pt)
244 .translated(pt));
245 };
246
247 addHandleRectFunc(m_d->transaction.originalTopLeft());
248 addHandleRectFunc(m_d->transaction.originalTopRight());
249 addHandleRectFunc(m_d->transaction.originalBottomLeft());
250 addHandleRectFunc(m_d->transaction.originalBottomRight());
251 addHandleRectFunc(m_d->transaction.originalMiddleTop());
252 addHandleRectFunc(m_d->transaction.originalMiddleBottom());
253 addHandleRectFunc(m_d->transaction.originalMiddleLeft());
254 addHandleRectFunc(m_d->transaction.originalMiddleRight());
255
256 gc.save();
257
258 if (m_d->isTransforming) {
259 gc.setOpacity(0.1);
260 }
261
272 //gc.setTransform(m_d->handlesTransform, true); <-- don't do like this!
273
274 QPainterPath mappedHandles = m_d->handlesTransform.map(handles);
275
276 QPen pen[2];
277 pen[0].setWidth(decorationThickness());
278 pen[0].setCosmetic(true);
279 pen[1].setWidth(decorationThickness() * 2);
280 pen[1].setCosmetic(true);
281 pen[1].setColor(displayRendererInterface->convertColorToDisplayColorSpace(KoColor(Qt::lightGray, KoColorSpaceRegistry::instance()->rgb8())));
282
283 for (int i = 1; i >= 0; --i) {
284 gc.setPen(pen[i]);
285 gc.drawPath(mappedHandles);
286 }
287
288 gc.restore();
289
290 { // painting perspective handles
291 QPainterPath perspectiveHandles;
292
293 QRectF handleRect =
295 QTransform(),
296 m_d->transaction.originalRect(), 0, 0);
297
298 if (m_d->transformedHandles.xVanishingExists) {
299 QRectF rc = handleRect.translated(m_d->transformedHandles.xVanishing);
300 perspectiveHandles.addEllipse(rc);
301 }
302
303 if (m_d->transformedHandles.yVanishingExists) {
304 QRectF rc = handleRect.translated(m_d->transformedHandles.yVanishing);
305 perspectiveHandles.addEllipse(rc);
306 }
307
308 if (!perspectiveHandles.isEmpty()) {
309 gc.save();
310 gc.setTransform(m_d->converter->imageToWidgetTransform());
311
312 gc.setBrush(Qt::red);
313
314 for (int i = 1; i >= 0; --i) {
315 gc.setPen(pen[i]);
316 gc.drawPath(perspectiveHandles);
317 }
318
319 gc.restore();
320 }
321 }
322}
323
325{
326 m_d->recalculateTransformations();
327}
328
330{
331 if (m_d->function == NONE) return false;
332
333 m_d->clickPos = pt;
334 m_d->clickArgs = m_d->currentArgs;
335
336 return true;
337}
338
339Eigen::Matrix3f getTransitionMatrix(const QVector<QPointF> &sp)
340{
341 Eigen::Matrix3f A;
342 Eigen::Vector3f v3;
343
344 A << sp[HANDLE_TOP_LEFT].x() , sp[HANDLE_TOP_RIGHT].x() , sp[HANDLE_BOTTOM_LEFT].x()
345 ,sp[HANDLE_TOP_LEFT].y() , sp[HANDLE_TOP_RIGHT].y() , sp[HANDLE_BOTTOM_LEFT].y()
346 , 1 , 1 , 1;
347
348 v3 << sp[HANDLE_BOTTOM_RIGHT].x() , sp[HANDLE_BOTTOM_RIGHT].y() , 1;
349
350 Eigen::Vector3f coeffs = A.colPivHouseholderQr().solve(v3);
351
352 A.col(0) *= coeffs(0);
353 A.col(1) *= coeffs(1);
354 A.col(2) *= coeffs(2);
355
356 return A;
357}
358
359QTransform toQTransform(const Eigen::Matrix3f &m)
360{
361 return QTransform(m(0,0), m(1,0), m(2,0),
362 m(0,1), m(1,1), m(2,1),
363 m(0,2), m(1,2), m(2,2));
364}
365
366Eigen::Matrix3f fromQTransform(const QTransform &t)
367{
368 Eigen::Matrix3f m;
369
370 m << t.m11() , t.m21() , t.m31()
371 ,t.m12() , t.m22() , t.m32()
372 ,t.m13() , t.m23() , t.m33();
373
374 return m;
375}
376
377Eigen::Matrix3f fromTranslate(const QPointF &pt)
378{
379 Eigen::Matrix3f m;
380
381 m << 1 , 0 , pt.x()
382 ,0 , 1 , pt.y()
383 ,0 , 0 , 1;
384
385 return m;
386}
387
388Eigen::Matrix3f fromScale(qreal sx, qreal sy)
389{
390 Eigen::Matrix3f m;
391
392 m << sx , 0 , 0
393 ,0 , sy , 0
394 ,0 , 0 , 1;
395
396 return m;
397}
398
399Eigen::Matrix3f fromShear(qreal sx, qreal sy)
400{
401 Eigen::Matrix3f m;
402
403 m << 1 , sx , 0
404 ,sy , sx*sy + 1, 0
405 ,0 , 0 , 1;
406
407 return m;
408}
409
411{
412 Eigen::Matrix3f TS = fromTranslate(-currentArgs.originalCenter());
413
414 Eigen::Matrix3f m = t * TS.inverse();
415
416 qreal tX = m(0,2) / m(2,2);
417 qreal tY = m(1,2) / m(2,2);
418
419 Eigen::Matrix3f T = fromTranslate(QPointF(tX, tY));
420
421 m = T.inverse() * m;
422
436#if 0
437 // Decomposition according to:
438 // https://www.w3.org/TR/css-transforms-1/#decomposing-a-3d-matrix
440
441 currentArgs.setScaleX(dm.scaleX);
442 currentArgs.setScaleY(dm.scaleY);
443
444 currentArgs.setShearX(dm.shearXY);
445 currentArgs.setShearY(0.0);
446
447 currentArgs.setAZ(kisDegreesToRadians(dm.angle));
448
449 QTransform pre = dm.scaleTransform() * dm.shearTransform() * dm.rotateTransform();
450 m = m * fromQTransform(pre.inverted());
451#else
452 currentArgs.setScaleX(1.0);
453 currentArgs.setScaleY(1.0);
454 currentArgs.setShearX(0.0);
455 currentArgs.setShearY(0.0);
456 currentArgs.setAZ(0.0);
457#endif
458
459 currentArgs.setTransformedCenter(QPointF(tX, tY));
460 currentArgs.setFlattenedPerspectiveTransform(toQTransform(m));
461}
462
468
469QVector4D fromQPointF(const QPointF &pt) {
470 return QVector4D(pt.x(), pt.y(), 0, 1.0);
471}
472
473QPointF toQPointF(const QVector4D &v) {
474 return v.toVector2DAffine().toPointF();
475}
476
477void KisPerspectiveTransformStrategy::continuePrimaryAction(const QPointF &mousePos, bool shiftModifierActive, bool altModifierActive)
478{
479 Q_UNUSED(shiftModifierActive);
480 Q_UNUSED(altModifierActive);
481
482 m_d->isTransforming = true;
483
484 switch (m_d->function) {
485 case NONE:
486 break;
487 case MOVE: {
488 QPointF diff = mousePos - m_d->clickPos;
489 m_d->currentArgs.setTransformedCenter(
490 m_d->clickArgs.transformedCenter() + diff);
491 break;
492 }
493 case DRAG_HANDLE: {
494 KIS_ASSERT_RECOVER_RETURN(m_d->currentDraggingHandlePoint >= 0);
495 KIS_ASSERT_RECOVER_RETURN(m_d->currentDraggingHandlePoint < HANDLE_COUNT);
496 if (m_d->currentDraggingHandlePoint < HANDLE_MIDDLE_TOP) {
497 // Corner point, transform directly.
498 m_d->dstHandlePoints[m_d->currentDraggingHandlePoint] = mousePos;
499 } else {
500 // Middle point, move adjacent corners.
501 QPointF delta = mousePos - m_d->dstHandlePoints[m_d->currentDraggingHandlePoint];
502 switch(m_d->currentDraggingHandlePoint) {
503 case HANDLE_MIDDLE_TOP:
504 m_d->dstHandlePoints[HANDLE_TOP_LEFT] += delta;
505 m_d->dstHandlePoints[HANDLE_TOP_RIGHT] += delta;
506 break;
507 case HANDLE_MIDDLE_BOTTOM:
508 m_d->dstHandlePoints[HANDLE_BOTTOM_LEFT] += delta;
509 m_d->dstHandlePoints[HANDLE_BOTTOM_RIGHT] += delta;
510 break;
511 case HANDLE_MIDDLE_LEFT:
512 m_d->dstHandlePoints[HANDLE_TOP_LEFT] += delta;
513 m_d->dstHandlePoints[HANDLE_BOTTOM_LEFT] += delta;
514 break;
515 case HANDLE_MIDDLE_RIGHT:
516 m_d->dstHandlePoints[HANDLE_TOP_RIGHT] += delta;
517 m_d->dstHandlePoints[HANDLE_BOTTOM_RIGHT] += delta;
518 break;
519 }
520 }
521
522 Eigen::Matrix3f A = getTransitionMatrix(m_d->srcHandlePoints);
523 Eigen::Matrix3f B = getTransitionMatrix(m_d->dstHandlePoints);
524 Eigen::Matrix3f result = B * A.inverse();
525
526 // Points in dstHandlePoints are not arranged in an organized way, so if we used it directly as a polygon it
527 // will never be considered convex
528 QPolygonF poly;
529 poly << m_d->dstHandlePoints[HANDLE_TOP_LEFT] << m_d->dstHandlePoints[HANDLE_TOP_RIGHT]
530 << m_d->dstHandlePoints[HANDLE_BOTTOM_RIGHT] << m_d->dstHandlePoints[HANDLE_BOTTOM_LEFT];
531
532 //Don't apply the handle movement if it makes the transform area not convex
534 break;
535 }
536
537 m_d->transformIntoArgs(result);
538
539 break;
540 }
541 case DRAG_X_VANISHING_POINT:
542 case DRAG_Y_VANISHING_POINT: {
543
544 QMatrix4x4 m(m_d->transform);
545
546 QPointF tl = m_d->transaction.originalTopLeft();
547 QPointF tr = m_d->transaction.originalTopRight();
548 QPointF bl = m_d->transaction.originalBottomLeft();
549 QPointF br = m_d->transaction.originalBottomRight();
550
551 QVector4D v(1,0,0,0);
552 QVector4D otherV(0,1,0,0);
553
554 if (m_d->function == DRAG_X_VANISHING_POINT) {
555 v = QVector4D(1,0,0,0);
556 otherV = QVector4D(0,1,0,0);
557 } else {
558 v = QVector4D(0,1,0,0);
559 otherV = QVector4D(1,0,0,0);
560 }
561
562 QPointF tl_dst = toQPointF(m * fromQPointF(tl));
563 QPointF tr_dst = toQPointF(m * fromQPointF(tr));
564 QPointF bl_dst = toQPointF(m * fromQPointF(bl));
565 QPointF br_dst = toQPointF(m * fromQPointF(br));
566 QPointF v_dst = toQPointF(m * v);
567 QPointF otherV_dst = toQPointF(m * otherV);
568
569 QVector<QPointF> srcPoints;
570 QVector<QPointF> dstPoints;
571
572 QPointF far1_src;
573 QPointF far2_src;
574 QPointF near1_src;
575 QPointF near2_src;
576
577 QPointF far1_dst;
578 QPointF far2_dst;
579 QPointF near1_dst;
580 QPointF near2_dst;
581
582 if (m_d->function == DRAG_X_VANISHING_POINT) {
583
584 // topLeft (far) --- topRight (near) --- vanishing
585 if (kisSquareDistance(v_dst, tl_dst) > kisSquareDistance(v_dst, tr_dst)) {
586 far1_src = tl;
587 far2_src = bl;
588 near1_src = tr;
589 near2_src = br;
590
591 far1_dst = tl_dst;
592 far2_dst = bl_dst;
593 near1_dst = tr_dst;
594 near2_dst = br_dst;
595
596 // topRight (far) --- topLeft (near) --- vanishing
597 } else {
598 far1_src = tr;
599 far2_src = br;
600 near1_src = tl;
601 near2_src = bl;
602
603 far1_dst = tr_dst;
604 far2_dst = br_dst;
605 near1_dst = tl_dst;
606 near2_dst = bl_dst;
607 }
608
609 } else /* if (m_d->function == DRAG_Y_VANISHING_POINT) */{
610 // topLeft (far) --- bottomLeft (near) --- vanishing
611 if (kisSquareDistance(v_dst, tl_dst) > kisSquareDistance(v_dst, bl_dst)) {
612 far1_src = tl;
613 far2_src = tr;
614 near1_src = bl;
615 near2_src = br;
616
617 far1_dst = tl_dst;
618 far2_dst = tr_dst;
619 near1_dst = bl_dst;
620 near2_dst = br_dst;
621
622 // bottomLeft (far) --- topLeft (near) --- vanishing
623 } else {
624 far1_src = bl;
625 far2_src = br;
626 near1_src = tl;
627 near2_src = tr;
628
629 far1_dst = bl_dst;
630 far2_dst = br_dst;
631 near1_dst = tl_dst;
632 near2_dst = tr_dst;
633 }
634 }
635
636 QLineF l0(far1_dst, mousePos);
637 QLineF l1(far2_dst, mousePos);
638 QLineF l2(otherV_dst, near1_dst);
639 l0.intersects(l2, &near1_dst);
640 l1.intersects(l2, &near2_dst);
641
642 srcPoints << far1_src;
643 srcPoints << far2_src;
644 srcPoints << near1_src;
645 srcPoints << near2_src;
646
647 dstPoints << far1_dst;
648 dstPoints << far2_dst;
649 dstPoints << near1_dst;
650 dstPoints << near2_dst;
651
652 Eigen::Matrix3f A = getTransitionMatrix(srcPoints);
653 Eigen::Matrix3f B = getTransitionMatrix(dstPoints);
654 Eigen::Matrix3f result = B * A.inverse();
655
656 m_d->transformIntoArgs(result);
657 break;
658 }
659 }
660
661 m_d->recalculateTransformations();
662}
663
665{
666 bool shouldSave = !m_d->imageTooBig;
667 m_d->isTransforming = false;
668
669 if (m_d->imageTooBig) {
670 m_d->currentArgs = m_d->clickArgs;
671 m_d->recalculateTransformations();
672 }
673
674 return shouldSave;
675}
676
678{
679 transform = transformFromArgs();
680
681 QTransform viewScaleTransform = converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
682 handlesTransform = transform * viewScaleTransform;
683
684 QTransform tl = QTransform::fromTranslate(transaction.originalTopLeft().x(), transaction.originalTopLeft().y());
685 paintingTransform = tl.inverted() * q->thumbToImageTransform() * tl * transform * viewScaleTransform;
686 paintingOffset = transaction.originalTopLeft();
687
688 // check whether image is too big to be displayed or not
689 const qreal maxScale = 20.0;
690
691 imageTooBig = false;
692
693 if (qAbs(currentArgs.scaleX()) > maxScale ||
694 qAbs(currentArgs.scaleY()) > maxScale) {
695
696 imageTooBig = true;
697
698 } else {
699 QVector<QPointF> points;
700 points << transaction.originalRect().topLeft();
701 points << transaction.originalRect().topRight();
702 points << transaction.originalRect().bottomRight();
703 points << transaction.originalRect().bottomLeft();
704
705 for (int i = 0; i < points.size(); i++) {
706 points[i] = transform.map(points[i]);
707 }
708
709 for (int i = 0; i < points.size(); i++) {
710 const QPointF &pt = points[i];
711 const QPointF &prev = points[(i - 1 + 4) % 4];
712 const QPointF &next = points[(i + 1) % 4];
713 const QPointF &other = points[(i + 2) % 4];
714
715 QLineF l1(pt, other);
716 QLineF l2(prev, next);
717
718 QPointF intersection;
719 l1.intersects(l2, &intersection);
720
721 qreal maxDistance = kisSquareDistance(pt, other);
722
723 if (kisSquareDistance(pt, intersection) > maxDistance ||
724 kisSquareDistance(other, intersection) > maxDistance) {
725
726 imageTooBig = true;
727 break;
728 }
729
730 const qreal thresholdDistance = 0.02 * l2.length();
731
732 if (kisDistanceToLine(pt, l2) < thresholdDistance) {
733 imageTooBig = true;
734 break;
735 }
736 }
737 }
738
739 // recalculate cached handles position
740 recalculateTransformedHandles();
741
742 Q_EMIT q->requestShowImageTooBig(imageTooBig);
743 Q_EMIT q->requestImageRecalculation();
744}
qreal v
@ NONE
static QCursor moveCursor()
static QCursor arrowCursor()
Definition kis_cursor.cc:24
static QCursor pointingHandCursor()
KisPerspectiveTransformStrategy(const KisCoordinatesConverter *converter, KoSnapGuide *snapGuide, ToolTransformArgs &currentArgs, TransformTransactionProperties &transaction)
void paint(QPainter &gc, const KoColorDisplayRendererInterface *displayRendererInterface) override
void setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive, bool altModifierActive) override
void continuePrimaryAction(const QPointF &pt, bool shiftModifierActive, bool altModifierActive) override
bool beginPrimaryAction(const QPointF &pt) override
bool addFunction(const QPointF &pt, qreal radius, Function function)
static qreal effectiveHandleGrabRadius(const KisCoordinatesConverter *converter)
static const int handleVisualRadius
static QRectF handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY)
virtual QColor convertColorToDisplayColorSpace(const KoColor color) const =0
convertColorToDisplayColorSpace
static bool qFuzzyCompare(half p1, half p2)
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
T kisDegreesToRadians(T degrees)
Definition kis_global.h:176
qreal kisDistanceToLine(const QPointF &m, const QLineF &line)
Definition kis_global.h:234
qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:194
Eigen::Matrix3f fromShear(qreal sx, qreal sy)
Eigen::Matrix3f fromScale(qreal sx, qreal sy)
Eigen::Matrix3f fromTranslate(const QPointF &pt)
QVector4D fromQPointF(const QPointF &pt)
Eigen::Matrix3f fromQTransform(const QTransform &t)
QTransform toQTransform(const Eigen::Matrix3f &m)
Eigen::Matrix3f getTransitionMatrix(const QVector< QPointF > &sp)
QPointF toQPointF(const ExpressionType &expr)
Definition kis_vec.h:29
bool isPolygonTrulyConvex(const QVector< T > &polygon, bool ensureNoLoops=true)
QTransform shearTransform() const
QTransform rotateTransform() const
QTransform scaleTransform() const
QCursor getScaleCursor(const QPointF &handlePt)
Private(KisPerspectiveTransformStrategy *_q, const KisCoordinatesConverter *_converter, ToolTransformArgs &_currentArgs, TransformTransactionProperties &_transaction)
QCursor getShearCursor(const QPointF &start, const QPointF &end)
const KisCoordinatesConverter * converter
standard members ///
static KoColorSpaceRegistry * instance()