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

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisAsyncAnimationRendererBase.h"
0008 
0009 #include <QTimer>
0010 #include <QThread>
0011 
0012 #include "KisMpl.h"
0013 #include "kis_image.h"
0014 #include "kis_image_animation_interface.h"
0015 #include "kis_signal_auto_connection.h"
0016 #include "kis_image_config.h"
0017 #include <KisStaticInitializer.h>
0018 
0019 KIS_DECLARE_STATIC_INITIALIZER {
0020     qRegisterMetaType<KisAsyncAnimationRendererBase::CancelReason>("KisAsyncAnimationRendererBase::CancelReason");
0021 }
0022 
0023 struct KRITAUI_NO_EXPORT KisAsyncAnimationRendererBase::Private
0024 {
0025 
0026     KisSignalAutoConnectionsStore imageRequestConnections;
0027     QTimer regenerationTimeout;
0028 
0029     KisImageSP requestedImage;
0030     int requestedFrame = -1;
0031     bool isCancelled = false;
0032     KisRegion requestedRegion;
0033 };
0034 
0035 KisAsyncAnimationRendererBase::KisAsyncAnimationRendererBase(QObject *parent)
0036     : QObject(parent),
0037       m_d(new Private())
0038 {
0039     connect(&m_d->regenerationTimeout, SIGNAL(timeout()), SLOT(slotFrameRegenerationTimedOut()));
0040 
0041     KisImageConfig cfg(true);
0042 
0043     m_d->regenerationTimeout.setSingleShot(true);
0044     m_d->regenerationTimeout.setInterval(cfg.frameRenderingTimeout());
0045 }
0046 
0047 KisAsyncAnimationRendererBase::~KisAsyncAnimationRendererBase()
0048 {
0049 
0050 }
0051 
0052 void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame, const KisRegion &regionOfInterest, Flags flags, KisLockFrameGenerationLock &&frameGenerationLock)
0053 {
0054     KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
0055 
0056     m_d->requestedImage = image;
0057     m_d->requestedFrame = frame;
0058     m_d->isCancelled = false;
0059     m_d->requestedRegion = !regionOfInterest.isEmpty() ? regionOfInterest : image->bounds();
0060 
0061     KisImageAnimationInterface *animation = m_d->requestedImage->animationInterface();
0062 
0063     m_d->imageRequestConnections.clear();
0064     m_d->imageRequestConnections.addConnection(
0065                 animation, SIGNAL(sigFrameReady(int)),
0066                 this, SLOT(slotFrameRegenerationFinished(int)),
0067                 Qt::DirectConnection);
0068 
0069     m_d->imageRequestConnections.addConnection(
0070                 animation, SIGNAL(sigFrameCancelled()),
0071                 this, SLOT(slotFrameRegenerationCancelled()),
0072                 Qt::AutoConnection);
0073 
0074     m_d->regenerationTimeout.start();
0075     animation->requestFrameRegeneration(m_d->requestedFrame, m_d->requestedRegion, flags & Cancellable, std::move(frameGenerationLock));
0076 }
0077 
0078 void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame, Flags flags, KisLockFrameGenerationLock &&frameGenerationLock)
0079 {
0080     startFrameRegeneration(image, frame, KisRegion(), flags, std::move(frameGenerationLock));
0081 }
0082 
0083 bool KisAsyncAnimationRendererBase::isActive() const
0084 {
0085     return m_d->requestedImage;
0086 }
0087 
0088 void KisAsyncAnimationRendererBase::cancelCurrentFrameRendering(CancelReason cancelReason)
0089 {
0090     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage);
0091     frameCancelledCallback(m_d->requestedFrame, cancelReason);
0092 }
0093 
0094 void KisAsyncAnimationRendererBase::slotFrameRegenerationCancelled()
0095 {
0096     // the cancel can arrive in async way
0097     if (!m_d->requestedImage) return;
0098     frameCancelledCallback(m_d->requestedFrame, RenderingFailed);
0099 }
0100 
0101 void KisAsyncAnimationRendererBase::slotFrameRegenerationTimedOut()
0102 {
0103     // the timeout can arrive in async way
0104     if (!m_d->requestedImage) return;
0105     frameCancelledCallback(m_d->requestedFrame, RenderingTimedOut);
0106 }
0107 
0108 void KisAsyncAnimationRendererBase::slotFrameRegenerationFinished(int frame)
0109 {
0110     // We might have already cancelled the regeneration. We don't check
0111     // isCancelled flag here because this code runs asynchronously.
0112     if (!m_d->requestedImage) return;
0113 
0114     // WARNING: executed in the context of image worker thread!
0115 
0116     // probably a bit too strict...
0117     KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != this->thread());
0118 
0119     frameCompletedCallback(frame, m_d->requestedRegion);
0120 }
0121 
0122 void KisAsyncAnimationRendererBase::notifyFrameCompleted(int frame)
0123 {
0124     KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
0125 
0126     // the image events can come with a delay, even after
0127     // the processing was cancelled
0128     if (m_d->isCancelled) return;
0129 
0130     {
0131         // clean state right after the assert checks
0132         auto cleanup = kismpl::finally([&] () {
0133             clearFrameRegenerationState(false);
0134         });
0135 
0136         KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage);
0137         KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame);
0138     }
0139 
0140 
0141     emit sigFrameCompleted(frame);
0142 }
0143 
0144 void KisAsyncAnimationRendererBase::notifyFrameCancelled(int frame, CancelReason cancelReason)
0145 {
0146     KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
0147 
0148     // the image events can come with a delay, even after
0149     // the processing was cancelled
0150     if (m_d->isCancelled) return;
0151 
0152     {
0153         // clean state right after the assert checks
0154         auto cleanup = kismpl::finally([&] () {
0155             clearFrameRegenerationState(true);
0156         });
0157 
0158         KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage);
0159         KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame);
0160     }
0161 
0162     emit sigFrameCancelled(frame, cancelReason);
0163 }
0164 
0165 void KisAsyncAnimationRendererBase::clearFrameRegenerationState(bool isCancelled)
0166 {
0167     // TODO: for some reason we mark the process as cancelled in any case, and it
0168     //       seem to be a correct behavior
0169     Q_UNUSED(isCancelled);
0170 
0171     m_d->imageRequestConnections.clear();
0172     m_d->requestedImage = 0;
0173     m_d->requestedFrame = -1;
0174     m_d->regenerationTimeout.stop();
0175     m_d->isCancelled = true;
0176     m_d->requestedRegion = KisRegion();
0177 }
0178 
0179 KisImageSP KisAsyncAnimationRendererBase::requestedImage() const
0180 {
0181     return m_d->requestedImage;
0182 }
0183 
0184