File indexing completed on 2024-05-26 04:34:45

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *  SPDX-FileCopyrightText: 2022 Carsten Hartenfels <carsten.hartenfels@pm.me>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_perspective_transform_strategy.h"
0009 
0010 #include <QPointF>
0011 #include <QPainter>
0012 #include <QPainterPath>
0013 #include <QMatrix4x4>
0014 #include <QVector2D>
0015 
0016 #include <Eigen/Dense>
0017 
0018 #include "kis_coordinates_converter.h"
0019 #include "tool_transform_args.h"
0020 #include "transform_transaction_properties.h"
0021 #include "krita_utils.h"
0022 #include "kis_cursor.h"
0023 #include "kis_transform_utils.h"
0024 #include "kis_free_transform_strategy_gsl_helpers.h"
0025 
0026 namespace {
0027 enum StrokeFunction {
0028     DRAG_HANDLE = 0,
0029     DRAG_X_VANISHING_POINT,
0030     DRAG_Y_VANISHING_POINT,
0031     MOVE,
0032     NONE
0033 };
0034 
0035 enum HandleIndexes {
0036     HANDLE_TOP_LEFT = 0,
0037     HANDLE_TOP_RIGHT,
0038     HANDLE_BOTTOM_LEFT,
0039     HANDLE_BOTTOM_RIGHT,
0040     HANDLE_MIDDLE_TOP,
0041     HANDLE_MIDDLE_BOTTOM,
0042     HANDLE_MIDDLE_LEFT,
0043     HANDLE_MIDDLE_RIGHT,
0044     HANDLE_COUNT,
0045 };
0046 }
0047 
0048 struct KisPerspectiveTransformStrategy::Private
0049 {
0050     Private(KisPerspectiveTransformStrategy *_q,
0051             const KisCoordinatesConverter *_converter,
0052             ToolTransformArgs &_currentArgs,
0053             TransformTransactionProperties &_transaction)
0054         : q(_q),
0055           converter(_converter),
0056           currentArgs(_currentArgs),
0057           transaction(_transaction),
0058           imageTooBig(false),
0059           isTransforming(false)
0060     {
0061     }
0062 
0063     KisPerspectiveTransformStrategy *q;
0064 
0065     /// standard members ///
0066 
0067     const KisCoordinatesConverter *converter;
0068 
0069     //////
0070     ToolTransformArgs &currentArgs;
0071     //////
0072     TransformTransactionProperties &transaction;
0073 
0074 
0075     QTransform thumbToImageTransform;
0076     QImage originalImage;
0077 
0078     QTransform paintingTransform;
0079     QPointF paintingOffset;
0080 
0081     QTransform handlesTransform;
0082 
0083     /// custom members ///
0084 
0085     StrokeFunction function {NONE};
0086 
0087     struct HandlePoints {
0088         bool xVanishingExists {false};
0089         bool yVanishingExists {false};
0090 
0091         QPointF xVanishing;
0092         QPointF yVanishing;
0093     };
0094     HandlePoints transformedHandles;
0095 
0096     QTransform transform;
0097 
0098     QVector<QPointF> srcHandlePoints;
0099     QVector<QPointF> dstHandlePoints;
0100     int currentDraggingHandlePoint {0};
0101 
0102     bool imageTooBig {false};
0103 
0104     QPointF clickPos;
0105     ToolTransformArgs clickArgs;
0106     bool isTransforming {false};
0107 
0108     QCursor getScaleCursor(const QPointF &handlePt);
0109     QCursor getShearCursor(const QPointF &start, const QPointF &end);
0110     void recalculateTransformations();
0111     void recalculateTransformedHandles();
0112 
0113     void transformIntoArgs(const Eigen::Matrix3f &t);
0114     QTransform transformFromArgs();
0115 };
0116 
0117 KisPerspectiveTransformStrategy::KisPerspectiveTransformStrategy(const KisCoordinatesConverter *converter,
0118                                                                  KoSnapGuide *snapGuide,
0119                                                    ToolTransformArgs &currentArgs,
0120                                                    TransformTransactionProperties &transaction)
0121     : KisSimplifiedActionPolicyStrategy(converter, snapGuide),
0122       m_d(new Private(this, converter, currentArgs, transaction))
0123 {
0124 }
0125 
0126 KisPerspectiveTransformStrategy::~KisPerspectiveTransformStrategy()
0127 {
0128 }
0129 
0130 void KisPerspectiveTransformStrategy::Private::recalculateTransformedHandles()
0131 {
0132     srcHandlePoints.resize(HANDLE_COUNT);
0133     srcHandlePoints[HANDLE_TOP_LEFT] = transaction.originalTopLeft();
0134     srcHandlePoints[HANDLE_TOP_RIGHT] = transaction.originalTopRight();
0135     srcHandlePoints[HANDLE_BOTTOM_LEFT] = transaction.originalBottomLeft();
0136     srcHandlePoints[HANDLE_BOTTOM_RIGHT] = transaction.originalBottomRight();
0137     srcHandlePoints[HANDLE_MIDDLE_TOP] = transaction.originalMiddleTop();
0138     srcHandlePoints[HANDLE_MIDDLE_BOTTOM] = transaction.originalMiddleBottom();
0139     srcHandlePoints[HANDLE_MIDDLE_LEFT] = transaction.originalMiddleLeft();
0140     srcHandlePoints[HANDLE_MIDDLE_RIGHT] = transaction.originalMiddleRight();
0141 
0142     dstHandlePoints.clear();
0143     Q_FOREACH (const QPointF &pt, srcHandlePoints) {
0144         dstHandlePoints << transform.map(pt);
0145     }
0146 
0147     QMatrix4x4 realMatrix(transform);
0148     QVector4D v;
0149 
0150     v = QVector4D(1, 0, 0, 0);
0151     v = realMatrix * v;
0152     transformedHandles.xVanishingExists = !qFuzzyCompare(v.w(), 0);
0153     transformedHandles.xVanishing = v.toVector2DAffine().toPointF();
0154 
0155     v = QVector4D(0, 1, 0, 0);
0156     v = realMatrix * v;
0157     transformedHandles.yVanishingExists = !qFuzzyCompare(v.w(), 0);
0158     transformedHandles.yVanishing = v.toVector2DAffine().toPointF();
0159 }
0160 
0161 void KisPerspectiveTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive, bool altModifierActive)
0162 {
0163     Q_UNUSED(perspectiveModifierActive);
0164     Q_UNUSED(shiftModifierActive);
0165     Q_UNUSED(altModifierActive);
0166 
0167     QPolygonF transformedPolygon = m_d->transform.map(QPolygonF(m_d->transaction.originalRect()));
0168     StrokeFunction defaultFunction = transformedPolygon.containsPoint(mousePos, Qt::OddEvenFill) ? MOVE : NONE;
0169     KisTransformUtils::HandleChooser<StrokeFunction>
0170         handleChooser(mousePos, defaultFunction);
0171 
0172     qreal handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
0173 
0174     if (!m_d->transformedHandles.xVanishing.isNull()) {
0175         handleChooser.addFunction(m_d->transformedHandles.xVanishing,
0176                                   handleRadius, DRAG_X_VANISHING_POINT);
0177     }
0178 
0179     if (!m_d->transformedHandles.yVanishing.isNull()) {
0180         handleChooser.addFunction(m_d->transformedHandles.yVanishing,
0181                                   handleRadius, DRAG_Y_VANISHING_POINT);
0182     }
0183 
0184     m_d->currentDraggingHandlePoint = -1;
0185     for (int i = 0; i < m_d->dstHandlePoints.size(); i++) {
0186         if (handleChooser.addFunction(m_d->dstHandlePoints[i],
0187                                       handleRadius, DRAG_HANDLE)) {
0188 
0189             m_d->currentDraggingHandlePoint = i;
0190         }
0191     }
0192 
0193     m_d->function = handleChooser.function();
0194 }
0195 
0196 QCursor KisPerspectiveTransformStrategy::getCurrentCursor() const
0197 {
0198     QCursor cursor;
0199 
0200     switch (m_d->function) {
0201     case NONE:
0202         cursor = KisCursor::arrowCursor();
0203         break;
0204     case MOVE:
0205         cursor = KisCursor::moveCursor();
0206         break;
0207     case DRAG_HANDLE:
0208     case DRAG_X_VANISHING_POINT:
0209     case DRAG_Y_VANISHING_POINT:
0210         cursor = KisCursor::pointingHandCursor();
0211         break;
0212     }
0213 
0214     return cursor;
0215 }
0216 
0217 void KisPerspectiveTransformStrategy::paint(QPainter &gc)
0218 {
0219     gc.save();
0220 
0221     gc.setOpacity(m_d->transaction.basePreviewOpacity());
0222     gc.setTransform(m_d->paintingTransform, true);
0223     gc.drawImage(m_d->paintingOffset, originalImage());
0224 
0225     gc.restore();
0226 
0227     // Draw Handles
0228     QPainterPath handles;
0229 
0230     handles.moveTo(m_d->transaction.originalTopLeft());
0231     handles.lineTo(m_d->transaction.originalTopRight());
0232     handles.lineTo(m_d->transaction.originalBottomRight());
0233     handles.lineTo(m_d->transaction.originalBottomLeft());
0234     handles.lineTo(m_d->transaction.originalTopLeft());
0235 
0236 
0237     auto addHandleRectFunc =
0238         [&](const QPointF &pt) {
0239             handles.addRect(
0240                 KisTransformUtils::handleRect(KisTransformUtils::handleVisualRadius,
0241                                               m_d->handlesTransform,
0242                                               m_d->transaction.originalRect(), pt)
0243                 .translated(pt));
0244     };
0245 
0246     addHandleRectFunc(m_d->transaction.originalTopLeft());
0247     addHandleRectFunc(m_d->transaction.originalTopRight());
0248     addHandleRectFunc(m_d->transaction.originalBottomLeft());
0249     addHandleRectFunc(m_d->transaction.originalBottomRight());
0250     addHandleRectFunc(m_d->transaction.originalMiddleTop());
0251     addHandleRectFunc(m_d->transaction.originalMiddleBottom());
0252     addHandleRectFunc(m_d->transaction.originalMiddleLeft());
0253     addHandleRectFunc(m_d->transaction.originalMiddleRight());
0254 
0255     gc.save();
0256 
0257     if (m_d->isTransforming) {
0258         gc.setOpacity(0.1);
0259     }
0260 
0261     /**
0262      * WARNING: we cannot install a transform to paint the handles here!
0263      *
0264      * There is a bug in Qt that prevents painting of cosmetic-pen
0265      * brushes in openGL mode when a TxProject matrix is active on
0266      * a QPainter. So just convert it manually.
0267      *
0268      * https://bugreports.qt-project.org/browse/QTBUG-42658
0269      */
0270 
0271     //gc.setTransform(m_d->handlesTransform, true); <-- don't do like this!
0272 
0273     QPainterPath mappedHandles = m_d->handlesTransform.map(handles);
0274 
0275     QPen pen[2];
0276     pen[0].setWidth(decorationThickness());
0277     pen[0].setCosmetic(true);
0278     pen[1].setWidth(decorationThickness() * 2);
0279     pen[1].setCosmetic(true);
0280     pen[1].setColor(Qt::lightGray);
0281 
0282     for (int i = 1; i >= 0; --i) {
0283         gc.setPen(pen[i]);
0284         gc.drawPath(mappedHandles);
0285     }
0286 
0287     gc.restore();
0288 
0289     { // painting perspective handles
0290         QPainterPath perspectiveHandles;
0291 
0292         QRectF handleRect =
0293             KisTransformUtils::handleRect(KisTransformUtils::handleVisualRadius,
0294                                           QTransform(),
0295                                           m_d->transaction.originalRect(), 0, 0);
0296 
0297         if (m_d->transformedHandles.xVanishingExists) {
0298             QRectF rc = handleRect.translated(m_d->transformedHandles.xVanishing);
0299             perspectiveHandles.addEllipse(rc);
0300         }
0301 
0302         if (m_d->transformedHandles.yVanishingExists) {
0303             QRectF rc = handleRect.translated(m_d->transformedHandles.yVanishing);
0304             perspectiveHandles.addEllipse(rc);
0305         }
0306 
0307         if (!perspectiveHandles.isEmpty()) {
0308             gc.save();
0309             gc.setTransform(m_d->converter->imageToWidgetTransform());
0310 
0311             gc.setBrush(Qt::red);
0312 
0313             for (int i = 1; i >= 0; --i) {
0314                 gc.setPen(pen[i]);
0315                 gc.drawPath(perspectiveHandles);
0316             }
0317 
0318             gc.restore();
0319         }
0320     }
0321 }
0322 
0323 void KisPerspectiveTransformStrategy::externalConfigChanged()
0324 {
0325     m_d->recalculateTransformations();
0326 }
0327 
0328 bool KisPerspectiveTransformStrategy::beginPrimaryAction(const QPointF &pt)
0329 {
0330     Q_UNUSED(pt);
0331 
0332     if (m_d->function == NONE) return false;
0333 
0334     m_d->clickPos = pt;
0335     m_d->clickArgs = m_d->currentArgs;
0336 
0337     return true;
0338 }
0339 
0340 Eigen::Matrix3f getTransitionMatrix(const QVector<QPointF> &sp)
0341 {
0342     Eigen::Matrix3f A;
0343     Eigen::Vector3f v3;
0344 
0345     A << sp[HANDLE_TOP_LEFT].x() , sp[HANDLE_TOP_RIGHT].x() , sp[HANDLE_BOTTOM_LEFT].x()
0346         ,sp[HANDLE_TOP_LEFT].y() , sp[HANDLE_TOP_RIGHT].y() , sp[HANDLE_BOTTOM_LEFT].y()
0347         ,                    1 ,                        1   ,                        1;
0348 
0349     v3 << sp[HANDLE_BOTTOM_RIGHT].x() , sp[HANDLE_BOTTOM_RIGHT].y() , 1;
0350 
0351     Eigen::Vector3f coeffs = A.colPivHouseholderQr().solve(v3);
0352 
0353     A.col(0) *= coeffs(0);
0354     A.col(1) *= coeffs(1);
0355     A.col(2) *= coeffs(2);
0356 
0357     return A;
0358 }
0359 
0360 QTransform toQTransform(const Eigen::Matrix3f &m)
0361 {
0362     return QTransform(m(0,0), m(1,0), m(2,0),
0363                       m(0,1), m(1,1), m(2,1),
0364                       m(0,2), m(1,2), m(2,2));
0365 }
0366 
0367 Eigen::Matrix3f fromQTransform(const QTransform &t)
0368 {
0369     Eigen::Matrix3f m;
0370 
0371     m << t.m11() , t.m21() , t.m31()
0372         ,t.m12() , t.m22() , t.m32()
0373         ,t.m13() , t.m23() , t.m33();
0374 
0375     return m;
0376 }
0377 
0378 Eigen::Matrix3f fromTranslate(const QPointF &pt)
0379 {
0380     Eigen::Matrix3f m;
0381 
0382     m << 1 , 0 , pt.x()
0383         ,0 , 1 , pt.y()
0384         ,0 , 0 , 1;
0385 
0386     return m;
0387 }
0388 
0389 Eigen::Matrix3f fromScale(qreal sx, qreal sy)
0390 {
0391     Eigen::Matrix3f m;
0392 
0393     m << sx , 0 , 0
0394         ,0 , sy , 0
0395         ,0 , 0 , 1;
0396 
0397     return m;
0398 }
0399 
0400 Eigen::Matrix3f fromShear(qreal sx, qreal sy)
0401 {
0402     Eigen::Matrix3f m;
0403 
0404     m << 1 , sx , 0
0405         ,sy , sx*sy + 1, 0
0406         ,0 , 0 , 1;
0407 
0408     return m;
0409 }
0410 
0411 void KisPerspectiveTransformStrategy::Private::transformIntoArgs(const Eigen::Matrix3f &t)
0412 {
0413     Eigen::Matrix3f TS = fromTranslate(-currentArgs.originalCenter());
0414 
0415     Eigen::Matrix3f m = t * TS.inverse();
0416 
0417     qreal tX = m(0,2) / m(2,2);
0418     qreal tY = m(1,2) / m(2,2);
0419 
0420     Eigen::Matrix3f T = fromTranslate(QPointF(tX, tY));
0421 
0422     m = T.inverse() * m;
0423 
0424     /**
0425      * We disabled decomposed transformation due to bug
0426      * https://bugs.kde.org/show_bug.cgi?id=447255
0427      *
0428      * In some cases decomposed preliminary transformation
0429      * shrinks the image into a very small size, which is later
0430      * inflated by the perspective transform. It creates a really
0431      * bad and blurry result.
0432      *
0433      * Even though the usage of preliminary rotation makes the bug
0434      * much less obvious, but the image is still really blurred.
0435      */
0436 
0437 #if 0
0438     // Decomposition according to:
0439     // https://www.w3.org/TR/css-transforms-1/#decomposing-a-3d-matrix
0440     KisAlgebra2D::DecomposedMatrix dm(toQTransform(m));
0441 
0442     currentArgs.setScaleX(dm.scaleX);
0443     currentArgs.setScaleY(dm.scaleY);
0444 
0445     currentArgs.setShearX(dm.shearXY);
0446     currentArgs.setShearY(0.0);
0447 
0448     currentArgs.setAZ(kisDegreesToRadians(dm.angle));
0449 
0450     QTransform pre = dm.scaleTransform() * dm.shearTransform() * dm.rotateTransform();
0451     m = m * fromQTransform(pre.inverted());
0452 #else
0453     currentArgs.setScaleX(1.0);
0454     currentArgs.setScaleY(1.0);
0455     currentArgs.setShearX(0.0);
0456     currentArgs.setShearY(0.0);
0457     currentArgs.setAZ(0.0);
0458 #endif
0459 
0460     currentArgs.setTransformedCenter(QPointF(tX, tY));
0461     currentArgs.setFlattenedPerspectiveTransform(toQTransform(m));
0462 }
0463 
0464 QTransform KisPerspectiveTransformStrategy::Private::transformFromArgs()
0465 {
0466     KisTransformUtils::MatricesPack m(currentArgs);
0467     return m.finalTransform();
0468 }
0469 
0470 QVector4D fromQPointF(const QPointF &pt) {
0471     return QVector4D(pt.x(), pt.y(), 0, 1.0);
0472 }
0473 
0474 QPointF toQPointF(const QVector4D &v) {
0475     return v.toVector2DAffine().toPointF();
0476 }
0477 
0478 void KisPerspectiveTransformStrategy::continuePrimaryAction(const QPointF &mousePos, bool shiftModifierActive, bool altModifierActive)
0479 {
0480     Q_UNUSED(shiftModifierActive);
0481     Q_UNUSED(altModifierActive);
0482 
0483     m_d->isTransforming = true;
0484 
0485     switch (m_d->function) {
0486     case NONE:
0487         break;
0488     case MOVE: {
0489         QPointF diff = mousePos - m_d->clickPos;
0490         m_d->currentArgs.setTransformedCenter(
0491             m_d->clickArgs.transformedCenter() + diff);
0492         break;
0493     }
0494     case DRAG_HANDLE: {
0495         KIS_ASSERT_RECOVER_RETURN(m_d->currentDraggingHandlePoint >= 0);
0496         KIS_ASSERT_RECOVER_RETURN(m_d->currentDraggingHandlePoint < HANDLE_COUNT);
0497         if (m_d->currentDraggingHandlePoint < HANDLE_MIDDLE_TOP) {
0498             // Corner point, transform directly.
0499             m_d->dstHandlePoints[m_d->currentDraggingHandlePoint] = mousePos;
0500         } else {
0501             // Middle point, move adjacent corners.
0502             QPointF delta = mousePos - m_d->dstHandlePoints[m_d->currentDraggingHandlePoint];
0503             switch(m_d->currentDraggingHandlePoint) {
0504             case HANDLE_MIDDLE_TOP:
0505                 m_d->dstHandlePoints[HANDLE_TOP_LEFT] += delta;
0506                 m_d->dstHandlePoints[HANDLE_TOP_RIGHT] += delta;
0507                 break;
0508             case HANDLE_MIDDLE_BOTTOM:
0509                 m_d->dstHandlePoints[HANDLE_BOTTOM_LEFT] += delta;
0510                 m_d->dstHandlePoints[HANDLE_BOTTOM_RIGHT] += delta;
0511                 break;
0512             case HANDLE_MIDDLE_LEFT:
0513                 m_d->dstHandlePoints[HANDLE_TOP_LEFT] += delta;
0514                 m_d->dstHandlePoints[HANDLE_BOTTOM_LEFT] += delta;
0515                 break;
0516             case HANDLE_MIDDLE_RIGHT:
0517                 m_d->dstHandlePoints[HANDLE_TOP_RIGHT] += delta;
0518                 m_d->dstHandlePoints[HANDLE_BOTTOM_RIGHT] += delta;
0519                 break;
0520             }
0521         }
0522 
0523         Eigen::Matrix3f A = getTransitionMatrix(m_d->srcHandlePoints);
0524         Eigen::Matrix3f B = getTransitionMatrix(m_d->dstHandlePoints);
0525         Eigen::Matrix3f result = B * A.inverse();
0526 
0527         m_d->transformIntoArgs(result);
0528 
0529         break;
0530     }
0531     case DRAG_X_VANISHING_POINT:
0532     case DRAG_Y_VANISHING_POINT: {
0533 
0534         QMatrix4x4 m(m_d->transform);
0535 
0536         QPointF tl = m_d->transaction.originalTopLeft();
0537         QPointF tr = m_d->transaction.originalTopRight();
0538         QPointF bl = m_d->transaction.originalBottomLeft();
0539         QPointF br = m_d->transaction.originalBottomRight();
0540 
0541         QVector4D v(1,0,0,0);
0542         QVector4D otherV(0,1,0,0);
0543 
0544         if (m_d->function == DRAG_X_VANISHING_POINT) {
0545             v = QVector4D(1,0,0,0);
0546             otherV = QVector4D(0,1,0,0);
0547         } else {
0548             v = QVector4D(0,1,0,0);
0549             otherV = QVector4D(1,0,0,0);
0550         }
0551 
0552         QPointF tl_dst = toQPointF(m * fromQPointF(tl));
0553         QPointF tr_dst = toQPointF(m * fromQPointF(tr));
0554         QPointF bl_dst = toQPointF(m * fromQPointF(bl));
0555         QPointF br_dst = toQPointF(m * fromQPointF(br));
0556         QPointF v_dst = toQPointF(m * v);
0557         QPointF otherV_dst = toQPointF(m * otherV);
0558 
0559         QVector<QPointF> srcPoints;
0560         QVector<QPointF> dstPoints;
0561 
0562         QPointF far1_src;
0563         QPointF far2_src;
0564         QPointF near1_src;
0565         QPointF near2_src;
0566 
0567         QPointF far1_dst;
0568         QPointF far2_dst;
0569         QPointF near1_dst;
0570         QPointF near2_dst;
0571 
0572         if (m_d->function == DRAG_X_VANISHING_POINT) {
0573 
0574             // topLeft (far) --- topRight (near) --- vanishing
0575             if (kisSquareDistance(v_dst, tl_dst) > kisSquareDistance(v_dst, tr_dst)) {
0576                 far1_src = tl;
0577                 far2_src = bl;
0578                 near1_src = tr;
0579                 near2_src = br;
0580 
0581                 far1_dst = tl_dst;
0582                 far2_dst = bl_dst;
0583                 near1_dst = tr_dst;
0584                 near2_dst = br_dst;
0585 
0586                 // topRight (far) --- topLeft (near) --- vanishing
0587             } else {
0588                 far1_src = tr;
0589                 far2_src = br;
0590                 near1_src = tl;
0591                 near2_src = bl;
0592 
0593                 far1_dst = tr_dst;
0594                 far2_dst = br_dst;
0595                 near1_dst = tl_dst;
0596                 near2_dst = bl_dst;
0597             }
0598 
0599         } else /* if (m_d->function == DRAG_Y_VANISHING_POINT) */{
0600             // topLeft (far) --- bottomLeft (near) --- vanishing
0601             if (kisSquareDistance(v_dst, tl_dst) > kisSquareDistance(v_dst, bl_dst)) {
0602                 far1_src = tl;
0603                 far2_src = tr;
0604                 near1_src = bl;
0605                 near2_src = br;
0606 
0607                 far1_dst = tl_dst;
0608                 far2_dst = tr_dst;
0609                 near1_dst = bl_dst;
0610                 near2_dst = br_dst;
0611 
0612                 // bottomLeft (far) --- topLeft (near) --- vanishing
0613             } else {
0614                 far1_src = bl;
0615                 far2_src = br;
0616                 near1_src = tl;
0617                 near2_src = tr;
0618 
0619                 far1_dst = bl_dst;
0620                 far2_dst = br_dst;
0621                 near1_dst = tl_dst;
0622                 near2_dst = tr_dst;
0623             }
0624         }
0625 
0626         QLineF l0(far1_dst, mousePos);
0627         QLineF l1(far2_dst, mousePos);
0628         QLineF l2(otherV_dst, near1_dst);
0629         l0.intersect(l2, &near1_dst);
0630         l1.intersect(l2, &near2_dst);
0631 
0632         srcPoints << far1_src;
0633         srcPoints << far2_src;
0634         srcPoints << near1_src;
0635         srcPoints << near2_src;
0636 
0637         dstPoints << far1_dst;
0638         dstPoints << far2_dst;
0639         dstPoints << near1_dst;
0640         dstPoints << near2_dst;
0641 
0642         Eigen::Matrix3f A = getTransitionMatrix(srcPoints);
0643         Eigen::Matrix3f B = getTransitionMatrix(dstPoints);
0644         Eigen::Matrix3f result = B * A.inverse();
0645 
0646         m_d->transformIntoArgs(result);
0647         break;
0648     }
0649     }
0650 
0651     m_d->recalculateTransformations();
0652 }
0653 
0654 bool KisPerspectiveTransformStrategy::endPrimaryAction()
0655 {
0656     bool shouldSave = !m_d->imageTooBig;
0657     m_d->isTransforming = false;
0658 
0659     if (m_d->imageTooBig) {
0660         m_d->currentArgs = m_d->clickArgs;
0661         m_d->recalculateTransformations();
0662     }
0663 
0664     return shouldSave;
0665 }
0666 
0667 void KisPerspectiveTransformStrategy::Private::recalculateTransformations()
0668 {
0669     transform = transformFromArgs();
0670 
0671     QTransform viewScaleTransform = converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
0672     handlesTransform = transform * viewScaleTransform;
0673 
0674     QTransform tl = QTransform::fromTranslate(transaction.originalTopLeft().x(), transaction.originalTopLeft().y());
0675     paintingTransform = tl.inverted() * q->thumbToImageTransform() * tl * transform * viewScaleTransform;
0676     paintingOffset = transaction.originalTopLeft();
0677 
0678     // check whether image is too big to be displayed or not
0679     const qreal maxScale = 20.0;
0680 
0681     imageTooBig = false;
0682 
0683     if (qAbs(currentArgs.scaleX()) > maxScale ||
0684         qAbs(currentArgs.scaleY()) > maxScale) {
0685 
0686         imageTooBig = true;
0687 
0688     } else {
0689         QVector<QPointF> points;
0690         points << transaction.originalRect().topLeft();
0691         points << transaction.originalRect().topRight();
0692         points << transaction.originalRect().bottomRight();
0693         points << transaction.originalRect().bottomLeft();
0694 
0695         for (int i = 0; i < points.size(); i++) {
0696             points[i] = transform.map(points[i]);
0697         }
0698 
0699         for (int i = 0; i < points.size(); i++) {
0700             const QPointF &pt = points[i];
0701             const QPointF &prev = points[(i - 1 + 4) % 4];
0702             const QPointF &next = points[(i + 1) % 4];
0703             const QPointF &other = points[(i + 2) % 4];
0704 
0705             QLineF l1(pt, other);
0706             QLineF l2(prev, next);
0707 
0708             QPointF intersection;
0709             l1.intersect(l2, &intersection);
0710 
0711             qreal maxDistance = kisSquareDistance(pt, other);
0712 
0713             if (kisSquareDistance(pt, intersection) > maxDistance ||
0714                 kisSquareDistance(other, intersection) > maxDistance) {
0715 
0716                 imageTooBig = true;
0717                 break;
0718             }
0719 
0720             const qreal thresholdDistance = 0.02 * l2.length();
0721 
0722             if (kisDistanceToLine(pt, l2) < thresholdDistance) {
0723                 imageTooBig = true;
0724                 break;
0725             }
0726         }
0727     }
0728 
0729     // recalculate cached handles position
0730     recalculateTransformedHandles();
0731 
0732     emit q->requestShowImageTooBig(imageTooBig);
0733     emit q->requestImageRecalculation();
0734 }