File indexing completed on 2025-02-23 04:09:02
0001 /* 0002 * SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com> 0003 * SPDX-FileCopyrightText: 2021 Eoin O'Neill <eoinoneill1991@gmail.com> 0004 * SPDX-FileCopyrightText: 2021 Emmet O'Neill <emmetoneill.pdx@gmail.com> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "KisCanvasAnimationState.h" 0010 0011 #include <QTimer> 0012 #include <QtMath> 0013 0014 #include "kis_global.h" 0015 #include "kis_algebra_2d.h" 0016 0017 #include "kis_config.h" 0018 #include "kis_config_notifier.h" 0019 #include "kis_image.h" 0020 #include "kis_canvas2.h" 0021 #include "kis_animation_frame_cache.h" 0022 #include "kis_signal_auto_connection.h" 0023 #include "kis_image_animation_interface.h" 0024 #include "kis_time_span.h" 0025 #include "kis_signal_compressor.h" 0026 #include "animation/KisFrameDisplayProxy.h" 0027 #include <KisDocument.h> 0028 #include <QFileInfo> 0029 #include <QThread> 0030 #include "kis_signal_compressor_with_param.h" 0031 #include "KisImageBarrierLock.h" 0032 #include "kis_layer_utils.h" 0033 #include "KisDecoratedNodeInterface.h" 0034 #include "kis_keyframe_channel.h" 0035 #include "kis_algebra_2d.h" 0036 #include "KisPlaybackEngine.h" 0037 0038 #include "kis_image_config.h" 0039 #include <limits> 0040 0041 #include "KisViewManager.h" 0042 #include "kis_icon_utils.h" 0043 0044 #include "KisPart.h" 0045 #include "dialogs/KisAsyncAnimationCacheRenderDialog.h" 0046 #include "KisRollingMeanAccumulatorWrapper.h" 0047 #include "kis_onion_skin_compositor.h" 0048 0049 #include <atomic> 0050 0051 class SingleShotSignal : public QObject { 0052 Q_OBJECT 0053 public: 0054 SingleShotSignal(QObject* parent = nullptr) 0055 : QObject(parent) 0056 , lock(false) 0057 { 0058 } 0059 0060 ~SingleShotSignal() {} 0061 0062 public Q_SLOTS: 0063 void tryFire() { 0064 if (!lock) { 0065 lock = true; 0066 emit output(); 0067 } 0068 } 0069 0070 Q_SIGNALS: 0071 void output(); 0072 0073 private: 0074 bool lock; 0075 0076 }; 0077 0078 0079 //===== 0080 0081 /** @brief PlaybackEnvironment (Private) 0082 * Constructs and deconstructs the necessary viewing conditions when animation playback begins and ends. 0083 * This includes disabling and enabling onion skins based on playback condition and other such tasks. 0084 * Also keeps track of original origin frame of initial play command, so play/pause can work while stop 0085 * will always return to the origin of playback (where the user first pressed play from the stopped state.) 0086 */ 0087 class CanvasPlaybackEnvironment : public QObject { 0088 Q_OBJECT 0089 public: 0090 CanvasPlaybackEnvironment(int originFrame, KisCanvasAnimationState* parent = nullptr) 0091 : QObject(parent) 0092 , m_originFrame(originFrame) 0093 { 0094 connect(&m_cancelTrigger, SIGNAL(output()), parent, SIGNAL(sigCancelPlayback())); 0095 } 0096 0097 ~CanvasPlaybackEnvironment() { 0098 restore(); 0099 } 0100 0101 CanvasPlaybackEnvironment() = delete; 0102 CanvasPlaybackEnvironment(const CanvasPlaybackEnvironment&) = delete; 0103 CanvasPlaybackEnvironment& operator= (const CanvasPlaybackEnvironment&) = delete; 0104 0105 int originFrame() { return m_originFrame; } 0106 0107 KisTimeSpan playbackRange() const { 0108 return m_playbackRange; 0109 } 0110 0111 void setPlaybackRange(KisTimeSpan p_playbackRange) { 0112 m_playbackRange = p_playbackRange; 0113 } 0114 0115 void prepare(KisCanvas2* canvas) 0116 { 0117 KIS_ASSERT(canvas); // Sanity check... 0118 m_canvas = canvas; 0119 0120 const KisTimeSpan range = canvas->image()->animationInterface()->activePlaybackRange(); 0121 setPlaybackRange(range); 0122 0123 // Initialize and optimize playback environment... 0124 if (canvas->frameCache()) { 0125 KisImageConfig cfg(true); 0126 0127 const int dimensionLimit = cfg.useAnimationCacheFrameSizeLimit() ? 0128 cfg.animationCacheFrameSizeLimit() : std::numeric_limits<int>::max(); 0129 0130 const int largestDimension = KisAlgebra2D::maxDimension(canvas->image()->bounds()); 0131 0132 const QRect regionOfInterest = 0133 cfg.useAnimationCacheRegionOfInterest() && largestDimension > dimensionLimit ? 0134 canvas->regionOfInterest() : canvas->coordinatesConverter()->imageRectInImagePixels(); 0135 0136 const QRect minimalRect = 0137 canvas->coordinatesConverter()->widgetRectInImagePixels().toAlignedRect() & 0138 canvas->coordinatesConverter()->imageRectInImagePixels(); 0139 0140 canvas->frameCache()->dropLowQualityFrames(range, regionOfInterest, minimalRect); 0141 canvas->setRenderingLimit(regionOfInterest); 0142 0143 // Preemptively cache all frames... 0144 KisAsyncAnimationCacheRenderDialog dlg(canvas->frameCache(), range); 0145 dlg.setRegionOfInterest(regionOfInterest); 0146 dlg.regenerateRange(canvas->viewManager()); 0147 } else { 0148 KisImageBarrierLock lock(canvas->image()); 0149 KisLayerUtils::recursiveApplyNodes(canvas->image()->root(), [this](KisNodeSP node){ 0150 KisDecoratedNodeInterface* decoratedNode = dynamic_cast<KisDecoratedNodeInterface*>(node.data()); 0151 if (decoratedNode && decoratedNode->decorationsVisible()) { 0152 decoratedNode->setDecorationsVisible(false, false); 0153 m_disabledDecoratedNodes.append(node); 0154 } 0155 }); 0156 } 0157 0158 // Setup appropriate interrupt connections... 0159 m_cancelStrokeConnections.addConnection( 0160 canvas->image().data(), SIGNAL(sigUndoDuringStrokeRequested()), 0161 &m_cancelTrigger, SLOT(tryFire())); 0162 0163 m_cancelStrokeConnections.addConnection( 0164 canvas->image().data(), SIGNAL(sigStrokeCancellationRequested()), 0165 &m_cancelTrigger, SLOT(tryFire())); 0166 0167 // We only want to stop on stroke end when running on a system 0168 // without cache / opengl / graphics driver support! 0169 if (canvas->frameCache()) { 0170 m_cancelStrokeConnections.addConnection( 0171 canvas->image().data(), SIGNAL(sigStrokeEndRequested()), 0172 &m_cancelTrigger, SLOT(tryFire())); 0173 } 0174 } 0175 0176 void restore() { 0177 m_cancelStrokeConnections.clear(); 0178 0179 if (m_canvas) { 0180 if (m_canvas->frameCache()) { 0181 m_canvas->setRenderingLimit(QRect()); 0182 } else { 0183 KisImageBarrierLock lock(m_canvas->image()); 0184 Q_FOREACH(KisNodeWSP disabledNode, m_disabledDecoratedNodes) { 0185 KisDecoratedNodeInterface* decoratedNode = dynamic_cast<KisDecoratedNodeInterface*>(disabledNode.data()); 0186 if (decoratedNode) { 0187 decoratedNode->setDecorationsVisible(true, true); 0188 } 0189 } 0190 m_disabledDecoratedNodes.clear(); 0191 } 0192 0193 m_canvas = nullptr; 0194 } 0195 } 0196 0197 Q_SIGNALS: 0198 void sigPlaybackStatisticsUpdated(); 0199 0200 private: 0201 int m_originFrame; //!< The frame user started playback from. 0202 KisSignalAutoConnectionsStore m_cancelStrokeConnections; 0203 SingleShotSignal m_cancelTrigger; 0204 QVector<KisNodeWSP> m_disabledDecoratedNodes; 0205 0206 KisCanvas2* m_canvas; 0207 0208 KisTimeSpan m_playbackRange; 0209 }; 0210 0211 // Needed for QObject definition outside of header file. 0212 #include "KisCanvasAnimationState.moc" 0213 0214 struct KisCanvasAnimationState::Private 0215 { 0216 public: 0217 Private(KisCanvas2* p_canvas) 0218 : canvas(p_canvas) 0219 , displayProxy( new KisFrameDisplayProxy(p_canvas) ) 0220 , playbackEnvironment( nullptr ) 0221 { 0222 m_statsTimer.setInterval(1000); 0223 } 0224 0225 KisCanvas2 *canvas; 0226 PlaybackState state; 0227 QScopedPointer<KisFrameDisplayProxy> displayProxy; 0228 QScopedPointer<QFileInfo> media; // TODO: Should we just get this from the document instead? 0229 QScopedPointer<CanvasPlaybackEnvironment> playbackEnvironment; 0230 0231 QTimer m_statsTimer; 0232 0233 qreal playbackSpeed {1.0}; 0234 }; 0235 0236 KisCanvasAnimationState::KisCanvasAnimationState(KisCanvas2 *canvas) 0237 : QObject(canvas) 0238 , m_d(new Private(canvas)) 0239 { 0240 setPlaybackState(STOPPED); 0241 0242 // Handle image-internal frame change case... 0243 connect(m_d->displayProxy.data(), SIGNAL(sigFrameChange()), this, SIGNAL(sigFrameChanged())); 0244 0245 // Grow to new playback range when new frames added (configurable)... 0246 connect(m_d->canvas->image()->animationInterface(), &KisImageAnimationInterface::sigKeyframeAdded, this, [this](const KisKeyframeChannel*, int time){ 0247 if (m_d->canvas && m_d->canvas->image()) { 0248 KisImageAnimationInterface* animInterface = m_d->canvas->image()->animationInterface(); 0249 KisConfig cfg(true); 0250 if (animInterface && cfg.adaptivePlaybackRange()) { 0251 KisTimeSpan desiredPlaybackRange = animInterface->documentPlaybackRange(); 0252 desiredPlaybackRange.include(time); 0253 animInterface->setDocumentRange(desiredPlaybackRange); 0254 } 0255 } 0256 }); 0257 0258 connect(m_d->canvas->imageView()->document(), &KisDocument::sigAudioTracksChanged, this, &KisCanvasAnimationState::setupAudioTracks); 0259 connect(m_d->canvas->imageView()->document(), &KisDocument::sigAudioLevelChanged, this, &KisCanvasAnimationState::sigAudioLevelChanged); 0260 connect(&m_d->m_statsTimer, SIGNAL(timeout()), this, SIGNAL(sigPlaybackStatisticsUpdated())); 0261 } 0262 0263 KisCanvasAnimationState::~KisCanvasAnimationState() 0264 { 0265 } 0266 0267 PlaybackState KisCanvasAnimationState::playbackState() 0268 { 0269 return m_d->state; 0270 } 0271 0272 boost::optional<QFileInfo> KisCanvasAnimationState::mediaInfo() 0273 { 0274 if (m_d->media) { 0275 return boost::optional<QFileInfo>(*m_d->media); 0276 } else { 0277 return boost::none; 0278 } 0279 } 0280 0281 qreal KisCanvasAnimationState::currentVolume() 0282 { 0283 if (m_d->canvas && m_d->canvas->imageView() && m_d->canvas->imageView()->document()) { 0284 return m_d->canvas->imageView()->document()->getAudioLevel(); 0285 } else { 0286 return 0.0; 0287 } 0288 } 0289 0290 boost::optional<int> KisCanvasAnimationState::playbackOrigin() 0291 { 0292 if (m_d->playbackEnvironment) { 0293 return boost::optional<int>(m_d->playbackEnvironment->originFrame()); 0294 } else { 0295 return boost::none; 0296 } 0297 } 0298 0299 KisFrameDisplayProxy *KisCanvasAnimationState::displayProxy() 0300 { 0301 return m_d->displayProxy.data(); 0302 } 0303 0304 void KisCanvasAnimationState::setPlaybackSpeed(qreal value) 0305 { 0306 if (!qFuzzyCompare(value, m_d->playbackSpeed)) { 0307 m_d->playbackSpeed = value; 0308 Q_EMIT sigPlaybackSpeedChanged(value); 0309 } 0310 } 0311 0312 qreal KisCanvasAnimationState::playbackSpeed() const 0313 { 0314 return m_d->playbackSpeed; 0315 } 0316 0317 void KisCanvasAnimationState::setupAudioTracks() 0318 { 0319 if (!m_d->canvas || !m_d->canvas->imageView()) { 0320 return; 0321 } 0322 0323 KisDocument* doc = m_d->canvas->imageView()->document(); 0324 if (doc) { 0325 QVector<QFileInfo> files = doc->getAudioTracks(); 0326 0327 if (doc->getAudioTracks().isEmpty()) { 0328 m_d->media.reset(); 0329 } else { 0330 //Only get first file for now and make that a producer... 0331 QFileInfo toLoad = files.first(); 0332 KIS_SAFE_ASSERT_RECOVER_RETURN(toLoad.exists()); 0333 m_d->media.reset(new QFileInfo(toLoad)); 0334 0335 // Once media is attached we upgrade to the MLT-based playbackEngine... 0336 KisPart::instance()->upgradeToPlaybackEngineMLT(m_d->canvas); 0337 } 0338 0339 emit sigPlaybackMediaChanged(); 0340 } 0341 } 0342 0343 void KisCanvasAnimationState::showFrame(int frame, bool finalize) 0344 { 0345 m_d->displayProxy->displayFrame(frame, finalize); 0346 } 0347 0348 KisTimeSpan KisCanvasAnimationState::activePlaybackRange() 0349 { 0350 if (!m_d->canvas || !m_d->canvas->image()) { 0351 return KisTimeSpan::infinite(0); 0352 } 0353 0354 const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface(); 0355 return animation->activePlaybackRange(); 0356 } 0357 0358 void KisCanvasAnimationState::setPlaybackState(PlaybackState p_state) 0359 { 0360 if (m_d->state != p_state) { 0361 m_d->state = p_state; 0362 if (m_d->state == PLAYING) { 0363 if (!m_d->playbackEnvironment) { 0364 m_d->playbackEnvironment.reset(new CanvasPlaybackEnvironment(m_d->displayProxy->activeFrame(), this)); 0365 connect(m_d->playbackEnvironment.data(), SIGNAL(sigPlaybackStatisticsUpdated()), 0366 this, SIGNAL(sigPlaybackStatisticsUpdated())); 0367 } 0368 0369 m_d->playbackEnvironment->prepare(m_d->canvas); 0370 0371 m_d->m_statsTimer.start(); 0372 Q_EMIT sigPlaybackStatisticsUpdated(); 0373 0374 } else { 0375 if (m_d->playbackEnvironment) { 0376 m_d->playbackEnvironment->restore(); 0377 } 0378 0379 if (m_d->state == STOPPED) { 0380 m_d->playbackEnvironment.reset(); 0381 } 0382 0383 m_d->m_statsTimer.stop(); 0384 Q_EMIT sigPlaybackStatisticsUpdated(); 0385 } 0386 0387 emit sigPlaybackStateChanged(m_d->state); 0388 } 0389 }