File indexing completed on 2024-06-16 04:18:02

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_transform_utils.h"
0008 
0009 #include <cmath>
0010 #include <QPainterPath>
0011 #include <QTransform>
0012 #include <KoUnit.h>
0013 #include "tool_transform_args.h"
0014 #include "kis_paint_device.h"
0015 #include "kis_algebra_2d.h"
0016 #include "transform_transaction_properties.h"
0017 #include "kis_painter.h"
0018 
0019 #include <kis_transform_worker.h>
0020 #include <kis_perspectivetransform_worker.h>
0021 #include <kis_warptransform_worker.h>
0022 #include <kis_cage_transform_worker.h>
0023 #include <kis_liquify_transform_worker.h>
0024 
0025 #include "commands_new/kis_saved_commands.h"
0026 #include "kis_transform_mask.h"
0027 #include "kis_transform_mask_adapter.h"
0028 #include "krita_container_utils.h"
0029 #include "kis_selection.h"
0030 #include "kis_image.h"
0031 #include "kis_image_animation_interface.h"
0032 
0033 const int KisTransformUtils::rotationHandleVisualRadius = 12;
0034 const int KisTransformUtils::rotationHandleRadius = 8;
0035 const int KisTransformUtils::handleVisualRadius = 12;
0036 const int KisTransformUtils::handleRadius = 8;
0037 
0038 
0039 QTransform KisTransformUtils::imageToFlakeTransform(const KisCoordinatesConverter *converter)
0040 {
0041     return converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
0042 }
0043 
0044 qreal KisTransformUtils::effectiveHandleGrabRadius(const KisCoordinatesConverter *converter)
0045 {
0046     QPointF handleRadiusPt = flakeToImage(converter, QPointF(handleRadius, handleRadius));
0047     return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y();
0048 }
0049 
0050 qreal KisTransformUtils::effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter)
0051 {
0052     QPointF handleRadiusPt = flakeToImage(converter, QPointF(rotationHandleRadius, rotationHandleRadius));
0053     return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y();
0054 }
0055 
0056 qreal KisTransformUtils::scaleFromAffineMatrix(const QTransform &t) {
0057     return KoUnit::approxTransformScale(t);
0058 }
0059 
0060 qreal KisTransformUtils::scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt) {
0061     const QPointF pt = basePt + QPointF(1.0, 0);
0062     return kisDistance(t.map(pt), t.map(basePt));
0063 }
0064 
0065 qreal KisTransformUtils::scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt) {
0066     const QPointF pt = basePt + QPointF(0, 1.0);
0067     return kisDistance(t.map(pt), t.map(basePt));
0068 }
0069 
0070 qreal KisTransformUtils::effectiveSize(const QRectF &rc) {
0071     return 0.5 * (rc.width() + rc.height());
0072 }
0073 
0074 bool KisTransformUtils::thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect)
0075 {
0076     return KisAlgebra2D::minDimension(resultThumbTransform.mapRect(originalImageRect)) < 32;
0077 }
0078 
0079 QRectF handleRectImpl(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint, qreal *dOutX, qreal *dOutY) {
0080     const qreal handlesExtraScaleX =
0081         KisTransformUtils::scaleFromPerspectiveMatrixX(t, basePoint);
0082     const qreal handlesExtraScaleY =
0083         KisTransformUtils::scaleFromPerspectiveMatrixY(t, basePoint);
0084 
0085     const qreal maxD = 0.2 * KisTransformUtils::effectiveSize(limitingRect);
0086     const qreal dX = qMin(maxD, radius / handlesExtraScaleX);
0087     const qreal dY = qMin(maxD, radius / handlesExtraScaleY);
0088 
0089     QRectF handleRect(-0.5 * dX, -0.5 * dY, dX, dY);
0090 
0091     if (dOutX) {
0092         *dOutX = dX;
0093     }
0094 
0095     if (dOutY) {
0096         *dOutY = dY;
0097     }
0098 
0099     return handleRect;
0100 
0101 }
0102 
0103 QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY) {
0104     return handleRectImpl(radius, t, limitingRect, limitingRect.center(), dOutX, dOutY);
0105 }
0106 
0107 QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint) {
0108     return handleRectImpl(radius, t, limitingRect, basePoint, 0, 0);
0109 }
0110 
0111 QPointF KisTransformUtils::clipInRect(QPointF p, QRectF r)
0112 {
0113     QPointF center = r.center();
0114     QPointF t = p - center;
0115     r.translate(- center);
0116 
0117     if (t.y() != 0) {
0118         if (t.x() != 0) {
0119             double slope = t.y() / t.x();
0120 
0121             if (t.x() < r.left()) {
0122                 t.setY(r.left() * slope);
0123                 t.setX(r.left());
0124             }
0125             else if (t.x() > r.right()) {
0126                 t.setY(r.right() * slope);
0127                 t.setX(r.right());
0128             }
0129 
0130             if (t.y() < r.top()) {
0131                 t.setX(r.top() / slope);
0132                 t.setY(r.top());
0133             }
0134             else if (t.y() > r.bottom()) {
0135                 t.setX(r.bottom() / slope);
0136                 t.setY(r.bottom());
0137             }
0138         }
0139         else {
0140             if (t.y() < r.top())
0141                 t.setY(r.top());
0142             else if (t.y() > r.bottom())
0143                 t.setY(r.bottom());
0144         }
0145     }
0146     else {
0147         if (t.x() < r.left())
0148             t.setX(r.left());
0149         else if (t.x() > r.right())
0150             t.setX(r.right());
0151     }
0152 
0153     t += center;
0154 
0155     return t;
0156 }
0157 
0158 KisTransformUtils::MatricesPack::MatricesPack(const ToolTransformArgs &args)
0159 {
0160     TS = QTransform::fromTranslate(-args.originalCenter().x(), -args.originalCenter().y());
0161     SC = QTransform::fromScale(args.scaleX(), args.scaleY());
0162     S.shear(0, args.shearY()); S.shear(args.shearX(), 0);
0163 
0164     if (args.mode() == ToolTransformArgs::FREE_TRANSFORM) {
0165         P.rotate(180. * normalizeAngle(args.aX()) / M_PI, QVector3D(1, 0, 0));
0166         P.rotate(180. * normalizeAngle(args.aY()) / M_PI, QVector3D(0, 1, 0));
0167         P.rotate(180. * normalizeAngle(args.aZ()) / M_PI, QVector3D(0, 0, 1));
0168         projectedP = P.toTransform(args.cameraPos().z());
0169     } else if (args.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
0170         // see a comment in KisPerspectiveTransformStrategy::Private::transformIntoArgs()
0171 #if 0
0172         projectedP.rotate(kisRadiansToDegrees(args.aZ()));
0173         projectedP *= args.flattenedPerspectiveTransform();
0174 #else
0175         projectedP = args.flattenedPerspectiveTransform();
0176 #endif
0177         P = QMatrix4x4(projectedP);
0178     }
0179 
0180     QPointF translation = args.transformedCenter();
0181     T = QTransform::fromTranslate(translation.x(), translation.y());
0182 }
0183 
0184 QTransform KisTransformUtils::MatricesPack::finalTransform() const
0185 {
0186     return TS * SC * S * projectedP * T;
0187 }
0188 
0189 bool KisTransformUtils::checkImageTooBig(const QRectF &bounds, const MatricesPack &m, qreal cameraHeight)
0190 {
0191     bool imageTooBig = false;
0192 
0193     QMatrix4x4 unprojectedMatrix = QMatrix4x4(m.T) * m.P * QMatrix4x4(m.TS * m.SC * m.S);
0194     QVector<QPointF> points;
0195     points << bounds.topLeft();
0196     points << bounds.topRight();
0197     points << bounds.bottomRight();
0198     points << bounds.bottomLeft();
0199 
0200     Q_FOREACH (const QPointF &pt, points) {
0201         QVector4D v(pt.x(), pt.y(), 0, 1);
0202 
0203         v = unprojectedMatrix * v;
0204         qreal z = v.z() / v.w();
0205 
0206         imageTooBig = z > 1.5 * cameraHeight;
0207 
0208         if (imageTooBig) {
0209             break;
0210         }
0211     }
0212 
0213     return imageTooBig;
0214 }
0215 
0216 KisTransformWorker KisTransformUtils::createTransformWorker(const ToolTransformArgs &config,
0217                                                             KisPaintDeviceSP device,
0218                                                             KoUpdaterPtr updater,
0219                                                             QVector3D *transformedCenter /* OUT */)
0220 {
0221     {
0222         KisTransformWorker t(0,
0223                              config.scaleX(), config.scaleY(),
0224                              config.shearX(), config.shearY(),
0225                              config.originalCenter().x(),
0226                              config.originalCenter().y(),
0227                              config.aZ(),
0228                              0, // set X and Y translation
0229                              0, // to null for calculation
0230                              0,
0231                              config.filter());
0232 
0233         *transformedCenter = QVector3D(t.transform().map(config.originalCenter()));
0234     }
0235 
0236     QPointF translation = config.transformedCenter() - (*transformedCenter).toPointF();
0237 
0238     KisTransformWorker transformWorker(device,
0239                                        config.scaleX(), config.scaleY(),
0240                                        config.shearX(), config.shearY(),
0241                                        config.originalCenter().x(),
0242                                        config.originalCenter().y(),
0243                                        normalizeAngle(config.aZ()),
0244                                        translation.x(),
0245                                        translation.y(),
0246                                        updater,
0247                                        config.filter());
0248 
0249     return transformWorker;
0250 }
0251 
0252 void KisTransformUtils::transformDevice(const ToolTransformArgs &config,
0253                                         KisPaintDeviceSP device,
0254                                         KisProcessingVisitor::ProgressHelper *helper)
0255 {
0256     KisPaintDeviceSP tmp = new KisPaintDevice(*device);
0257     transformDevice(config, tmp, device, helper);
0258 }
0259 
0260 
0261 namespace {
0262 
0263 void transformDeviceImpl(const ToolTransformArgs &config,
0264                          KisPaintDeviceSP srcDevice,
0265                          KisPaintDeviceSP dstDevice,
0266                          KisProcessingVisitor::ProgressHelper *helper,
0267                          bool cropDst,
0268                          bool forceSubPixelTranslation)
0269 {
0270     if (config.mode() == ToolTransformArgs::WARP) {
0271         KoUpdaterPtr updater = helper->updater();
0272 
0273         KisWarpTransformWorker worker(config.warpType(),
0274                                       config.origPoints(),
0275                                       config.transfPoints(),
0276                                       config.alpha(),
0277                                       updater);
0278         worker.run(srcDevice, dstDevice);
0279     } else if (config.mode() == ToolTransformArgs::CAGE) {
0280         KoUpdaterPtr updater = helper->updater();
0281 
0282         dstDevice->makeCloneFromRough(srcDevice, srcDevice->extent());
0283 
0284         KisCageTransformWorker worker(srcDevice->region().boundingRect(),
0285                                       config.origPoints(),
0286                                       updater,
0287                                       config.pixelPrecision());
0288 
0289         worker.prepareTransform();
0290         worker.setTransformedCage(config.transfPoints());
0291         worker.run(srcDevice, dstDevice);
0292     } else if (config.mode() == ToolTransformArgs::LIQUIFY && config.liquifyWorker()) {
0293         KoUpdaterPtr updater = helper->updater();
0294         //FIXME:
0295         Q_UNUSED(updater);
0296 
0297         config.liquifyWorker()->run(srcDevice, dstDevice);
0298     } else if (config.mode() == ToolTransformArgs::MESH) {
0299         KoUpdaterPtr updater = helper->updater();
0300         //FIXME:
0301         Q_UNUSED(updater);
0302 
0303         dstDevice->clear();
0304         config.meshTransform()->transformMesh(srcDevice, dstDevice);
0305 
0306     } else {
0307         QVector3D transformedCenter;
0308         KoUpdaterPtr updater1 = helper->updater();
0309         KoUpdaterPtr updater2 = helper->updater();
0310 
0311         dstDevice->makeCloneFromRough(srcDevice, srcDevice->extent());
0312 
0313         KisTransformWorker transformWorker =
0314             KisTransformUtils::createTransformWorker(config, dstDevice, updater1, &transformedCenter);
0315 
0316         transformWorker.setForceSubPixelTranslation(forceSubPixelTranslation);
0317         transformWorker.run();
0318 
0319         KisPerspectiveTransformWorker::SampleType sampleType =
0320             config.filterId() == "NearestNeighbor" ?
0321             KisPerspectiveTransformWorker::NearestNeighbour :
0322             KisPerspectiveTransformWorker::Bilinear;
0323 
0324         if (config.mode() == ToolTransformArgs::FREE_TRANSFORM) {
0325             KisPerspectiveTransformWorker perspectiveWorker(dstDevice,
0326                                                             config.transformedCenter(),
0327                                                             config.aX(),
0328                                                             config.aY(),
0329                                                             config.cameraPos().z(),
0330                                                             cropDst,
0331                                                             updater2);
0332             perspectiveWorker.setForceSubPixelTranslation(forceSubPixelTranslation);
0333             perspectiveWorker.run(sampleType);
0334         } else if (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
0335             QTransform T =
0336                 QTransform::fromTranslate(config.transformedCenter().x(),
0337                                           config.transformedCenter().y());
0338 
0339             KisPerspectiveTransformWorker perspectiveWorker(dstDevice,
0340                                                             T.inverted() * config.flattenedPerspectiveTransform() * T,
0341                                                             cropDst,
0342                                                             updater2);
0343             perspectiveWorker.setForceSubPixelTranslation(forceSubPixelTranslation);
0344             perspectiveWorker.run(sampleType);
0345         }
0346     }
0347 }
0348 
0349 }
0350 
0351 void KisTransformUtils::transformDevice(const ToolTransformArgs &config,
0352                                         KisPaintDeviceSP srcDevice,
0353                                         KisPaintDeviceSP dstDevice,
0354                                         KisProcessingVisitor::ProgressHelper *helper)
0355 {
0356     transformDeviceImpl(config, srcDevice, dstDevice, helper, false, false);
0357 }
0358 
0359 void KisTransformUtils::transformDeviceWithCroppedDst(const ToolTransformArgs &config, KisPaintDeviceSP srcDevice, KisPaintDeviceSP dstDevice, KisProcessingVisitor::ProgressHelper *helper, bool forceSubPixelTranslation)
0360 {
0361     transformDeviceImpl(config, srcDevice, dstDevice, helper, true, forceSubPixelTranslation);
0362 }
0363 
0364 QRect KisTransformUtils::needRect(const ToolTransformArgs &config,
0365                                   const QRect &rc,
0366                                   const QRect &srcBounds)
0367 {
0368     QRect result = rc;
0369 
0370     if (config.mode() == ToolTransformArgs::WARP) {
0371         KisWarpTransformWorker worker(config.warpType(),
0372                                       config.origPoints(),
0373                                       config.transfPoints(),
0374                                       config.alpha(),
0375                                       0);
0376 
0377         result = worker.approxNeedRect(rc, srcBounds);
0378 
0379     } else if (config.mode() == ToolTransformArgs::CAGE) {
0380         KisCageTransformWorker worker(srcBounds,
0381                                       config.origPoints(),
0382                                       0,
0383                                       config.pixelPrecision());
0384         worker.setTransformedCage(config.transfPoints());
0385         result = worker.approxNeedRect(rc, srcBounds);
0386     } else if (config.mode() == ToolTransformArgs::LIQUIFY) {
0387         result = config.liquifyWorker() ?
0388             config.liquifyWorker()->approxNeedRect(rc, srcBounds) : rc;
0389     } else if (config.mode() == ToolTransformArgs::MESH) {
0390         result = config.meshTransform()->approxNeedRect(rc);
0391     } else {
0392         KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!");
0393     }
0394 
0395     return result;
0396 }
0397 
0398 QRect KisTransformUtils::changeRect(const ToolTransformArgs &config,
0399                                     const QRect &rc)
0400 {
0401     QRect result = rc;
0402 
0403     if (config.mode() == ToolTransformArgs::WARP) {
0404         KisWarpTransformWorker worker(config.warpType(),
0405                                       config.origPoints(),
0406                                       config.transfPoints(),
0407                                       config.alpha(),
0408                                       0);
0409 
0410         result = worker.approxChangeRect(rc);
0411 
0412     } else if (config.mode() == ToolTransformArgs::CAGE) {
0413         KisCageTransformWorker worker(rc,
0414                                       config.origPoints(),
0415                                       0,
0416                                       config.pixelPrecision());
0417 
0418         worker.setTransformedCage(config.transfPoints());
0419         result = worker.approxChangeRect(rc);
0420     } else if (config.mode() == ToolTransformArgs::LIQUIFY) {
0421         result = config.liquifyWorker() ?
0422             config.liquifyWorker()->approxChangeRect(rc) : rc;
0423     } else if (config.mode() == ToolTransformArgs::MESH) {
0424         result = config.meshTransform()->approxChangeRect(rc);
0425 
0426     } else {
0427         KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!");
0428     }
0429 
0430     return result;
0431 }
0432 
0433 KisTransformUtils::AnchorHolder::AnchorHolder(bool enabled, ToolTransformArgs *config)
0434     : m_enabled(enabled),
0435       m_config(config)
0436 {
0437     if (!m_enabled) return;
0438 
0439     m_staticPoint = m_config->originalCenter() + m_config->rotationCenterOffset();
0440 
0441     const KisTransformUtils::MatricesPack m(*m_config);
0442     m_oldStaticPointInView = m.finalTransform().map(m_staticPoint);
0443 }
0444 
0445 KisTransformUtils::AnchorHolder::~AnchorHolder() {
0446     if (!m_enabled) return;
0447 
0448     const KisTransformUtils::MatricesPack m(*m_config);
0449     const QPointF newStaticPointInView = m.finalTransform().map(m_staticPoint);
0450 
0451     const QPointF diff = m_oldStaticPointInView - newStaticPointInView;
0452 
0453     m_config->setTransformedCenter(m_config->transformedCenter() + diff);
0454 }
0455 
0456 void KisTransformUtils::setDefaultWarpPoints(int pointsPerLine,
0457                                              const TransformTransactionProperties *transaction,
0458                                              ToolTransformArgs *config)
0459 {
0460     static const int DEFAULT_POINTS_PER_LINE = 3;
0461 
0462     if (pointsPerLine < 0) {
0463         pointsPerLine = DEFAULT_POINTS_PER_LINE;
0464     }
0465 
0466     int nbPoints = pointsPerLine * pointsPerLine;
0467     QVector<QPointF> origPoints(nbPoints);
0468     QVector<QPointF> transfPoints(nbPoints);
0469     qreal gridSpaceX, gridSpaceY;
0470 
0471     if (nbPoints == 1) {
0472         //there is actually no grid
0473         origPoints[0] = transaction->originalCenterGeometric();
0474         transfPoints[0] = transaction->originalCenterGeometric();
0475     }
0476     else if (nbPoints > 1) {
0477         gridSpaceX = transaction->originalRect().width() / (pointsPerLine - 1);
0478         gridSpaceY = transaction->originalRect().height() / (pointsPerLine - 1);
0479         double y = transaction->originalRect().top();
0480         for (int i = 0; i < pointsPerLine; ++i) {
0481             double x = transaction->originalRect().left();
0482             for (int j = 0 ; j < pointsPerLine; ++j) {
0483                 origPoints[i * pointsPerLine + j] = QPointF(x, y);
0484                 transfPoints[i * pointsPerLine + j] = QPointF(x, y);
0485                 x += gridSpaceX;
0486             }
0487             y += gridSpaceY;
0488         }
0489     }
0490 
0491     config->setDefaultPoints(nbPoints > 0);
0492     config->setPoints(origPoints, transfPoints);
0493 }
0494 
0495 ToolTransformArgs KisTransformUtils::resetArgsForMode(ToolTransformArgs::TransformMode mode,
0496                                                       const QString &filterId,
0497                                                       const TransformTransactionProperties &transaction,
0498                                                       KisPaintDeviceSP externalSource)
0499 {
0500     ToolTransformArgs args;
0501 
0502     args.setOriginalCenter(transaction.originalCenterGeometric());
0503     args.setTransformedCenter(transaction.originalCenterGeometric());
0504     args.setFilterId(filterId);
0505     args.setExternalSource(externalSource);
0506 
0507     if (mode == ToolTransformArgs::FREE_TRANSFORM) {
0508         args.setMode(ToolTransformArgs::FREE_TRANSFORM);
0509     } else if (mode == ToolTransformArgs::WARP) {
0510         args.setMode(ToolTransformArgs::WARP);
0511         KisTransformUtils::setDefaultWarpPoints(-1, &transaction, &args);
0512         args.setEditingTransformPoints(false);
0513     } else if (mode == ToolTransformArgs::CAGE) {
0514         args.setMode(ToolTransformArgs::CAGE);
0515         args.setEditingTransformPoints(true);
0516     } else if (mode == ToolTransformArgs::LIQUIFY) {
0517         args.setMode(ToolTransformArgs::LIQUIFY);
0518         const QRect srcRect = transaction.originalRect().toAlignedRect();
0519         if (!srcRect.isEmpty()) {
0520             args.initLiquifyTransformMode(srcRect);
0521         }
0522     } else if (mode == ToolTransformArgs::MESH) {
0523         args.setMode(ToolTransformArgs::MESH);
0524         const QRect srcRect = transaction.originalRect().toAlignedRect();
0525         if (!srcRect.isEmpty()) {
0526             *args.meshTransform() = KisBezierTransformMesh(QRectF(srcRect));
0527         }
0528     } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) {
0529         args.setMode(ToolTransformArgs::PERSPECTIVE_4POINT);
0530     }
0531 
0532     return args;
0533 }
0534 
0535 bool KisTransformUtils::shouldRestartStrokeOnModeChange(ToolTransformArgs::TransformMode oldMode, ToolTransformArgs::TransformMode newMode, KisNodeList processedNodes)
0536 {
0537     bool hasExternalLayers = false;
0538     Q_FOREACH (KisNodeSP node, processedNodes) {
0539         if (node->inherits("KisShapeLayer")) {
0540             hasExternalLayers = true;
0541             break;
0542         }
0543     }
0544 
0545     bool result = false;
0546 
0547     if (hasExternalLayers) {
0548         result =
0549             (oldMode == ToolTransformArgs::FREE_TRANSFORM) !=
0550             (newMode == ToolTransformArgs::FREE_TRANSFORM);
0551     }
0552 
0553     return result;
0554 }
0555 
0556 void KisTransformUtils::transformAndMergeDevice(const ToolTransformArgs &config,
0557                                                 KisPaintDeviceSP src,
0558                                                 KisPaintDeviceSP dst,
0559                                                 KisProcessingVisitor::ProgressHelper *helper)
0560 {
0561     KoUpdaterPtr mergeUpdater = helper->updater();
0562 
0563     KisPaintDeviceSP tmp = new KisPaintDevice(src->colorSpace());
0564     tmp->prepareClone(src);
0565 
0566     KisTransformUtils::transformDevice(config, src, tmp, helper);
0567 
0568     QRect mergeRect = tmp->extent();
0569     KisPainter painter(dst);
0570     painter.setProgress(mergeUpdater);
0571     painter.bitBlt(mergeRect.topLeft(), tmp, mergeRect);
0572     painter.end();
0573 }
0574 
0575 struct TransformExtraData : public KUndo2CommandExtraData
0576 {
0577     ToolTransformArgs savedTransformArgs;
0578     KisNodeList rootNodes;
0579     KisNodeList transformedNodes;
0580     int transformedTime = -1;
0581 
0582     KUndo2CommandExtraData* clone() const override {
0583         return new TransformExtraData(*this);
0584     }
0585 };
0586 
0587 void KisTransformUtils::postProcessToplevelCommand(KUndo2Command *command, const ToolTransformArgs &args, KisNodeList rootNodes, KisNodeList processedNodes, int currentTime, const KisSavedMacroCommand *overriddenCommand)
0588 {
0589     TransformExtraData *data = new TransformExtraData();
0590     data->savedTransformArgs = args;
0591     data->rootNodes = rootNodes;
0592     data->transformedNodes = processedNodes;
0593     data->transformedTime = currentTime;
0594 
0595     command->setExtraData(data);
0596 
0597     KisSavedMacroCommand *macroCommand = dynamic_cast<KisSavedMacroCommand*>(command);
0598     KIS_SAFE_ASSERT_RECOVER_NOOP(macroCommand);
0599 
0600     if (overriddenCommand && macroCommand) {
0601         macroCommand->setOverrideInfo(overriddenCommand, {});
0602     }
0603 }
0604 
0605 bool KisTransformUtils::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeList *rootNodes, KisNodeList *transformedNodes, int *oldTime)
0606 {
0607     const TransformExtraData *data = dynamic_cast<const TransformExtraData*>(command->extraData());
0608 
0609     if (data) {
0610         *args = data->savedTransformArgs;
0611         *rootNodes = data->rootNodes;
0612         *transformedNodes = data->transformedNodes;
0613         *oldTime = data->transformedTime;
0614     }
0615 
0616     return bool(data);
0617 }
0618 
0619 KisNodeSP KisTransformUtils::tryOverrideRootToTransformMask(KisNodeSP root)
0620 {
0621     // we search for masks only at the first level of hierarchy,
0622     // all other masks are just ignored.
0623 
0624     KisNodeSP node = root->firstChild();
0625 
0626     while (node) {
0627         if (node->inherits("KisTransformMask") && node->isEditable()) {
0628             root = node;
0629             break;
0630         }
0631 
0632         node = node->nextSibling();
0633     }
0634 
0635     return root;
0636 }
0637 
0638 int KisTransformUtils::fetchCurrentImageTime(KisNodeList rootNodes)
0639 {
0640     Q_FOREACH(KisNodeSP node, rootNodes) {
0641         /**
0642          * We cannot just use projection's default bounds, because masks don't have
0643          * any projection
0644          */
0645         if (node && node->image()) {
0646             return node->image()->animationInterface()->currentTime();
0647         }
0648     }
0649     return -1;
0650 }
0651 
0652 QList<KisNodeSP> KisTransformUtils::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeList rootNodes, bool isExternalSourcePresent, KisSelectionSP selection)
0653 {
0654     QList<KisNodeSP> result;
0655 
0656     Q_FOREACH (KisNodeSP root, rootNodes) {
0657         bool hasTransformMaskDescendant =
0658             KisLayerUtils::recursiveFindNode(root, [root] (KisNodeSP node) {
0659                 return node != root && node->visible() && node->inherits("KisTransformMask");
0660             });
0661 
0662         /// Cannot transform nodes with visible transform masks inside,
0663         /// this situation should have been caught either in
0664         /// tryOverrideRootToTransformMask or in the transform tool
0665         /// stroke initialization routine.
0666         KIS_SAFE_ASSERT_RECOVER_NOOP(!hasTransformMaskDescendant);
0667 
0668         KisNodeSP selectionNode = selection ? selection->parentNode() : 0;
0669 
0670         auto fetchFunc =
0671             [&result, mode, root, selectionNode] (KisNodeSP node) {
0672             if (node->isEditable(node == root) &&
0673                     (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) &&
0674                     !node->inherits("KisFileLayer") &&
0675                     !node->inherits("KisColorizeMask") &&
0676                     (!node->inherits("KisTransformMask") || node == root) &&
0677                     (!selectionNode || node != selectionNode)) {
0678 
0679                     result << node;
0680                 }
0681         };
0682 
0683         if (isExternalSourcePresent) {
0684             fetchFunc(root);
0685         } else {
0686             KisLayerUtils::recursiveApplyNodes(root, fetchFunc);
0687         }
0688     }
0689 
0690     return result;
0691 }
0692 
0693 bool KisTransformUtils::tryInitArgsFromNode(KisNodeList rootNodes, ToolTransformArgs *args)
0694 {
0695     bool result = false;
0696 
0697     Q_FOREACH(KisNodeSP node, rootNodes) {
0698         if (KisTransformMaskSP mask =
0699             dynamic_cast<KisTransformMask*>(node.data())) {
0700 
0701             KisTransformMaskParamsInterfaceSP savedParams =
0702                 mask->transformParams();
0703 
0704             KisTransformMaskAdapter *adapter =
0705                 dynamic_cast<KisTransformMaskAdapter*>(savedParams.data());
0706 
0707             if (adapter && adapter->isInitialized()) {
0708                 *args = *adapter->transformArgs();
0709                 result = true;
0710             }
0711         }
0712     }
0713 
0714     return result;
0715 }
0716 
0717 bool KisTransformUtils::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *outArgs,
0718                                                        ToolTransformArgs::TransformMode mode,
0719                                                        KisNodeList currentNodes,
0720                                                        KisNodeList selectedNodes,
0721                                                        KisStrokeUndoFacade *undoFacade,
0722                                                        int currentTime,
0723                                                        QVector<KisStrokeJobData *> *undoJobs,
0724                                                        const KisSavedMacroCommand **overriddenCommand)
0725 {
0726     bool result = false;
0727 
0728     const KUndo2Command *lastCommand = undoFacade->lastExecutedCommand();
0729     KisNodeList oldRootNodes;
0730     KisNodeList oldTransformedNodes;
0731     int oldTime = -1;
0732 
0733     ToolTransformArgs args;
0734 
0735     if (lastCommand &&
0736         KisTransformUtils::fetchArgsFromCommand(lastCommand, &args, &oldRootNodes, &oldTransformedNodes, &oldTime) &&
0737         args.mode() == mode &&
0738         oldRootNodes == currentNodes &&
0739         oldTime == currentTime) {
0740 
0741         if (KritaUtils::compareListsUnordered(oldTransformedNodes, selectedNodes)) {
0742             args.saveContinuedState();
0743 
0744             *outArgs = args;
0745 
0746             const KisSavedMacroCommand *command = dynamic_cast<const KisSavedMacroCommand*>(lastCommand);
0747             KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(command, false);
0748 
0749             // the jobs are fetched as !shouldGoToHistory,
0750             // so there is no need to put them into
0751             // m_s->skippedWhileMergeCommands
0752             command->getCommandExecutionJobs(undoJobs, true, false);
0753             *overriddenCommand = command;
0754 
0755             result = true;
0756         }
0757     }
0758 
0759     return result;
0760 }