File indexing completed on 2024-12-22 04:16:53

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_free_transform_strategy.h"
0008 
0009 #include <QPointF>
0010 #include <QPainter>
0011 #include <QPainterPath>
0012 #include <QMatrix4x4>
0013 
0014 #include <KoResourcePaths.h>
0015 
0016 #include "kis_coordinates_converter.h"
0017 #include "tool_transform_args.h"
0018 #include "transform_transaction_properties.h"
0019 #include "krita_utils.h"
0020 #include "kis_cursor.h"
0021 #include "kis_transform_utils.h"
0022 #include "kis_free_transform_strategy_gsl_helpers.h"
0023 #include "kis_algebra_2d.h"
0024 
0025 
0026 namespace {
0027 enum StrokeFunction {
0028     ROTATE = 0,
0029     MOVE,
0030     RIGHTSCALE,
0031     TOPRIGHTSCALE,
0032     TOPSCALE,
0033     TOPLEFTSCALE,
0034     LEFTSCALE,
0035     BOTTOMLEFTSCALE,
0036     BOTTOMSCALE,
0037     BOTTOMRIGHTSCALE,
0038     BOTTOMSHEAR,
0039     RIGHTSHEAR,
0040     TOPSHEAR,
0041     LEFTSHEAR,
0042     MOVECENTER,
0043     PERSPECTIVE
0044 };
0045 }
0046 
0047 struct KisFreeTransformStrategy::Private
0048 {
0049     Private(KisFreeTransformStrategy *_q,
0050             const KisCoordinatesConverter *_converter,
0051             ToolTransformArgs &_currentArgs,
0052             TransformTransactionProperties &_transaction)
0053         : q(_q),
0054           converter(_converter),
0055           currentArgs(_currentArgs),
0056           transaction(_transaction),
0057           imageTooBig(false),
0058           isTransforming(false)
0059     {
0060         scaleCursors[0] = KisCursor::sizeHorCursor();
0061         scaleCursors[1] = KisCursor::sizeFDiagCursor();
0062         scaleCursors[2] = KisCursor::sizeVerCursor();
0063         scaleCursors[3] = KisCursor::sizeBDiagCursor();
0064         scaleCursors[4] = KisCursor::sizeHorCursor();
0065         scaleCursors[5] = KisCursor::sizeFDiagCursor();
0066         scaleCursors[6] = KisCursor::sizeVerCursor();
0067         scaleCursors[7] = KisCursor::sizeBDiagCursor();
0068 
0069         shearCursorPixmap.load(":/shear_cursor.png");
0070     }
0071 
0072     KisFreeTransformStrategy *q;
0073 
0074     /// standard members ///
0075 
0076     const KisCoordinatesConverter *converter;
0077 
0078     //////
0079     ToolTransformArgs &currentArgs;
0080     //////
0081     TransformTransactionProperties &transaction;
0082 
0083 
0084     QTransform thumbToImageTransform;
0085     QImage originalImage;
0086 
0087     QTransform paintingTransform;
0088     QPointF paintingOffset;
0089 
0090     QTransform handlesTransform;
0091 
0092     /// custom members ///
0093 
0094     StrokeFunction function {MOVE};
0095 
0096     struct HandlePoints {
0097         QPointF topLeft;
0098         QPointF topMiddle;
0099         QPointF topRight;
0100 
0101         QPointF middleLeft;
0102         QPointF rotationCenter;
0103         QPointF middleRight;
0104 
0105         QPointF bottomLeft;
0106         QPointF bottomMiddle;
0107         QPointF bottomRight;
0108     };
0109     HandlePoints transformedHandles;
0110 
0111     QTransform transform;
0112 
0113     QCursor scaleCursors[8]; // cursors for the 8 directions
0114     QPixmap shearCursorPixmap;
0115 
0116     bool imageTooBig {false};
0117 
0118     ToolTransformArgs clickArgs;
0119     QPointF clickPos;
0120     QTransform clickTransform;
0121 
0122     bool isTransforming {false};
0123 
0124     QCursor getScaleCursor(const QPointF &handlePt);
0125     QCursor getShearCursor(const QPointF &start, const QPointF &end);
0126     void recalculateTransformations();
0127     void recalculateTransformedHandles();
0128 };
0129 
0130 KisFreeTransformStrategy::KisFreeTransformStrategy(const KisCoordinatesConverter *converter,
0131                                                    KoSnapGuide *snapGuide,
0132                                                    ToolTransformArgs &currentArgs,
0133                                                    TransformTransactionProperties &transaction)
0134     : KisSimplifiedActionPolicyStrategy(converter, snapGuide),
0135       m_d(new Private(this, converter, currentArgs, transaction))
0136 {
0137 }
0138 
0139 KisFreeTransformStrategy::~KisFreeTransformStrategy()
0140 {
0141 }
0142 
0143 void KisFreeTransformStrategy::Private::recalculateTransformedHandles()
0144 {
0145     transformedHandles.topLeft = transform.map(transaction.originalTopLeft());
0146     transformedHandles.topMiddle = transform.map(transaction.originalMiddleTop());
0147     transformedHandles.topRight = transform.map(transaction.originalTopRight());
0148 
0149     transformedHandles.middleLeft = transform.map(transaction.originalMiddleLeft());
0150     transformedHandles.rotationCenter = transform.map(currentArgs.originalCenter() + currentArgs.rotationCenterOffset());
0151     transformedHandles.middleRight = transform.map(transaction.originalMiddleRight());
0152 
0153     transformedHandles.bottomLeft = transform.map(transaction.originalBottomLeft());
0154     transformedHandles.bottomMiddle = transform.map(transaction.originalMiddleBottom());
0155     transformedHandles.bottomRight = transform.map(transaction.originalBottomRight());
0156 }
0157 
0158 void KisFreeTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive, bool altModifierActive)
0159 {
0160     Q_UNUSED(shiftModifierActive);
0161     Q_UNUSED(altModifierActive);
0162 
0163     if (perspectiveModifierActive && !m_d->transaction.shouldAvoidPerspectiveTransform()) {
0164         m_d->function = PERSPECTIVE;
0165         return;
0166     }
0167 
0168     QPolygonF transformedPolygon = m_d->transform.map(QPolygonF(m_d->transaction.originalRect()));
0169     qreal handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
0170     qreal rotationHandleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
0171 
0172 
0173     StrokeFunction defaultFunction =
0174         transformedPolygon.containsPoint(mousePos, Qt::OddEvenFill) ? MOVE : ROTATE;
0175     KisTransformUtils::HandleChooser<StrokeFunction>
0176         handleChooser(mousePos, defaultFunction);
0177 
0178     handleChooser.addFunction(m_d->transformedHandles.topMiddle,
0179                               handleRadius, TOPSCALE);
0180     handleChooser.addFunction(m_d->transformedHandles.topRight,
0181                               handleRadius, TOPRIGHTSCALE);
0182     handleChooser.addFunction(m_d->transformedHandles.middleRight,
0183                               handleRadius, RIGHTSCALE);
0184 
0185     handleChooser.addFunction(m_d->transformedHandles.bottomRight,
0186                               handleRadius, BOTTOMRIGHTSCALE);
0187     handleChooser.addFunction(m_d->transformedHandles.bottomMiddle,
0188                               handleRadius, BOTTOMSCALE);
0189     handleChooser.addFunction(m_d->transformedHandles.bottomLeft,
0190                               handleRadius, BOTTOMLEFTSCALE);
0191     handleChooser.addFunction(m_d->transformedHandles.middleLeft,
0192                               handleRadius, LEFTSCALE);
0193     handleChooser.addFunction(m_d->transformedHandles.topLeft,
0194                               handleRadius, TOPLEFTSCALE);
0195     handleChooser.addFunction(m_d->transformedHandles.rotationCenter,
0196                               rotationHandleRadius, MOVECENTER);
0197 
0198     m_d->function = handleChooser.function();
0199 
0200     if (m_d->function == ROTATE || m_d->function == MOVE) {
0201         QRectF originalRect = m_d->transaction.originalRect();
0202         QPointF t = m_d->transform.inverted().map(mousePos);
0203 
0204         if (t.x() >= originalRect.left() && t.x() <= originalRect.right()) {
0205             if (fabs(t.y() - originalRect.top()) <= handleRadius)
0206                 m_d->function = TOPSHEAR;
0207             if (fabs(t.y() - originalRect.bottom()) <= handleRadius)
0208                 m_d->function = BOTTOMSHEAR;
0209         }
0210         if (t.y() >= originalRect.top() && t.y() <= originalRect.bottom()) {
0211             if (fabs(t.x() - originalRect.left()) <= handleRadius)
0212                 m_d->function = LEFTSHEAR;
0213             if (fabs(t.x() - originalRect.right()) <= handleRadius)
0214                 m_d->function = RIGHTSHEAR;
0215         }
0216     }
0217 }
0218 
0219 bool KisFreeTransformStrategy::shiftModifierIsUsed() const
0220 {
0221     return true;
0222 }
0223 
0224 QCursor KisFreeTransformStrategy::Private::getScaleCursor(const QPointF &handlePt)
0225 {
0226     QPointF handlePtInWidget = converter->imageToWidget(handlePt);
0227     QPointF centerPtInWidget = converter->imageToWidget(currentArgs.transformedCenter());
0228 
0229     QPointF direction = handlePtInWidget - centerPtInWidget;
0230     qreal angle = atan2(direction.y(), direction.x());
0231     angle = normalizeAngle(angle);
0232 
0233     int octant = qRound(angle * 4. / M_PI) % 8;
0234     return scaleCursors[octant];
0235 }
0236 
0237 QCursor KisFreeTransformStrategy::Private::getShearCursor(const QPointF &start, const QPointF &end)
0238 {
0239     QPointF startPtInWidget = converter->imageToWidget(start);
0240     QPointF endPtInWidget = converter->imageToWidget(end);
0241     QPointF direction = endPtInWidget - startPtInWidget;
0242 
0243     qreal angle = atan2(-direction.y(), direction.x());
0244     return QCursor(shearCursorPixmap.transformed(QTransform().rotateRadians(-angle)));
0245 }
0246 
0247 QCursor KisFreeTransformStrategy::getCurrentCursor() const
0248 {
0249     QCursor cursor;
0250 
0251     switch (m_d->function) {
0252     case MOVE:
0253         cursor = KisCursor::moveCursor();
0254         break;
0255     case ROTATE:
0256         cursor = KisCursor::rotateCursor();
0257         break;
0258     case PERSPECTIVE:
0259         //TODO: find another cursor for perspective
0260         cursor = KisCursor::rotateCursor();
0261         break;
0262     case RIGHTSCALE:
0263         cursor = m_d->getScaleCursor(m_d->transformedHandles.middleRight);
0264         break;
0265     case TOPSCALE:
0266         cursor = m_d->getScaleCursor(m_d->transformedHandles.topMiddle);
0267         break;
0268     case LEFTSCALE:
0269         cursor = m_d->getScaleCursor(m_d->transformedHandles.middleLeft);
0270         break;
0271     case BOTTOMSCALE:
0272         cursor = m_d->getScaleCursor(m_d->transformedHandles.bottomMiddle);
0273         break;
0274     case TOPRIGHTSCALE:
0275         cursor = m_d->getScaleCursor(m_d->transformedHandles.topRight);
0276         break;
0277     case BOTTOMLEFTSCALE:
0278         cursor = m_d->getScaleCursor(m_d->transformedHandles.bottomLeft);
0279         break;
0280     case TOPLEFTSCALE:
0281         cursor = m_d->getScaleCursor(m_d->transformedHandles.topLeft);
0282         break;
0283     case BOTTOMRIGHTSCALE:
0284         cursor = m_d->getScaleCursor(m_d->transformedHandles.bottomRight);
0285         break;
0286     case MOVECENTER:
0287         cursor = KisCursor::handCursor();
0288         break;
0289     case BOTTOMSHEAR:
0290         cursor = m_d->getShearCursor(m_d->transformedHandles.bottomLeft, m_d->transformedHandles.bottomRight);
0291         break;
0292     case RIGHTSHEAR:
0293         cursor = m_d->getShearCursor(m_d->transformedHandles.bottomRight, m_d->transformedHandles.topRight);
0294         break;
0295     case TOPSHEAR:
0296         cursor = m_d->getShearCursor(m_d->transformedHandles.topRight, m_d->transformedHandles.topLeft);
0297         break;
0298     case LEFTSHEAR:
0299         cursor = m_d->getShearCursor(m_d->transformedHandles.topLeft, m_d->transformedHandles.bottomLeft);
0300         break;
0301     }
0302 
0303     return cursor;
0304 }
0305 
0306 void KisFreeTransformStrategy::paint(QPainter &gc)
0307 {
0308     gc.save();
0309 
0310     gc.setOpacity(m_d->transaction.basePreviewOpacity());
0311     gc.setTransform(m_d->paintingTransform, true);
0312     gc.drawImage(m_d->paintingOffset, originalImage());
0313 
0314     gc.restore();
0315 
0316     // Draw Handles
0317 
0318     QRectF handleRect =
0319         KisTransformUtils::handleRect(KisTransformUtils::handleVisualRadius,
0320                                       m_d->handlesTransform,
0321                                       m_d->transaction.originalRect(), 0, 0);
0322 
0323     qreal rX = 1;
0324     qreal rY = 1;
0325     QRectF rotationCenterRect =
0326         KisTransformUtils::handleRect(KisTransformUtils::rotationHandleVisualRadius,
0327                                       m_d->handlesTransform,
0328                                       m_d->transaction.originalRect(),
0329                                       &rX,
0330                                       &rY);
0331 
0332     QPainterPath handles;
0333 
0334     handles.moveTo(m_d->transaction.originalTopLeft());
0335     handles.lineTo(m_d->transaction.originalTopRight());
0336     handles.lineTo(m_d->transaction.originalBottomRight());
0337     handles.lineTo(m_d->transaction.originalBottomLeft());
0338     handles.lineTo(m_d->transaction.originalTopLeft());
0339 
0340     handles.addRect(handleRect.translated(m_d->transaction.originalTopLeft()));
0341     handles.addRect(handleRect.translated(m_d->transaction.originalTopRight()));
0342     handles.addRect(handleRect.translated(m_d->transaction.originalBottomLeft()));
0343     handles.addRect(handleRect.translated(m_d->transaction.originalBottomRight()));
0344     handles.addRect(handleRect.translated(m_d->transaction.originalMiddleLeft()));
0345     handles.addRect(handleRect.translated(m_d->transaction.originalMiddleRight()));
0346     handles.addRect(handleRect.translated(m_d->transaction.originalMiddleTop()));
0347     handles.addRect(handleRect.translated(m_d->transaction.originalMiddleBottom()));
0348 
0349     QPointF rotationCenter = m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset();
0350     QPointF dx(rX + 3, 0);
0351     QPointF dy(0, rY + 3);
0352     handles.addEllipse(rotationCenterRect.translated(rotationCenter));
0353     handles.moveTo(rotationCenter - dx);
0354     handles.lineTo(rotationCenter + dx);
0355     handles.moveTo(rotationCenter - dy);
0356     handles.lineTo(rotationCenter + dy);
0357 
0358     gc.save();
0359 
0360     if (m_d->isTransforming) {
0361         gc.setOpacity(0.1);
0362     }
0363 
0364     //gc.setTransform(m_d->handlesTransform, true); <-- don't do like this!
0365     QPainterPath mappedHandles = m_d->handlesTransform.map(handles);
0366 
0367     QPen pen[2];
0368     pen[0].setWidth(decorationThickness());
0369     pen[0].setCosmetic(true);
0370     pen[1].setWidth(decorationThickness() * 2);
0371     pen[1].setCosmetic(true);
0372     pen[1].setColor(Qt::lightGray);
0373 
0374     for (int i = 1; i >= 0; --i) {
0375         gc.setPen(pen[i]);
0376         gc.drawPath(mappedHandles);
0377     }
0378 
0379     gc.restore();
0380 }
0381 
0382 void KisFreeTransformStrategy::externalConfigChanged()
0383 {
0384     m_d->recalculateTransformations();
0385 }
0386 
0387 bool KisFreeTransformStrategy::beginPrimaryAction(const QPointF &pt)
0388 {
0389     m_d->clickArgs = m_d->currentArgs;
0390     m_d->clickPos = pt;
0391 
0392     KisTransformUtils::MatricesPack m(m_d->clickArgs);
0393     m_d->clickTransform = m.finalTransform();
0394 
0395     return true;
0396 }
0397 
0398 void KisFreeTransformStrategy::continuePrimaryAction(const QPointF &mousePos,
0399                                                      bool shiftModifierActive,
0400                                                      bool altModifierActive)
0401 {
0402     // Note: "shiftModifierActive" just tells us if the shift key is being pressed
0403     // Note: "altModifierActive" just tells us if the alt key is being pressed
0404 
0405     m_d->isTransforming = true;
0406     const QPointF anchorPoint = m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset();
0407 
0408     switch (m_d->function) {
0409     case MOVE: {
0410         QPointF diff = mousePos - m_d->clickPos;
0411 
0412         if (shiftModifierActive) {
0413 
0414             KisTransformUtils::MatricesPack m(m_d->clickArgs);
0415             QTransform t = m.S * m.projectedP;
0416             QPointF originalDiff = t.inverted().map(diff);
0417 
0418             if (qAbs(originalDiff.x()) >= qAbs(originalDiff.y())) {
0419                 originalDiff.setY(0);
0420             } else {
0421                 originalDiff.setX(0);
0422             }
0423 
0424             diff = t.map(originalDiff);
0425 
0426         }
0427 
0428         m_d->currentArgs.setTransformedCenter(m_d->clickArgs.transformedCenter() + diff);
0429 
0430         break;
0431     }
0432     case ROTATE:
0433     {
0434         const KisTransformUtils::MatricesPack clickM(m_d->clickArgs);
0435         const QTransform clickT = clickM.finalTransform();
0436 
0437         const QPointF rotationCenter = m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset();
0438         const QPointF clickMouseImagePos = clickT.inverted().map(m_d->clickPos) - rotationCenter;
0439         const QPointF mouseImagePosClickSpace = clickT.inverted().map(mousePos) - rotationCenter;
0440 
0441         const qreal a1 = atan2(clickMouseImagePos.y(), clickMouseImagePos.x());
0442         const qreal a2 = atan2(mouseImagePosClickSpace.y(), mouseImagePosClickSpace.x());
0443 
0444         const qreal theta = KisAlgebra2D::signZZ(clickT.determinant()) * (a2 - a1);
0445 
0446         // Snap with shift key
0447         if (shiftModifierActive) {
0448             const qreal snapAngle = M_PI_4 / 6.0; // fifteen degrees
0449             qint32 thetaIndex = static_cast<qint32>((theta / snapAngle) + 0.5);
0450             m_d->currentArgs.setAZ(thetaIndex * snapAngle);
0451         }
0452         else {
0453             const qreal clickAngle = m_d->clickArgs.aZ();
0454             const qreal targetAngle = m_d->clickArgs.aZ() + theta;
0455             qreal shortestDistance = shortestAngularDistance(clickAngle, targetAngle);
0456             const bool clockwise =  (theta <= M_PI && theta >= 0) || (theta < -M_PI);
0457             shortestDistance = clockwise ? shortestDistance : shortestDistance * -1;
0458 
0459             m_d->currentArgs.setAZ(m_d->clickArgs.aZ() + shortestDistance);
0460         }
0461 
0462         KisTransformUtils::MatricesPack m(m_d->currentArgs);
0463         QTransform t = m.finalTransform();
0464         QPointF newRotationCenter = t.map(m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset());
0465         QPointF oldRotationCenter = clickT.map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset());
0466 
0467         m_d->currentArgs.setTransformedCenter(m_d->currentArgs.transformedCenter() + oldRotationCenter - newRotationCenter);
0468     }
0469     break;
0470     case PERSPECTIVE:
0471     {
0472         QPointF diff = mousePos - m_d->clickPos;
0473         double thetaX = - diff.y() * M_PI / m_d->transaction.originalHalfHeight() / 2 / fabs(m_d->currentArgs.scaleY());
0474         m_d->currentArgs.setAX(normalizeAngle(m_d->clickArgs.aX() + thetaX));
0475 
0476         qreal sign = qAbs(m_d->currentArgs.aX() - M_PI) < M_PI / 2 ? -1.0 : 1.0;
0477         double thetaY = sign * diff.x() * M_PI / m_d->transaction.originalHalfWidth() / 2 / fabs(m_d->currentArgs.scaleX());
0478         m_d->currentArgs.setAY(normalizeAngle(m_d->clickArgs.aY() + thetaY));
0479 
0480         KisTransformUtils::MatricesPack m(m_d->currentArgs);
0481         QTransform t = m.finalTransform();
0482         QPointF newRotationCenter = t.map(m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset());
0483 
0484         KisTransformUtils::MatricesPack clickM(m_d->clickArgs);
0485         QTransform clickT = clickM.finalTransform();
0486         QPointF oldRotationCenter = clickT.map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset());
0487 
0488         m_d->currentArgs.setTransformedCenter(m_d->currentArgs.transformedCenter() + oldRotationCenter - newRotationCenter);
0489     }
0490     break;
0491     case TOPSCALE:
0492     case BOTTOMSCALE: {
0493         QPointF staticPoint;
0494         QPointF movingPoint;
0495 
0496         if (m_d->function == TOPSCALE) {
0497             staticPoint = m_d->transaction.originalMiddleBottom();
0498             movingPoint = m_d->transaction.originalMiddleTop();
0499         } else {
0500             staticPoint = m_d->transaction.originalMiddleTop();
0501             movingPoint = m_d->transaction.originalMiddleBottom();
0502         }
0503 
0504         QPointF staticPointInView = m_d->clickTransform.map(staticPoint);
0505         const QPointF movingPointInView = m_d->clickTransform.map(movingPoint);
0506 
0507         const QPointF projNormVector =
0508             KisAlgebra2D::normalize(movingPointInView - staticPointInView);
0509 
0510         const qreal projLength =
0511             KisAlgebra2D::dotProduct(mousePos - staticPointInView, projNormVector);
0512 
0513         const QPointF targetMovingPointInView = staticPointInView + projNormVector * projLength;
0514 
0515         // override scale static point if it is locked
0516         if ((m_d->clickArgs.transformAroundRotationCenter() ^ altModifierActive) &&
0517             !qFuzzyCompare(anchorPoint.y(), movingPoint.y())) {
0518 
0519             staticPoint = anchorPoint;
0520             staticPointInView = m_d->clickTransform.map(staticPoint);
0521         }
0522 
0523         GSL::ScaleResult1D result =
0524             GSL::calculateScaleY(m_d->currentArgs,
0525                                  staticPoint,
0526                                  staticPointInView,
0527                                  movingPoint,
0528                                  targetMovingPointInView);
0529 
0530         if (!result.isValid) {
0531             break;
0532         }
0533 
0534         if (shiftModifierActive ||  m_d->currentArgs.keepAspectRatio()) {
0535             qreal aspectRatio = m_d->clickArgs.scaleX() / m_d->clickArgs.scaleY();
0536             m_d->currentArgs.setScaleX(aspectRatio * result.scale);
0537         }
0538 
0539         m_d->currentArgs.setScaleY(result.scale);
0540         m_d->currentArgs.setTransformedCenter(result.transformedCenter);
0541         break;
0542     }
0543 
0544     case LEFTSCALE:
0545     case RIGHTSCALE: {
0546         QPointF staticPoint;
0547         QPointF movingPoint;
0548 
0549         if (m_d->function == LEFTSCALE) {
0550             staticPoint = m_d->transaction.originalMiddleRight();
0551             movingPoint = m_d->transaction.originalMiddleLeft();
0552         } else {
0553             staticPoint = m_d->transaction.originalMiddleLeft();
0554             movingPoint = m_d->transaction.originalMiddleRight();
0555         }
0556 
0557         QPointF staticPointInView = m_d->clickTransform.map(staticPoint);
0558         const QPointF movingPointInView = m_d->clickTransform.map(movingPoint);
0559 
0560         const QPointF projNormVector =
0561             KisAlgebra2D::normalize(movingPointInView - staticPointInView);
0562 
0563         const qreal projLength =
0564             KisAlgebra2D::dotProduct(mousePos - staticPointInView, projNormVector);
0565 
0566         const QPointF targetMovingPointInView = staticPointInView + projNormVector * projLength;
0567 
0568         // override scale static point if it is locked
0569         if ((m_d->currentArgs.transformAroundRotationCenter() ^ altModifierActive) &&
0570             !qFuzzyCompare(anchorPoint.x(), movingPoint.x())) {
0571 
0572             staticPoint = anchorPoint;
0573             staticPointInView = m_d->clickTransform.map(staticPoint);
0574         }
0575 
0576         GSL::ScaleResult1D result =
0577             GSL::calculateScaleX(m_d->currentArgs,
0578                                  staticPoint,
0579                                  staticPointInView,
0580                                  movingPoint,
0581                                  targetMovingPointInView);
0582 
0583         if (!result.isValid) {
0584             break;
0585         }
0586 
0587         if (shiftModifierActive  ||  m_d->currentArgs.keepAspectRatio()) {
0588             qreal aspectRatio = m_d->clickArgs.scaleY() / m_d->clickArgs.scaleX();
0589             m_d->currentArgs.setScaleY(aspectRatio * result.scale);
0590         }
0591 
0592         m_d->currentArgs.setScaleX(result.scale);
0593         m_d->currentArgs.setTransformedCenter(result.transformedCenter);
0594         break;
0595     }
0596     case TOPRIGHTSCALE:
0597     case BOTTOMRIGHTSCALE:
0598     case TOPLEFTSCALE:
0599     case BOTTOMLEFTSCALE: {
0600         QPointF staticPoint;
0601         QPointF movingPoint;
0602 
0603         if (m_d->function == TOPRIGHTSCALE) {
0604             staticPoint = m_d->transaction.originalBottomLeft();
0605             movingPoint = m_d->transaction.originalTopRight();
0606         } else if (m_d->function == BOTTOMRIGHTSCALE) {
0607             staticPoint = m_d->transaction.originalTopLeft();
0608             movingPoint = m_d->transaction.originalBottomRight();
0609         } else if (m_d->function == TOPLEFTSCALE) {
0610             staticPoint = m_d->transaction.originalBottomRight();
0611             movingPoint = m_d->transaction.originalTopLeft();
0612         } else {
0613             staticPoint = m_d->transaction.originalTopRight();
0614             movingPoint = m_d->transaction.originalBottomLeft();
0615         }
0616 
0617         // override scale static point if it is locked
0618         if ((m_d->currentArgs.transformAroundRotationCenter() ^ altModifierActive) &&
0619             !(qFuzzyCompare(anchorPoint.x(), movingPoint.x()) ||
0620               qFuzzyCompare(anchorPoint.y(), movingPoint.y()))) {
0621 
0622             staticPoint = anchorPoint;
0623         }
0624 
0625         QPointF staticPointInView = m_d->clickTransform.map(staticPoint);
0626         QPointF movingPointInView = mousePos;
0627 
0628         if (shiftModifierActive  ||  m_d->currentArgs.keepAspectRatio()) {
0629             QPointF refDiff = m_d->clickTransform.map(movingPoint) - staticPointInView;
0630             QPointF realDiff = mousePos - staticPointInView;
0631             realDiff = kisProjectOnVector(refDiff, realDiff);
0632 
0633             movingPointInView = staticPointInView + realDiff;
0634         }
0635 
0636         const bool isAffine =
0637             qFuzzyIsNull(m_d->currentArgs.aX()) &&
0638             qFuzzyIsNull(m_d->currentArgs.aY());
0639 
0640         GSL::ScaleResult2D result =
0641                 !isAffine ?
0642                     GSL::calculateScale2D(m_d->currentArgs,
0643                                           staticPoint,
0644                                           staticPointInView,
0645                                           movingPoint,
0646                                           movingPointInView) :
0647                     GSL::calculateScale2DAffine(m_d->currentArgs,
0648                                                 staticPoint,
0649                                                 staticPointInView,
0650                                                 movingPoint,
0651                                                 movingPointInView);
0652 
0653         if (result.isValid) {
0654             m_d->currentArgs.setScaleX(result.scaleX);
0655             m_d->currentArgs.setScaleY(result.scaleY);
0656             m_d->currentArgs.setTransformedCenter(result.transformedCenter);
0657         }
0658 
0659         break;
0660     }
0661     case MOVECENTER: {
0662         QPointF pt = m_d->transform.inverted().map(mousePos);
0663         if (altModifierActive) {
0664             pt = KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect());
0665         }
0666 
0667         QPointF newRotationCenterOffset = pt - m_d->currentArgs.originalCenter();
0668 
0669         if (shiftModifierActive) {
0670             if (qAbs(newRotationCenterOffset.x()) > qAbs(newRotationCenterOffset.y())) {
0671                 newRotationCenterOffset.ry() = 0;
0672             } else {
0673                 newRotationCenterOffset.rx() = 0;
0674             }
0675         }
0676 
0677         m_d->currentArgs.setRotationCenterOffset(newRotationCenterOffset);
0678         emit requestResetRotationCenterButtons();
0679     }
0680         break;
0681     case TOPSHEAR:
0682     case BOTTOMSHEAR: {
0683         KisTransformUtils::MatricesPack m(m_d->clickArgs);
0684 
0685         QPointF oldStaticPoint = m.finalTransform().map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset());
0686 
0687         QTransform backwardT = (m.S * m.projectedP).inverted();
0688         QPointF diff = backwardT.map(mousePos - m_d->clickPos);
0689 
0690         qreal sign = m_d->function == BOTTOMSHEAR ? 1.0 : -1.0;
0691 
0692         // get the dx pixels corresponding to the current shearX factor
0693         qreal dx = sign * m_d->clickArgs.shearX() * m_d->clickArgs.scaleY() * m_d->transaction.originalHalfHeight(); // get the dx pixels corresponding to the current shearX factor
0694         dx += diff.x();
0695 
0696         // calculate the new shearX factor
0697         m_d->currentArgs.setShearX(sign * dx / m_d->currentArgs.scaleY() / m_d->transaction.originalHalfHeight()); // calculate the new shearX factor
0698 
0699         KisTransformUtils::MatricesPack currentM(m_d->currentArgs);
0700         QTransform t = currentM.finalTransform();
0701         QPointF newStaticPoint = t.map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset());
0702         m_d->currentArgs.setTransformedCenter(m_d->currentArgs.transformedCenter() + oldStaticPoint - newStaticPoint);
0703         break;
0704     }
0705 
0706     case LEFTSHEAR:
0707     case RIGHTSHEAR: {
0708         KisTransformUtils::MatricesPack m(m_d->clickArgs);
0709 
0710         QPointF oldStaticPoint = m.finalTransform().map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset());
0711 
0712         QTransform backwardT = (m.S * m.projectedP).inverted();
0713         QPointF diff = backwardT.map(mousePos - m_d->clickPos);
0714 
0715         qreal sign = m_d->function == RIGHTSHEAR ? 1.0 : -1.0;
0716 
0717         // get the dx pixels corresponding to the current shearX factor
0718         qreal dy = sign *  m_d->clickArgs.shearY() * m_d->clickArgs.scaleX() * m_d->transaction.originalHalfWidth();
0719         dy += diff.y();
0720 
0721         // calculate the new shearY factor
0722         m_d->currentArgs.setShearY(sign * dy / m_d->clickArgs.scaleX() / m_d->transaction.originalHalfWidth());
0723 
0724         KisTransformUtils::MatricesPack currentM(m_d->currentArgs);
0725         QTransform t = currentM.finalTransform();
0726         QPointF newStaticPoint = t.map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset());
0727         m_d->currentArgs.setTransformedCenter(m_d->currentArgs.transformedCenter() + oldStaticPoint - newStaticPoint);
0728         break;
0729     }
0730     }
0731 
0732     m_d->recalculateTransformations();
0733 }
0734 
0735 bool KisFreeTransformStrategy::endPrimaryAction()
0736 {
0737     bool shouldSave = !m_d->imageTooBig;
0738     m_d->isTransforming = false;
0739 
0740     if (m_d->imageTooBig) {
0741         m_d->currentArgs = m_d->clickArgs;
0742         m_d->recalculateTransformations();
0743     }
0744 
0745     return shouldSave;
0746 }
0747 
0748 void KisFreeTransformStrategy::Private::recalculateTransformations()
0749 {
0750     KisTransformUtils::MatricesPack m(currentArgs);
0751     QTransform sanityCheckMatrix = m.TS * m.SC * m.S * m.projectedP;
0752 
0753     /**
0754      * The center of the original image should still
0755      * stay the origin of CS
0756      */
0757     KIS_ASSERT_RECOVER_NOOP(sanityCheckMatrix.map(currentArgs.originalCenter()).manhattanLength() < 1e-4);
0758 
0759     transform = m.finalTransform();
0760 
0761     QTransform viewScaleTransform = converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
0762     handlesTransform = transform * viewScaleTransform;
0763 
0764     QTransform tl = QTransform::fromTranslate(transaction.originalTopLeft().x(), transaction.originalTopLeft().y());
0765     paintingTransform = tl.inverted() * q->thumbToImageTransform() * tl * transform * viewScaleTransform;
0766     paintingOffset = transaction.originalTopLeft();
0767 
0768     // check whether image is too big to be displayed or not
0769     imageTooBig = KisTransformUtils::checkImageTooBig(transaction.originalRect(), m, currentArgs.cameraPos().z());
0770 
0771     // recalculate cached handles position
0772     recalculateTransformedHandles();
0773 
0774     emit q->requestShowImageTooBig(imageTooBig);
0775     emit q->requestImageRecalculation();
0776 }