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