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 static constexpr qreal MaxPolygonVertexAngle {177.0};
118};
119
121 KoSnapGuide *snapGuide,
122 ToolTransformArgs &currentArgs,
124 : KisSimplifiedActionPolicyStrategy(converter, snapGuide),
125 m_d(new Private(this, converter, currentArgs, transaction))
126{
127}
128
132
134{
135 srcHandlePoints.resize(HANDLE_COUNT);
136 srcHandlePoints[HANDLE_TOP_LEFT] = transaction.originalTopLeft();
137 srcHandlePoints[HANDLE_TOP_RIGHT] = transaction.originalTopRight();
138 srcHandlePoints[HANDLE_BOTTOM_LEFT] = transaction.originalBottomLeft();
139 srcHandlePoints[HANDLE_BOTTOM_RIGHT] = transaction.originalBottomRight();
140 srcHandlePoints[HANDLE_MIDDLE_TOP] = transaction.originalMiddleTop();
141 srcHandlePoints[HANDLE_MIDDLE_BOTTOM] = transaction.originalMiddleBottom();
142 srcHandlePoints[HANDLE_MIDDLE_LEFT] = transaction.originalMiddleLeft();
143 srcHandlePoints[HANDLE_MIDDLE_RIGHT] = transaction.originalMiddleRight();
144
145 dstHandlePoints.clear();
146 Q_FOREACH (const QPointF &pt, srcHandlePoints) {
147 dstHandlePoints << transform.map(pt);
148 }
149
150 QMatrix4x4 realMatrix(transform);
151 QVector4D v;
152
153 v = QVector4D(1, 0, 0, 0);
154 v = realMatrix * v;
156 transformedHandles.xVanishing = v.toVector2DAffine().toPointF();
157
158 v = QVector4D(0, 1, 0, 0);
159 v = realMatrix * v;
161 transformedHandles.yVanishing = v.toVector2DAffine().toPointF();
162}
163
164void KisPerspectiveTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive, bool altModifierActive)
165{
166 Q_UNUSED(perspectiveModifierActive);
167 Q_UNUSED(shiftModifierActive);
168 Q_UNUSED(altModifierActive);
169
170 QPolygonF transformedPolygon = m_d->transform.map(QPolygonF(m_d->transaction.originalRect()));
171 StrokeFunction defaultFunction = transformedPolygon.containsPoint(mousePos, Qt::OddEvenFill) ? MOVE : NONE;
173 handleChooser(mousePos, defaultFunction);
174
175 qreal handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
176
177 if (!m_d->transformedHandles.xVanishing.isNull()) {
178 handleChooser.addFunction(m_d->transformedHandles.xVanishing,
179 handleRadius, DRAG_X_VANISHING_POINT);
180 }
181
182 if (!m_d->transformedHandles.yVanishing.isNull()) {
183 handleChooser.addFunction(m_d->transformedHandles.yVanishing,
184 handleRadius, DRAG_Y_VANISHING_POINT);
185 }
186
187 m_d->currentDraggingHandlePoint = -1;
188 for (int i = 0; i < m_d->dstHandlePoints.size(); i++) {
189 if (handleChooser.addFunction(m_d->dstHandlePoints[i],
190 handleRadius, DRAG_HANDLE)) {
191
192 m_d->currentDraggingHandlePoint = i;
193 }
194 }
195
196 m_d->function = handleChooser.function();
197}
198
200{
201 QCursor cursor;
202
203 switch (m_d->function) {
204 case NONE:
205 cursor = KisCursor::arrowCursor();
206 break;
207 case MOVE:
208 cursor = KisCursor::moveCursor();
209 break;
210 case DRAG_HANDLE:
211 case DRAG_X_VANISHING_POINT:
212 case DRAG_Y_VANISHING_POINT:
214 break;
215 }
216
217 return cursor;
218}
219
220void KisPerspectiveTransformStrategy::paint(QPainter &gc, const KoColorDisplayRendererInterface *displayRendererInterface)
221{
222 gc.save();
223
224 gc.setOpacity(m_d->transaction.basePreviewOpacity());
225 gc.setTransform(m_d->paintingTransform, true);
226 gc.drawImage(m_d->paintingOffset, originalImage());
227
228 gc.restore();
229
230 // Draw Handles
231 QPainterPath handles;
232
233 handles.moveTo(m_d->transaction.originalTopLeft());
234 handles.lineTo(m_d->transaction.originalTopRight());
235 handles.lineTo(m_d->transaction.originalBottomRight());
236 handles.lineTo(m_d->transaction.originalBottomLeft());
237 handles.lineTo(m_d->transaction.originalTopLeft());
238
239
240 auto addHandleRectFunc =
241 [&](const QPointF &pt) {
242 handles.addRect(
244 m_d->handlesTransform,
245 m_d->transaction.originalRect(), pt)
246 .translated(pt));
247 };
248
249 addHandleRectFunc(m_d->transaction.originalTopLeft());
250 addHandleRectFunc(m_d->transaction.originalTopRight());
251 addHandleRectFunc(m_d->transaction.originalBottomLeft());
252 addHandleRectFunc(m_d->transaction.originalBottomRight());
253 addHandleRectFunc(m_d->transaction.originalMiddleTop());
254 addHandleRectFunc(m_d->transaction.originalMiddleBottom());
255 addHandleRectFunc(m_d->transaction.originalMiddleLeft());
256 addHandleRectFunc(m_d->transaction.originalMiddleRight());
257
258 gc.save();
259
260 if (m_d->isTransforming) {
261 gc.setOpacity(0.1);
262 }
263
274 //gc.setTransform(m_d->handlesTransform, true); <-- don't do like this!
275
276 QPainterPath mappedHandles = m_d->handlesTransform.map(handles);
277
278 QPen pen[2];
279 pen[0].setWidth(decorationThickness());
280 pen[0].setCosmetic(true);
281 pen[1].setWidth(decorationThickness() * 2);
282 pen[1].setCosmetic(true);
283 pen[1].setColor(displayRendererInterface->convertColorToDisplayColorSpace(KoColor(Qt::lightGray, KoColorSpaceRegistry::instance()->rgb8())));
284
285 for (int i = 1; i >= 0; --i) {
286 gc.setPen(pen[i]);
287 gc.drawPath(mappedHandles);
288 }
289
290 gc.restore();
291
292 { // painting perspective handles
293 QPainterPath perspectiveHandles;
294
295 QRectF handleRect =
297 QTransform(),
298 m_d->transaction.originalRect(), 0, 0);
299
300 if (m_d->transformedHandles.xVanishingExists) {
301 QRectF rc = handleRect.translated(m_d->transformedHandles.xVanishing);
302 perspectiveHandles.addEllipse(rc);
303 }
304
305 if (m_d->transformedHandles.yVanishingExists) {
306 QRectF rc = handleRect.translated(m_d->transformedHandles.yVanishing);
307 perspectiveHandles.addEllipse(rc);
308 }
309
310 if (!perspectiveHandles.isEmpty()) {
311 gc.save();
312 gc.setTransform(m_d->converter->imageToWidgetTransform());
313
314 gc.setBrush(Qt::red);
315
316 for (int i = 1; i >= 0; --i) {
317 gc.setPen(pen[i]);
318 gc.drawPath(perspectiveHandles);
319 }
320
321 gc.restore();
322 }
323 }
324}
325
327{
328 m_d->recalculateTransformations();
329}
330
332{
333 if (m_d->function == NONE) return false;
334
335 m_d->clickPos = pt;
336 m_d->clickArgs = m_d->currentArgs;
337
338 return true;
339}
340
341Eigen::Matrix3f getTransitionMatrix(const QVector<QPointF> &sp)
342{
343 Eigen::Matrix3f A;
344 Eigen::Vector3f v3;
345
346 A << sp[HANDLE_TOP_LEFT].x() , sp[HANDLE_TOP_RIGHT].x() , sp[HANDLE_BOTTOM_LEFT].x()
347 ,sp[HANDLE_TOP_LEFT].y() , sp[HANDLE_TOP_RIGHT].y() , sp[HANDLE_BOTTOM_LEFT].y()
348 , 1 , 1 , 1;
349
350 v3 << sp[HANDLE_BOTTOM_RIGHT].x() , sp[HANDLE_BOTTOM_RIGHT].y() , 1;
351
352 Eigen::Vector3f coeffs = A.colPivHouseholderQr().solve(v3);
353
354 A.col(0) *= coeffs(0);
355 A.col(1) *= coeffs(1);
356 A.col(2) *= coeffs(2);
357
358 return A;
359}
360
361QTransform toQTransform(const Eigen::Matrix3f &m)
362{
363 return QTransform(m(0,0), m(1,0), m(2,0),
364 m(0,1), m(1,1), m(2,1),
365 m(0,2), m(1,2), m(2,2));
366}
367
368Eigen::Matrix3f fromQTransform(const QTransform &t)
369{
370 Eigen::Matrix3f m;
371
372 m << t.m11() , t.m21() , t.m31()
373 ,t.m12() , t.m22() , t.m32()
374 ,t.m13() , t.m23() , t.m33();
375
376 return m;
377}
378
379Eigen::Matrix3f fromTranslate(const QPointF &pt)
380{
381 Eigen::Matrix3f m;
382
383 m << 1 , 0 , pt.x()
384 ,0 , 1 , pt.y()
385 ,0 , 0 , 1;
386
387 return m;
388}
389
390Eigen::Matrix3f fromScale(qreal sx, qreal sy)
391{
392 Eigen::Matrix3f m;
393
394 m << sx , 0 , 0
395 ,0 , sy , 0
396 ,0 , 0 , 1;
397
398 return m;
399}
400
401Eigen::Matrix3f fromShear(qreal sx, qreal sy)
402{
403 Eigen::Matrix3f m;
404
405 m << 1 , sx , 0
406 ,sy , sx*sy + 1, 0
407 ,0 , 0 , 1;
408
409 return m;
410}
411
413{
414 Eigen::Matrix3f TS = fromTranslate(-currentArgs.originalCenter());
415
416 Eigen::Matrix3f m = t * TS.inverse();
417
418 qreal tX = m(0,2) / m(2,2);
419 qreal tY = m(1,2) / m(2,2);
420
421 Eigen::Matrix3f T = fromTranslate(QPointF(tX, tY));
422
423 m = T.inverse() * m;
424
438#if 0
439 // Decomposition according to:
440 // https://www.w3.org/TR/css-transforms-1/#decomposing-a-3d-matrix
442
443 currentArgs.setScaleX(dm.scaleX);
444 currentArgs.setScaleY(dm.scaleY);
445
446 currentArgs.setShearX(dm.shearXY);
447 currentArgs.setShearY(0.0);
448
449 currentArgs.setAZ(kisDegreesToRadians(dm.angle));
450
451 QTransform pre = dm.scaleTransform() * dm.shearTransform() * dm.rotateTransform();
452 m = m * fromQTransform(pre.inverted());
453#else
454 currentArgs.setScaleX(1.0);
455 currentArgs.setScaleY(1.0);
456 currentArgs.setShearX(0.0);
457 currentArgs.setShearY(0.0);
458 currentArgs.setAZ(0.0);
459#endif
460
461 currentArgs.setTransformedCenter(QPointF(tX, tY));
462 currentArgs.setFlattenedPerspectiveTransform(toQTransform(m));
463}
464
470
471QVector4D fromQPointF(const QPointF &pt) {
472 return QVector4D(pt.x(), pt.y(), 0, 1.0);
473}
474
475QPointF toQPointF(const QVector4D &v) {
476 return v.toVector2DAffine().toPointF();
477}
478
479void KisPerspectiveTransformStrategy::continuePrimaryAction(const QPointF &mousePos, bool shiftModifierActive, bool altModifierActive)
480{
481 Q_UNUSED(shiftModifierActive);
482 Q_UNUSED(altModifierActive);
483
484 m_d->isTransforming = true;
485
486 switch (m_d->function) {
487 case NONE:
488 break;
489 case MOVE: {
490 QPointF diff = mousePos - m_d->clickPos;
491 m_d->currentArgs.setTransformedCenter(
492 m_d->clickArgs.transformedCenter() + diff);
493 break;
494 }
495 case DRAG_HANDLE: {
496 KIS_ASSERT_RECOVER_RETURN(m_d->currentDraggingHandlePoint >= 0);
497 KIS_ASSERT_RECOVER_RETURN(m_d->currentDraggingHandlePoint < HANDLE_COUNT);
498 if (m_d->currentDraggingHandlePoint < HANDLE_MIDDLE_TOP) {
499 // Corner point, transform directly.
500 m_d->dstHandlePoints[m_d->currentDraggingHandlePoint] = mousePos;
501 } else {
502 // Middle point, move adjacent corners.
503 QPointF delta = mousePos - m_d->dstHandlePoints[m_d->currentDraggingHandlePoint];
504 switch(m_d->currentDraggingHandlePoint) {
505 case HANDLE_MIDDLE_TOP:
506 m_d->dstHandlePoints[HANDLE_TOP_LEFT] += delta;
507 m_d->dstHandlePoints[HANDLE_TOP_RIGHT] += delta;
508 break;
509 case HANDLE_MIDDLE_BOTTOM:
510 m_d->dstHandlePoints[HANDLE_BOTTOM_LEFT] += delta;
511 m_d->dstHandlePoints[HANDLE_BOTTOM_RIGHT] += delta;
512 break;
513 case HANDLE_MIDDLE_LEFT:
514 m_d->dstHandlePoints[HANDLE_TOP_LEFT] += delta;
515 m_d->dstHandlePoints[HANDLE_BOTTOM_LEFT] += delta;
516 break;
517 case HANDLE_MIDDLE_RIGHT:
518 m_d->dstHandlePoints[HANDLE_TOP_RIGHT] += delta;
519 m_d->dstHandlePoints[HANDLE_BOTTOM_RIGHT] += delta;
520 break;
521 }
522 }
523
524 Eigen::Matrix3f A = getTransitionMatrix(m_d->srcHandlePoints);
525 Eigen::Matrix3f B = getTransitionMatrix(m_d->dstHandlePoints);
526 Eigen::Matrix3f result = B * A.inverse();
527
528 // Points in dstHandlePoints are not arranged in an organized way, so if we used it directly as a polygon it
529 // will never be considered convex
530 QPolygonF poly;
531 poly << m_d->dstHandlePoints[HANDLE_TOP_LEFT] << m_d->dstHandlePoints[HANDLE_TOP_RIGHT]
532 << m_d->dstHandlePoints[HANDLE_BOTTOM_RIGHT] << m_d->dstHandlePoints[HANDLE_BOTTOM_LEFT];
533
534 //Don't apply the handle movement if it makes the transform area not convex
536 break;
537 }
538
539 m_d->transformIntoArgs(result);
540
541 break;
542 }
543 case DRAG_X_VANISHING_POINT:
544 case DRAG_Y_VANISHING_POINT: {
545
546 QMatrix4x4 m(m_d->transform);
547
548 QPointF tl = m_d->transaction.originalTopLeft();
549 QPointF tr = m_d->transaction.originalTopRight();
550 QPointF bl = m_d->transaction.originalBottomLeft();
551 QPointF br = m_d->transaction.originalBottomRight();
552
553 QVector4D v(1,0,0,0);
554 QVector4D otherV(0,1,0,0);
555
556 if (m_d->function == DRAG_X_VANISHING_POINT) {
557 v = QVector4D(1,0,0,0);
558 otherV = QVector4D(0,1,0,0);
559 } else {
560 v = QVector4D(0,1,0,0);
561 otherV = QVector4D(1,0,0,0);
562 }
563
564 QPointF tl_dst = toQPointF(m * fromQPointF(tl));
565 QPointF tr_dst = toQPointF(m * fromQPointF(tr));
566 QPointF bl_dst = toQPointF(m * fromQPointF(bl));
567 QPointF br_dst = toQPointF(m * fromQPointF(br));
568 QPointF v_dst = toQPointF(m * v);
569 QPointF otherV_dst = toQPointF(m * otherV);
570
571 QVector<QPointF> srcPoints;
572 QVector<QPointF> dstPoints;
573
574 QPointF far1_src;
575 QPointF far2_src;
576 QPointF near1_src;
577 QPointF near2_src;
578
579 QPointF far1_dst;
580 QPointF far2_dst;
581 QPointF near1_dst;
582 QPointF near2_dst;
583
584 if (m_d->function == DRAG_X_VANISHING_POINT) {
585
586 // topLeft (far) --- topRight (near) --- vanishing
587 if (kisSquareDistance(v_dst, tl_dst) > kisSquareDistance(v_dst, tr_dst)) {
588 far1_src = tl;
589 far2_src = bl;
590 near1_src = tr;
591 near2_src = br;
592
593 far1_dst = tl_dst;
594 far2_dst = bl_dst;
595 near1_dst = tr_dst;
596 near2_dst = br_dst;
597
598 // topRight (far) --- topLeft (near) --- vanishing
599 } else {
600 far1_src = tr;
601 far2_src = br;
602 near1_src = tl;
603 near2_src = bl;
604
605 far1_dst = tr_dst;
606 far2_dst = br_dst;
607 near1_dst = tl_dst;
608 near2_dst = bl_dst;
609 }
610
611 } else /* if (m_d->function == DRAG_Y_VANISHING_POINT) */{
612 // topLeft (far) --- bottomLeft (near) --- vanishing
613 if (kisSquareDistance(v_dst, tl_dst) > kisSquareDistance(v_dst, bl_dst)) {
614 far1_src = tl;
615 far2_src = tr;
616 near1_src = bl;
617 near2_src = br;
618
619 far1_dst = tl_dst;
620 far2_dst = tr_dst;
621 near1_dst = bl_dst;
622 near2_dst = br_dst;
623
624 // bottomLeft (far) --- topLeft (near) --- vanishing
625 } else {
626 far1_src = bl;
627 far2_src = br;
628 near1_src = tl;
629 near2_src = tr;
630
631 far1_dst = bl_dst;
632 far2_dst = br_dst;
633 near1_dst = tl_dst;
634 near2_dst = tr_dst;
635 }
636 }
637
638 QLineF l0(far1_dst, mousePos);
639 QLineF l1(far2_dst, mousePos);
640 QLineF l2(otherV_dst, near1_dst);
641 l0.intersects(l2, &near1_dst);
642 l1.intersects(l2, &near2_dst);
643
644 srcPoints << far1_src;
645 srcPoints << far2_src;
646 srcPoints << near1_src;
647 srcPoints << near2_src;
648
649 dstPoints << far1_dst;
650 dstPoints << far2_dst;
651 dstPoints << near1_dst;
652 dstPoints << near2_dst;
653
654 Eigen::Matrix3f A = getTransitionMatrix(srcPoints);
655 Eigen::Matrix3f B = getTransitionMatrix(dstPoints);
656 Eigen::Matrix3f result = B * A.inverse();
657
658 m_d->transformIntoArgs(result);
659 break;
660 }
661 }
662
663 m_d->recalculateTransformations();
664}
665
667{
668 bool shouldSave = !m_d->imageTooBig;
669 m_d->isTransforming = false;
670
671 if (m_d->imageTooBig) {
672 m_d->currentArgs = m_d->clickArgs;
673 m_d->recalculateTransformations();
674 }
675
676 return shouldSave;
677}
678
680{
681 transform = transformFromArgs();
682
683 QTransform viewScaleTransform = converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
684 handlesTransform = transform * viewScaleTransform;
685
686 QTransform tl = QTransform::fromTranslate(transaction.originalTopLeft().x(), transaction.originalTopLeft().y());
687 paintingTransform = tl.inverted() * q->thumbToImageTransform() * tl * transform * viewScaleTransform;
688 paintingOffset = transaction.originalTopLeft();
689
690 imageTooBig = false;
691
692 {
693 QVector<QPointF> points;
694 points << transaction.originalRect().topLeft();
695 points << transaction.originalRect().topRight();
696 points << transaction.originalRect().bottomRight();
697 points << transaction.originalRect().bottomLeft();
698
699 for (int i = 0; i < points.size(); i++) {
700 points[i] = transform.map(points[i]);
701 }
702
703 QPolygonF poly (points);
705 imageTooBig = true;
706 }
707
708 const QRectF transformedRect = poly.boundingRect();
709 const QRectF limitRect = KisAlgebra2D::blowRect(converter->imageRectInImagePixels(), 2.0);
710 if (!limitRect.contains(transformedRect)) {
711 imageTooBig = true;
712 }
713 }
714
715 // recalculate cached handles position
716 recalculateTransformedHandles();
717
718 Q_EMIT q->requestShowImageTooBig(imageTooBig);
719 Q_EMIT q->requestImageRecalculation();
720}
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 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
Rect blowRect(const Rect &rect, qreal coeff)
bool isPolygonTrulyConvex(const QVector< T > &polygon, bool ensureNoLoops=true, qreal maxAngle=180.0)
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()