File indexing completed on 2024-05-19 04:29:17

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2022 Emmet O'Neill <emmetoneill.pdx@gmail.com>
0003    SPDX-FileCopyrightText: 2022 Eoin O'Neill <eoinoneill1991@gmail.com>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "KisPlaybackEngine.h"
0009 
0010 #include "kis_canvas2.h"
0011 #include "KisCanvasAnimationState.h"
0012 #include "kis_image_animation_interface.h"
0013 #include "kis_raster_keyframe_channel.h"
0014 #include "animation/KisFrameDisplayProxy.h"
0015 #include "KisViewManager.h"
0016 #include "kis_config.h"
0017 
0018 #include "kis_onion_skin_compositor.h"
0019 
0020 struct KisPlaybackEngine::Private {
0021 public:
0022     Private() {
0023     }
0024 
0025     ~Private() {
0026     }
0027 
0028     KisCanvas2* activeCanvas {nullptr};
0029     bool dropFramesMode {true};
0030 };
0031 
0032 //=====
0033 
0034 KisPlaybackEngine::KisPlaybackEngine(QObject *parent)
0035     : QObject(parent)
0036     , m_d(new Private)
0037 {
0038     KisConfig cfg(true);
0039     m_d->dropFramesMode = cfg.animationDropFrames();
0040 }
0041 
0042 KisPlaybackEngine::~KisPlaybackEngine()
0043 {
0044 }
0045 
0046 void KisPlaybackEngine::play()
0047 {
0048     KIS_SAFE_ASSERT_RECOVER_RETURN(activeCanvas() && activeCanvas()->animationState());
0049     activeCanvas()->animationState()->setPlaybackState(PLAYING);
0050 }
0051 
0052 void KisPlaybackEngine::pause()
0053 {
0054     KIS_SAFE_ASSERT_RECOVER_RETURN(activeCanvas() && activeCanvas()->animationState());
0055     activeCanvas()->animationState()->setPlaybackState(PAUSED);
0056 }
0057 
0058 void KisPlaybackEngine::playPause()
0059 {
0060     KIS_SAFE_ASSERT_RECOVER_RETURN(activeCanvas() && activeCanvas()->animationState());
0061     KisCanvasAnimationState* animationState = activeCanvas()->animationState();
0062 
0063     if (animationState->playbackState() == PLAYING) {
0064         pause();
0065         seek(animationState->displayProxy()->activeFrame(), SEEK_FINALIZE);
0066     } else {
0067         play();
0068     }
0069 }
0070 
0071 void KisPlaybackEngine::stop()
0072 {
0073     KIS_SAFE_ASSERT_RECOVER_RETURN(activeCanvas() && activeCanvas()->animationState());
0074     KisCanvasAnimationState* animationState = activeCanvas()->animationState();
0075 
0076     if (animationState->playbackState() != STOPPED) {
0077         const boost::optional<int> origin = animationState->playbackOrigin();
0078         animationState->setPlaybackState(STOPPED);
0079         if (origin.has_value()) {
0080             seek(origin.value(), SEEK_FINALIZE);
0081         }
0082     } else if (animationState->displayProxy()->activeFrame() != 0) {
0083         KisImageAnimationInterface* ai = activeCanvas()->image()->animationInterface();
0084         KIS_SAFE_ASSERT_RECOVER_RETURN(ai);
0085         const int firstFrame = ai->documentPlaybackRange().start();
0086         seek(firstFrame, SEEK_FINALIZE | SEEK_PUSH_AUDIO);
0087     }
0088 }
0089 
0090 void KisPlaybackEngine::previousFrame()
0091 {
0092     moveActiveFrameBy(-1);
0093 }
0094 
0095 void KisPlaybackEngine::nextFrame()
0096 {
0097     moveActiveFrameBy(1);
0098 }
0099 
0100 void KisPlaybackEngine::previousKeyframe()
0101 {
0102     if (!m_d->activeCanvas) return;
0103     KisCanvasAnimationState *animationState = m_d->activeCanvas->animationState();
0104     KIS_SAFE_ASSERT_RECOVER_RETURN(animationState);
0105 
0106     KisNodeSP node = m_d->activeCanvas->viewManager()->activeNode();
0107     if (!node) return;
0108 
0109     KisKeyframeChannel *keyframes =
0110         node->getKeyframeChannel(KisKeyframeChannel::Raster.id());
0111     if (!keyframes) return;
0112 
0113     int currentFrame = animationState->displayProxy()->activeFrame();
0114 
0115     int destinationTime = -1;
0116     if (!keyframes->keyframeAt(currentFrame)) {
0117         destinationTime = keyframes->activeKeyframeTime(currentFrame);
0118     } else {
0119         destinationTime = keyframes->previousKeyframeTime(currentFrame);
0120     }
0121 
0122     if (keyframes->keyframeAt(destinationTime)) {
0123         if (animationState->playbackState() != STOPPED) {
0124             stop();
0125         }
0126 
0127         seek(destinationTime, SEEK_FINALIZE | SEEK_PUSH_AUDIO);
0128     }
0129 }
0130 
0131 void KisPlaybackEngine::nextKeyframe()
0132 {
0133     if (!m_d->activeCanvas) return;
0134     KisCanvasAnimationState *animationState = m_d->activeCanvas->animationState();
0135     KIS_SAFE_ASSERT_RECOVER_RETURN(animationState);
0136 
0137     KisNodeSP node = m_d->activeCanvas->viewManager()->activeNode();
0138     if (!node) return;
0139 
0140     KisKeyframeChannel *keyframes =
0141         node->getKeyframeChannel(KisKeyframeChannel::Raster.id());
0142     if (!keyframes) return;
0143 
0144     int currentTime = animationState->displayProxy()->activeFrame();
0145 
0146     int destinationTime = -1;
0147     if (keyframes->activeKeyframeAt(currentTime)) {
0148         destinationTime = keyframes->nextKeyframeTime(currentTime);
0149     }
0150 
0151     if (keyframes->keyframeAt(destinationTime)) {
0152         // Jump to next key...
0153         if (animationState->playbackState() != STOPPED) {
0154             stop();
0155         }
0156 
0157         seek(destinationTime, SEEK_FINALIZE | SEEK_PUSH_AUDIO);
0158     } else {
0159         // Jump ahead by estimated timing...
0160         const int activeKeyTime = keyframes->activeKeyframeTime(currentTime);
0161         const int previousKeyTime = keyframes->previousKeyframeTime(activeKeyTime);
0162 
0163         if (previousKeyTime != -1) {
0164             if (animationState->playbackState() != STOPPED) {
0165                 stop();
0166             }
0167 
0168             const int timing = activeKeyTime - previousKeyTime;
0169             seek(currentTime + timing, SEEK_FINALIZE | SEEK_PUSH_AUDIO);
0170         }
0171     }
0172 }
0173 
0174 void KisPlaybackEngine::firstFrame()
0175 {
0176     if (!m_d->activeCanvas) return;
0177     KisCanvasAnimationState *animationState = m_d->activeCanvas->animationState();
0178     KIS_SAFE_ASSERT_RECOVER_RETURN(animationState);
0179     KisImageAnimationInterface *animInterface = m_d->activeCanvas->image()->animationInterface();
0180 
0181     const int startFrame = animInterface->activePlaybackRange().start();
0182 
0183     if (animationState->playbackState() != STOPPED) {
0184         stop();
0185     }
0186 
0187     seek(startFrame, SEEK_FINALIZE | SEEK_PUSH_AUDIO);
0188 }
0189 
0190 void KisPlaybackEngine::lastFrame()
0191 {
0192     if (!m_d->activeCanvas) return;
0193     KisCanvasAnimationState *animationState = m_d->activeCanvas->animationState();
0194     KIS_SAFE_ASSERT_RECOVER_RETURN(animationState);
0195     KisImageAnimationInterface *animInterface = m_d->activeCanvas->image()->animationInterface();
0196 
0197     const int endFrame = animInterface->activePlaybackRange().end();
0198 
0199     if (animationState->playbackState() != STOPPED) {
0200         stop();
0201     }
0202 
0203     seek(endFrame, SEEK_FINALIZE | SEEK_PUSH_AUDIO);
0204 }
0205 
0206 void KisPlaybackEngine::previousMatchingKeyframe()
0207 {
0208     if (!m_d->activeCanvas) return;
0209     KisCanvasAnimationState *animationState = m_d->activeCanvas->animationState();
0210     KIS_SAFE_ASSERT_RECOVER_RETURN(animationState);
0211 
0212     KisNodeSP node = m_d->activeCanvas->viewManager()->activeNode();
0213     if (!node) return;
0214 
0215     KisKeyframeChannel *keyframes =
0216         node->getKeyframeChannel(KisKeyframeChannel::Raster.id());
0217     if (!keyframes) return;
0218 
0219     int time = animationState->displayProxy()->activeFrame();
0220 
0221     KisKeyframeSP currentKeyframe = keyframes->keyframeAt(time);
0222     int destinationTime = keyframes->activeKeyframeTime(time);
0223     const int desiredColor = currentKeyframe ? currentKeyframe->colorLabel() : keyframes->keyframeAt(destinationTime)->colorLabel();
0224     previousKeyframeWithColor(desiredColor);
0225 }
0226 
0227 void KisPlaybackEngine::nextMatchingKeyframe()
0228 {
0229     if (!m_d->activeCanvas) return;
0230     KisCanvasAnimationState *animationState = m_d->activeCanvas->animationState();
0231     KIS_SAFE_ASSERT_RECOVER_RETURN(animationState);
0232 
0233     KisNodeSP node = m_d->activeCanvas->viewManager()->activeNode();
0234     if (!node) return;
0235 
0236     KisKeyframeChannel *keyframes =
0237         node->getKeyframeChannel(KisKeyframeChannel::Raster.id());
0238     if (!keyframes) return;
0239 
0240     int time = animationState->displayProxy()->activeFrame();
0241 
0242     if (!keyframes->activeKeyframeAt(time)) {
0243         return;
0244     }
0245 
0246     int destinationTime = keyframes->activeKeyframeTime(time);
0247     nextKeyframeWithColor(keyframes->keyframeAt(destinationTime)->colorLabel());
0248 }
0249 
0250 void KisPlaybackEngine::previousUnfilteredKeyframe()
0251 {
0252     previousKeyframeWithColor(KisOnionSkinCompositor::instance()->colorLabelFilter());
0253 }
0254 
0255 void KisPlaybackEngine::nextUnfilteredKeyframe()
0256 {
0257     nextKeyframeWithColor(KisOnionSkinCompositor::instance()->colorLabelFilter());
0258 }
0259 
0260 void KisPlaybackEngine::setDropFramesMode(bool value)
0261 {
0262     if (value != m_d->dropFramesMode) {
0263         m_d->dropFramesMode = value;
0264         Q_EMIT sigDropFramesModeChanged(value);
0265     }
0266 }
0267 
0268 bool KisPlaybackEngine::dropFrames() const
0269 {
0270     return m_d->dropFramesMode;
0271 }
0272 
0273 int KisPlaybackEngine::frameWrap(int frame, int startFrame, int endFrame)
0274 {
0275     // Since Krita has always considered the end frame as inclusive, we need
0276     // to make sure our wrap method respects that as well.
0277     const int inclusiveEndFrame = endFrame + 1;
0278     frame = ((frame - startFrame) % (inclusiveEndFrame - startFrame)) + startFrame;
0279 
0280     if (frame - startFrame < 0) {
0281         frame += (inclusiveEndFrame - startFrame);
0282     }
0283 
0284     return frame;
0285 }
0286 
0287 KisCanvas2 *KisPlaybackEngine::activeCanvas() const
0288 {
0289     return m_d->activeCanvas;
0290 }
0291 
0292 void KisPlaybackEngine::setCanvas(KoCanvasBase *p_canvas)
0293 {
0294     if (m_d->activeCanvas) {
0295         KisCanvasAnimationState* animState = m_d->activeCanvas->animationState();
0296         KIS_SAFE_ASSERT_RECOVER_RETURN(animState);
0297 
0298         animState->disconnect(this);
0299     }
0300 
0301     KisCanvas2* canvas = dynamic_cast<KisCanvas2*>(p_canvas);
0302     m_d->activeCanvas = canvas;
0303 
0304     if (m_d->activeCanvas) {
0305         KisCanvasAnimationState* animState = m_d->activeCanvas->animationState();
0306         KIS_SAFE_ASSERT_RECOVER_RETURN(animState);
0307 
0308         connect(animState, &KisCanvasAnimationState::sigCancelPlayback,
0309                 this, &KisPlaybackEngine::stop);
0310 
0311         /**
0312          * TODO: This forced updates causes image recalculation on every document
0313          * switch, which is weird and even causes some crashes on closing
0314          * many documents at once (which is a separate bug it seems). Why
0315          * document switch should forcefully regeneare the canvas?
0316          */
0317 //        KisImageAnimationInterface* animInterface = m_d->activeCanvas->image()->animationInterface();
0318 //        KisFrameDisplayProxy* displayProxy = animState->displayProxy();
0319 //        if (animState->playbackState() != PLAYING) {
0320 //            displayProxy->displayFrame(animInterface->currentTime(), false);
0321 //        }
0322     }
0323 }
0324 
0325 void KisPlaybackEngine::unsetCanvas()
0326 {
0327     m_d->activeCanvas = nullptr;
0328 }
0329 
0330 void KisPlaybackEngine::moveActiveFrameBy(int frames)
0331 {
0332     if (!m_d->activeCanvas) return;
0333     KisCanvasAnimationState *animationState = m_d->activeCanvas->animationState();
0334     KIS_SAFE_ASSERT_RECOVER_RETURN(animationState);
0335     KisImageAnimationInterface *animInterface = m_d->activeCanvas->image()->animationInterface();
0336 
0337     int frame = animationState->displayProxy()->activeFrame() + frames;
0338 
0339     const int startFrame = animInterface->activePlaybackRange().start();
0340     const int endFrame = animInterface->activePlaybackRange().end();
0341 
0342     frame = frameWrap(frame, startFrame, endFrame);
0343 
0344     KIS_SAFE_ASSERT_RECOVER_RETURN(frame >= 0);
0345 
0346     if (animationState->playbackState() != STOPPED) {
0347         stop();
0348     }
0349 
0350     seek(frame, SEEK_FINALIZE | SEEK_PUSH_AUDIO);
0351 }
0352 
0353 void KisPlaybackEngine::nextKeyframeWithColor(int color)
0354 {
0355     QSet<int> validColors;
0356     validColors.insert(color);
0357     nextKeyframeWithColor(validColors);
0358 }
0359 
0360 void KisPlaybackEngine::nextKeyframeWithColor(const QSet<int> &validColors)
0361 {
0362     if (!m_d->activeCanvas) return;
0363     KisCanvasAnimationState *animationState = m_d->activeCanvas->animationState();
0364     KIS_SAFE_ASSERT_RECOVER_RETURN(animationState);
0365 
0366     KisNodeSP node = m_d->activeCanvas->viewManager()->activeNode();
0367     if (!node) return;
0368 
0369     KisKeyframeChannel *keyframes =
0370         node->getKeyframeChannel(KisKeyframeChannel::Raster.id());
0371     if (!keyframes) return;
0372 
0373     int time = animationState->displayProxy()->activeFrame();
0374 
0375     if (!keyframes->activeKeyframeAt(time)) {
0376         return;
0377     }
0378 
0379     int destinationTime = keyframes->activeKeyframeTime(time);
0380     while ( keyframes->keyframeAt(destinationTime) &&
0381             ((destinationTime == time) ||
0382             !validColors.contains(keyframes->keyframeAt(destinationTime)->colorLabel()))) {
0383 
0384         destinationTime = keyframes->nextKeyframeTime(destinationTime);
0385     }
0386 
0387     if (keyframes->keyframeAt(destinationTime)) {
0388         if (animationState->playbackState() != STOPPED) {
0389             stop();
0390         }
0391 
0392         seek(destinationTime, SEEK_FINALIZE | SEEK_PUSH_AUDIO);
0393     }
0394 }
0395 
0396 void KisPlaybackEngine::previousKeyframeWithColor(int color)
0397 {
0398     QSet<int> validColors;
0399     validColors.insert(color);
0400     previousKeyframeWithColor(validColors);
0401 }
0402 
0403 void KisPlaybackEngine::previousKeyframeWithColor(const QSet<int> &validColors)
0404 {
0405     if (!m_d->activeCanvas) return;
0406     KisCanvasAnimationState *animationState = m_d->activeCanvas->animationState();
0407     KIS_SAFE_ASSERT_RECOVER_RETURN(animationState);
0408 
0409     KisNodeSP node = m_d->activeCanvas->viewManager()->activeNode();
0410     if (!node) return;
0411 
0412     KisKeyframeChannel *keyframes =
0413         node->getKeyframeChannel(KisKeyframeChannel::Raster.id());
0414     if (!keyframes) return;
0415 
0416     int time = animationState->displayProxy()->activeFrame();
0417 
0418     int destinationTime = keyframes->activeKeyframeTime(time);
0419     while (keyframes->keyframeAt(destinationTime) &&
0420            ((destinationTime == time) ||
0421            !validColors.contains(keyframes->keyframeAt(destinationTime)->colorLabel()))) {
0422 
0423         destinationTime = keyframes->previousKeyframeTime(destinationTime);
0424     }
0425 
0426     if (keyframes->keyframeAt(destinationTime)) {
0427         if (animationState->playbackState() != STOPPED) {
0428             stop();
0429         }
0430 
0431         seek(destinationTime, SEEK_FINALIZE | SEEK_PUSH_AUDIO);
0432     }
0433 }