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