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