File indexing completed on 2024-05-12 15:58:45

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include <KoIcon.h>
0009 #include <kis_icon.h>
0010 #include <KoCompositeOpRegistry.h>
0011 
0012 #include "kis_layer.h"
0013 #include "kis_transform_mask.h"
0014 #include "filter/kis_filter.h"
0015 #include "filter/kis_filter_configuration.h"
0016 #include "filter/kis_filter_registry.h"
0017 #include "kis_selection.h"
0018 #include "kis_processing_information.h"
0019 #include "kis_node.h"
0020 #include "kis_node_visitor.h"
0021 #include "kis_processing_visitor.h"
0022 #include "kis_node_progress_proxy.h"
0023 #include "kis_transaction.h"
0024 #include "kis_painter.h"
0025 
0026 #include "kis_busy_progress_indicator.h"
0027 #include "kis_perspectivetransform_worker.h"
0028 #include "kis_transform_mask_params_interface.h"
0029 #include "kis_transform_mask_params_factory_registry.h"
0030 #include "kis_recalculate_transform_mask_job.h"
0031 #include "kis_thread_safe_signal_compressor.h"
0032 #include "kis_algebra_2d.h"
0033 #include "kis_safe_transform.h"
0034 #include "kis_keyframe_channel.h"
0035 #include "kis_raster_keyframe_channel.h"
0036 #include "kis_scalar_keyframe_channel.h"
0037 
0038 #include "kis_image_config.h"
0039 #include "kis_lod_capable_layer_offset.h"
0040 
0041 //#include "kis_paint_device_debug_utils.h"
0042 //#define DEBUG_RENDERING
0043 //#define DUMP_RECT QRect(0,0,512,512)
0044 
0045 #define UPDATE_DELAY 3000 /*ms */
0046 
0047 struct Q_DECL_HIDDEN KisTransformMask::Private
0048 {
0049     Private(KisImageSP image)
0050         : worker(0, QTransform(), true, 0),
0051           staticCacheValid(false),
0052           recalculatingStaticImage(false),
0053           offset(new KisDefaultBounds(image)),
0054           updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE),
0055           offBoundsReadArea(0.5)
0056     {
0057     }
0058 
0059     Private(const Private &rhs)
0060         : worker(rhs.worker),
0061           params(rhs.params->clone()),
0062           staticCacheValid(rhs.staticCacheValid),
0063           recalculatingStaticImage(rhs.recalculatingStaticImage),
0064           staticCacheDevice(nullptr),
0065           offset(rhs.offset),
0066           updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE),
0067           offBoundsReadArea(rhs.offBoundsReadArea)
0068     {
0069     }
0070 
0071     void reloadParameters()
0072     {
0073         QTransform affineTransform;
0074         if (params->isAffine()) {
0075             affineTransform = params->finalAffineTransform();
0076         }
0077         worker.setForwardTransform(affineTransform);
0078 
0079         params->clearChangedFlag();
0080         staticCacheValid = false;
0081     }
0082 
0083     KisPerspectiveTransformWorker worker;
0084     KisTransformMaskParamsInterfaceSP params;
0085 
0086     bool staticCacheValid;
0087     bool recalculatingStaticImage;
0088     KisPaintDeviceSP staticCacheDevice;
0089     bool staticCacheIsOverridden = false;
0090 
0091     KisLodCapableLayerOffset offset;
0092 
0093     KisThreadSafeSignalCompressor updateSignalCompressor;
0094     qreal offBoundsReadArea;
0095 };
0096 
0097 
0098 KisTransformMask::KisTransformMask(KisImageWSP image, const QString &name)
0099     : KisEffectMask(image, name),
0100       m_d(new Private(image))
0101 {
0102     setTransformParams(
0103         KisTransformMaskParamsInterfaceSP(
0104             new KisDumbTransformMaskParams()));
0105 
0106     connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
0107     connect(this, SIGNAL(sigInternalForceStaticImageUpdate()), SLOT(slotInternalForceStaticImageUpdate()));
0108     m_d->offBoundsReadArea = KisImageConfig(true).transformMaskOffBoundsReadArea();
0109     setSupportsLodMoves(false);
0110 }
0111 
0112 KisTransformMask::~KisTransformMask()
0113 {
0114 }
0115 
0116 KisTransformMask::KisTransformMask(const KisTransformMask& rhs)
0117     : KisEffectMask(rhs),
0118       m_d(new Private(*rhs.m_d))
0119 {
0120     connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
0121 
0122     KisAnimatedTransformParamsInterface* rhsAniTransform = dynamic_cast<KisAnimatedTransformParamsInterface*>(rhs.m_d->params.data());
0123     KisAnimatedTransformParamsInterface* aniTransform = dynamic_cast<KisAnimatedTransformParamsInterface*>(m_d->params.data());
0124     if(rhsAniTransform && aniTransform) {
0125         QList<KisKeyframeChannel*> chans;
0126         chans = aniTransform->copyChannelsFrom(rhsAniTransform);
0127         foreach( KisKeyframeChannel* chan, chans) {
0128             addKeyframeChannel(chan);
0129         }
0130     }
0131 }
0132 
0133 KisPaintDeviceSP KisTransformMask::paintDevice() const
0134 {
0135     return 0;
0136 }
0137 
0138 QIcon KisTransformMask::icon() const
0139 {
0140     return KisIconUtils::loadIcon("transformMask");
0141 }
0142 
0143 void KisTransformMask::setTransformParams(KisTransformMaskParamsInterfaceSP params)
0144 {
0145     KIS_ASSERT_RECOVER(params) {
0146         params = KisTransformMaskParamsInterfaceSP(
0147             new KisDumbTransformMaskParams());
0148     }
0149 
0150     m_d->params = params;
0151 
0152     m_d->reloadParameters();
0153 
0154     m_d->updateSignalCompressor.stop();
0155 }
0156 
0157 KisTransformMaskParamsInterfaceSP KisTransformMask::transformParams() const
0158 {
0159     return m_d->params;
0160 }
0161 
0162 void KisTransformMask::slotDelayedStaticUpdate()
0163 {
0164     /**
0165      * The mask might have been deleted from the layers stack in the
0166      * meanwhile. Just ignore the updates in the case.
0167      */
0168 
0169     KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
0170     if (!parentLayer) return;
0171 
0172     KisImageSP image = parentLayer->image();
0173     if (image) {
0174         image->addSpontaneousJob(new KisRecalculateTransformMaskJob(this));
0175     }
0176 }
0177 
0178 KisPaintDeviceSP KisTransformMask::buildPreviewDevice()
0179 {
0180     /**
0181      * Note: this function must be called from within the scheduler's
0182      * context. We are accessing parent's updateProjection(), which
0183      * is not entirely safe. The calling job must ensure it is the
0184      * only job running.
0185      */
0186 
0187     KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
0188     KIS_ASSERT_RECOVER(parentLayer) { return new KisPaintDevice(colorSpace()); }
0189 
0190     KisPaintDeviceSP device =
0191         new KisPaintDevice(parentLayer->original()->colorSpace());
0192     device->setDefaultBounds(parentLayer->original()->defaultBounds());
0193 
0194     QRect requestedRect = parentLayer->original()->exactBounds();
0195     parentLayer->buildProjectionUpToNode(device, this, requestedRect);
0196 
0197     return device;
0198 }
0199 
0200 KisPaintDeviceSP KisTransformMask::buildSourcePreviewDevice()
0201 {
0202     /**
0203      * Note: this function must be called from within the scheduler's
0204      * context. We are accessing parent's updateProjection(), which
0205      * is not entirely safe. The calling job must ensure it is the
0206      * only job running.
0207      */
0208 
0209     KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
0210     KIS_ASSERT_RECOVER(parentLayer) { return new KisPaintDevice(colorSpace()); }
0211 
0212     KisPaintDeviceSP device =
0213         new KisPaintDevice(parentLayer->original()->colorSpace());
0214     device->setDefaultBounds(parentLayer->original()->defaultBounds());
0215 
0216     QRect requestedRect = parentLayer->original()->exactBounds();
0217 
0218     KisNodeSP prevSibling = this->prevSibling();
0219     if (prevSibling) {
0220         parentLayer->buildProjectionUpToNode(device, prevSibling, requestedRect);
0221     } else {
0222         requestedRect = parentLayer->outgoingChangeRect(requestedRect);
0223         parentLayer->copyOriginalToProjection(parentLayer->original(), device, requestedRect);
0224     }
0225 
0226     return device;
0227 }
0228 
0229 void KisTransformMask::overrideStaticCacheDevice(KisPaintDeviceSP device)
0230 {
0231     m_d->staticCacheDevice->clear();
0232 
0233     if (device) {
0234         const QRect rc = device->extent();
0235         KisPainter::copyAreaOptimized(rc.topLeft(), device, m_d->staticCacheDevice, rc);
0236     }
0237 
0238     m_d->staticCacheValid = bool(device);
0239     m_d->staticCacheIsOverridden = bool(device);
0240 }
0241 
0242 void KisTransformMask::recaclulateStaticImage()
0243 {
0244     /**
0245      * Note: this function must be called from within the scheduler's
0246      * context. We are accessing parent's updateProjection(), which
0247      * is not entirely safe.
0248      */
0249 
0250     KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
0251     KIS_SAFE_ASSERT_RECOVER_RETURN(parentLayer);
0252 
0253     // It might happen that the mask became invisible in the meantime
0254     // and the projection has become disabled. That mush be "impossible"
0255     // situation, hence assert.
0256     KIS_SAFE_ASSERT_RECOVER_RETURN(parentLayer->projection() != parentLayer->paintDevice());
0257 
0258     if (!m_d->staticCacheDevice ||
0259         *m_d->staticCacheDevice->colorSpace() != *parentLayer->original()->colorSpace()) {
0260 
0261         m_d->staticCacheDevice =
0262             new KisPaintDevice(parentLayer->original()->colorSpace());
0263         m_d->staticCacheDevice->setDefaultBounds(parentLayer->original()->defaultBounds());
0264     }
0265 
0266     m_d->recalculatingStaticImage = true;
0267     /**
0268      * updateProjection() is assuming that the requestedRect takes
0269      * into account all the change rects of all the masks. Usually,
0270      * this work is done by the walkers.
0271      */
0272     QRect requestedRect =
0273         parentLayer->changeRect(parentLayer->original()->exactBounds());
0274 
0275     // force reset parent layer's projection, because we might have changed
0276     // our mask parameters and going to write to some other area
0277     parentLayer->projection()->clear();
0278 
0279     /**
0280      * Here we use updateProjection() to regenerate the projection of
0281      * the layer and after that a special update call (no-filthy) will
0282      * be issued to pass the changed further through the stack.
0283      */
0284     parentLayer->updateProjection(requestedRect, this);
0285     m_d->recalculatingStaticImage = false;
0286 
0287     m_d->staticCacheValid = true;
0288 }
0289 
0290 QRect KisTransformMask::decorateRect(KisPaintDeviceSP &src,
0291                                      KisPaintDeviceSP &dst,
0292                                      const QRect & rc,
0293                                      PositionToFilthy maskPos) const
0294 {
0295     Q_ASSERT_X(src != dst, "KisTransformMask::decorateRect",
0296                "src must be != dst, because we can't create transactions "
0297                "during merge, as it breaks reentrancy");
0298 
0299     KIS_ASSERT_RECOVER(m_d->params) { return rc; }
0300 
0301     if (m_d->params->isHidden()) return rc;
0302     KIS_ASSERT_RECOVER_NOOP(maskPos == N_FILTHY ||
0303                             maskPos == N_ABOVE_FILTHY ||
0304                             maskPos == N_BELOW_FILTHY);
0305 
0306     if (m_d->params->hasChanged()) m_d->reloadParameters();
0307 
0308     if (!m_d->staticCacheIsOverridden &&
0309         !m_d->recalculatingStaticImage &&
0310         (maskPos == N_FILTHY || maskPos == N_ABOVE_FILTHY)) {
0311 
0312         m_d->staticCacheValid = false;
0313         m_d->updateSignalCompressor.start();
0314     }
0315 
0316     if (m_d->recalculatingStaticImage) {
0317         m_d->staticCacheDevice->clear();
0318         m_d->params->transformDevice(const_cast<KisTransformMask*>(this), src, m_d->staticCacheDevice);
0319         QRect updatedRect = m_d->staticCacheDevice->extent();
0320         KisPainter::copyAreaOptimized(updatedRect.topLeft(), m_d->staticCacheDevice, dst, updatedRect);
0321 
0322 #ifdef DEBUG_RENDERING
0323         qDebug() << "Recalculate" << name() << ppVar(src->exactBounds()) << ppVar(dst->exactBounds()) << ppVar(rc);
0324         KIS_DUMP_DEVICE_2(src, DUMP_RECT, "recalc_src", "dd");
0325         KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "recalc_dst", "dd");
0326 #endif /* DEBUG_RENDERING */
0327 
0328     } else if (!m_d->staticCacheValid && !m_d->staticCacheIsOverridden && m_d->params->isAffine()) {
0329         m_d->worker.runPartialDst(src, dst, rc);
0330 
0331 #ifdef DEBUG_RENDERING
0332         qDebug() << "Partial" << name() << ppVar(src->exactBounds()) << ppVar(src->extent()) << ppVar(dst->exactBounds()) << ppVar(dst->extent()) << ppVar(rc);
0333         KIS_DUMP_DEVICE_2(src, DUMP_RECT, "partial_src", "dd");
0334         KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "partial_dst", "dd");
0335 #endif /* DEBUG_RENDERING */
0336 
0337     } else if ((m_d->staticCacheValid || m_d->staticCacheIsOverridden) && m_d->staticCacheDevice) {
0338         KisPainter::copyAreaOptimized(rc.topLeft(), m_d->staticCacheDevice, dst, rc);
0339 
0340 #ifdef DEBUG_RENDERING
0341         qDebug() << "Fetch" << name() << ppVar(src->exactBounds()) << ppVar(dst->exactBounds()) << ppVar(rc);
0342         KIS_DUMP_DEVICE_2(src, DUMP_RECT, "fetch_src", "dd");
0343         KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "fetch_dst", "dd");
0344 #endif /* DEBUG_RENDERING */
0345 
0346     }
0347 
0348     KIS_ASSERT_RECOVER_NOOP(this->busyProgressIndicator());
0349     this->busyProgressIndicator()->update();
0350 
0351     return rc;
0352 }
0353 
0354 bool KisTransformMask::accept(KisNodeVisitor &v)
0355 {
0356     return v.visit(this);
0357 }
0358 
0359 void KisTransformMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
0360 {
0361     return visitor.visit(this, undoAdapter);
0362 }
0363 
0364 QRect KisTransformMask::changeRect(const QRect &rect, PositionToFilthy pos) const
0365 {
0366     Q_UNUSED(pos);
0367 
0368     /**
0369      * FIXME: This check of the emptiness should be done
0370      * on the higher/lower level
0371      */
0372     if (rect.isEmpty()) return rect;
0373 
0374     QRect changeRect = rect;
0375 
0376     if (m_d->params->isAffine()) {
0377         QRect bounds;
0378         QRect interestRect;
0379         KisNodeSP parentNode = parent();
0380 
0381         if (parentNode) {
0382             bounds = parentNode->original()->defaultBounds()->bounds();
0383             interestRect = parentNode->original()->extent();
0384         } else {
0385             bounds = QRect(0,0,777,777);
0386             interestRect = QRect(0,0,888,888);
0387             warnKrita << "WARNING: transform mask has no parent (change rect)."
0388                       << "Cannot run safe transformations."
0389                       << "Will limit bounds to" << ppVar(bounds);
0390         }
0391 
0392         const QRect limitingRect = KisAlgebra2D::blowRect(bounds, m_d->offBoundsReadArea);
0393 
0394         if (m_d->params->hasChanged()) m_d->reloadParameters();
0395         KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect);
0396         changeRect = transform.mapRectForward(rect);
0397     } else {
0398         QRect interestRect;
0399         interestRect = parent() ? parent()->original()->extent() : QRect();
0400 
0401         changeRect = m_d->params->nonAffineChangeRect(rect);
0402     }
0403 
0404     return changeRect;
0405 }
0406 
0407 QRect KisTransformMask::needRect(const QRect& rect, PositionToFilthy pos) const
0408 {
0409     Q_UNUSED(pos);
0410 
0411     /**
0412      * FIXME: This check of the emptiness should be done
0413      * on the higher/lower level
0414      */
0415     if (rect.isEmpty()) return rect;
0416     if (!m_d->params->isAffine()) return rect;
0417 
0418     QRect bounds;
0419     QRect interestRect;
0420     KisNodeSP parentNode = parent();
0421 
0422     if (parentNode) {
0423         bounds = parentNode->original()->defaultBounds()->bounds();
0424         interestRect = parentNode->original()->extent();
0425     } else {
0426         bounds = QRect(0,0,777,777);
0427         interestRect = QRect(0,0,888,888);
0428         warnKrita << "WARNING: transform mask has no parent (need rect)."
0429                    << "Cannot run safe transformations."
0430                    << "Will limit bounds to" << ppVar(bounds);
0431     }
0432 
0433     QRect needRect = rect;
0434 
0435     if (m_d->params->isAffine()) {
0436         const QRect limitingRect = KisAlgebra2D::blowRect(bounds, m_d->offBoundsReadArea);
0437 
0438         if (m_d->params->hasChanged()) m_d->reloadParameters();
0439         KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect);
0440         needRect = transform.mapRectBackward(rect);
0441 
0442         /**
0443          * When sampling affine transformations we use KisRandomSubAccessor,
0444          * which uses bilinear interpolation for calculating pixels. Therefore,
0445          * we need to extend the sides of the need rect by one pixel.
0446          */
0447         needRect = kisGrowRect(needRect, 1);
0448 
0449     } else {
0450         needRect = m_d->params->nonAffineNeedRect(rect, interestRect);
0451     }
0452 
0453     return needRect;
0454 }
0455 
0456 QRect KisTransformMask::extent() const
0457 {
0458     QRect rc = KisMask::extent();
0459 
0460     QRect partialChangeRect;
0461     QRect existentProjection;
0462     KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
0463     if (parentLayer) {
0464         partialChangeRect = parentLayer->partialChangeRect(const_cast<KisTransformMask*>(this), rc);
0465         existentProjection = parentLayer->projection()->extent();
0466     }
0467 
0468     return changeRect(partialChangeRect) | existentProjection;
0469 }
0470 
0471 QRect KisTransformMask::exactBounds() const
0472 {
0473     QRect existentProjection;
0474     KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
0475     if (parentLayer) {
0476         existentProjection = parentLayer->projection()->exactBounds();
0477 
0478         /* Take into account multiple keyframes... */
0479         if (parentLayer->original() && parentLayer->original()->defaultBounds() && parentLayer->original()->keyframeChannel()) {
0480             Q_FOREACH( const int& time, parentLayer->original()->keyframeChannel()->allKeyframeTimes() ) {
0481                 KisRasterKeyframeSP keyframe = parentLayer->original()->keyframeChannel()->keyframeAt<KisRasterKeyframe>(time);
0482                 existentProjection |= keyframe->contentBounds();
0483             }
0484         }
0485     }
0486 
0487     if (isAnimated()) {
0488         existentProjection |= changeRect(image()->bounds());
0489     }
0490 
0491     return changeRect(sourceDataBounds()) | existentProjection;
0492 }
0493 
0494 QRect KisTransformMask::sourceDataBounds() const
0495 {
0496     /// NOTE: we should avoid including parent layer's projection's
0497     ///       extent into the source of changeRect calculation, because
0498     ///       that is exactly what partialChangeRect() calculates.
0499 
0500     QRect partialChangeRect;
0501     KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
0502     if (parentLayer) {
0503         const QRect rc = parentLayer->original()->exactBounds();
0504         partialChangeRect = parentLayer->partialChangeRect(const_cast<KisTransformMask*>(this), rc);
0505     }
0506 
0507     return partialChangeRect;
0508 }
0509 
0510 qint32 KisTransformMask::x() const
0511 {
0512     return m_d->offset.x();
0513 }
0514 
0515 qint32 KisTransformMask::y() const
0516 {
0517     return m_d->offset.y();
0518 }
0519 
0520 void KisTransformMask::setX(qint32 x)
0521 {
0522     m_d->params->translateSrcAndDst(QPointF(x - this->x(), 0));
0523     setTransformParams(m_d->params);
0524     m_d->offset.setX(x);
0525 }
0526 
0527 void KisTransformMask::setY(qint32 y)
0528 {
0529     m_d->params->translateSrcAndDst(QPointF(0, y - this->y()));
0530     setTransformParams(m_d->params);
0531     m_d->offset.setY(y);
0532 }
0533 
0534 void KisTransformMask::forceUpdateTimedNode()
0535 {
0536     if (hasPendingTimedUpdates()) {
0537         KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->staticCacheValid);
0538 
0539         m_d->updateSignalCompressor.stop();
0540         slotDelayedStaticUpdate();
0541     }
0542 }
0543 
0544 bool KisTransformMask::hasPendingTimedUpdates() const
0545 {
0546     return m_d->updateSignalCompressor.isActive();
0547 }
0548 
0549 void KisTransformMask::threadSafeForceStaticImageUpdate()
0550 {
0551     emit sigInternalForceStaticImageUpdate();
0552 }
0553 
0554 void KisTransformMask::syncLodCache()
0555 {
0556     m_d->offset.syncLodOffset();
0557     KisEffectMask::syncLodCache();
0558 }
0559 
0560 KisPaintDeviceList KisTransformMask::getLodCapableDevices() const
0561 {
0562     KisPaintDeviceList devices;
0563     devices += KisEffectMask::getLodCapableDevices();
0564     if (m_d->staticCacheDevice) {
0565         devices << m_d->staticCacheDevice;
0566     }
0567     return devices;
0568 }
0569 
0570 void KisTransformMask::slotInternalForceStaticImageUpdate()
0571 {
0572     m_d->updateSignalCompressor.stop();
0573     slotDelayedStaticUpdate();
0574 }
0575 
0576 KisKeyframeChannel *KisTransformMask::requestKeyframeChannel(const QString &id)
0577 {
0578     if (id == KisKeyframeChannel::PositionX.id() ||
0579         id == KisKeyframeChannel::PositionY.id() ||
0580         id == KisKeyframeChannel::ScaleX.id() ||
0581         id == KisKeyframeChannel::ScaleY.id() ||
0582         id == KisKeyframeChannel::ShearX.id() ||
0583         id == KisKeyframeChannel::ShearY.id() ||
0584         id == KisKeyframeChannel::RotationX.id() ||
0585         id == KisKeyframeChannel::RotationY.id() ||
0586         id == KisKeyframeChannel::RotationZ.id()) {
0587 
0588         KisAnimatedTransformParamsInterface *animatedParams = dynamic_cast<KisAnimatedTransformParamsInterface*>(m_d->params.data());
0589 
0590         if (!animatedParams) {
0591             auto converted = KisTransformMaskParamsFactoryRegistry::instance()->animateParams(m_d->params, this);
0592             if (converted.isNull()) return KisEffectMask::requestKeyframeChannel(id);
0593             m_d->params = converted;
0594             animatedParams = dynamic_cast<KisAnimatedTransformParamsInterface*>(converted.data());
0595         }
0596 
0597         KisKeyframeChannel *channel = animatedParams->requestKeyframeChannel(id, this);
0598         if (channel) {
0599             channel->setNode(this);
0600             channel->setDefaultBounds(new KisDefaultBounds(this->image()));
0601             return channel;
0602         }
0603     }
0604 
0605     return KisEffectMask::requestKeyframeChannel(id);
0606 }
0607 
0608 bool KisTransformMask::supportsKeyframeChannel(const QString &id)
0609 {
0610     if (id == KisKeyframeChannel::PositionX.id() ||
0611         id == KisKeyframeChannel::PositionY.id() ||
0612         id == KisKeyframeChannel::ScaleX.id() ||
0613         id == KisKeyframeChannel::ScaleY.id() ||
0614         id == KisKeyframeChannel::ShearX.id() ||
0615         id == KisKeyframeChannel::ShearY.id() ||
0616         id == KisKeyframeChannel::RotationX.id() ||
0617         id == KisKeyframeChannel::RotationY.id() ||
0618             id == KisKeyframeChannel::RotationZ.id()) {
0619         return true;
0620     }
0621     else if (id == KisKeyframeChannel::Opacity.id()) {
0622         return false;
0623     }
0624 
0625     return KisEffectMask::supportsKeyframeChannel(id);
0626 }
0627