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 ¤tArgs; 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 ¤tArgs, 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 }