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 }