File indexing completed on 2024-06-23 04:28:30
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 }