1 /*
2  *  Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "KisAsyncAnimationRendererBase.h"
20 
21 #include <QTimer>
22 #include <QThread>
23 
24 #include "kis_image.h"
25 #include "kis_image_animation_interface.h"
26 #include "kis_signal_auto_connection.h"
27 
28 struct KRITAUI_NO_EXPORT KisAsyncAnimationRendererBase::Private
29 {
30 
31     KisSignalAutoConnectionsStore imageRequestConnections;
32     QTimer regenerationTimeout;
33 
34     KisImageSP requestedImage;
35     int requestedFrame = -1;
36     bool isCancelled = false;
37     KisRegion requestedRegion;
38 
39     static const int WAITING_FOR_FRAME_TIMEOUT = 30000;
40 };
41 
KisAsyncAnimationRendererBase(QObject * parent)42 KisAsyncAnimationRendererBase::KisAsyncAnimationRendererBase(QObject *parent)
43     : QObject(parent),
44       m_d(new Private())
45 {
46     connect(&m_d->regenerationTimeout, SIGNAL(timeout()), SLOT(slotFrameRegenerationCancelled()));
47     m_d->regenerationTimeout.setSingleShot(true);
48     m_d->regenerationTimeout.setInterval(Private::WAITING_FOR_FRAME_TIMEOUT);
49 }
50 
~KisAsyncAnimationRendererBase()51 KisAsyncAnimationRendererBase::~KisAsyncAnimationRendererBase()
52 {
53 
54 }
55 
startFrameRegeneration(KisImageSP image,int frame,const KisRegion & regionOfInterest)56 void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame, const KisRegion &regionOfInterest)
57 {
58     KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
59 
60     m_d->requestedImage = image;
61     m_d->requestedFrame = frame;
62     m_d->isCancelled = false;
63     m_d->requestedRegion = !regionOfInterest.isEmpty() ? regionOfInterest : image->bounds();
64 
65     KisImageAnimationInterface *animation = m_d->requestedImage->animationInterface();
66 
67     m_d->imageRequestConnections.clear();
68     m_d->imageRequestConnections.addConnection(
69                 animation, SIGNAL(sigFrameReady(int)),
70                 this, SLOT(slotFrameRegenerationFinished(int)),
71                 Qt::DirectConnection);
72 
73     m_d->imageRequestConnections.addConnection(
74                 animation, SIGNAL(sigFrameCancelled()),
75                 this, SLOT(slotFrameRegenerationCancelled()),
76                 Qt::AutoConnection);
77 
78     m_d->regenerationTimeout.start();
79     animation->requestFrameRegeneration(m_d->requestedFrame, m_d->requestedRegion);
80 }
81 
startFrameRegeneration(KisImageSP image,int frame)82 void KisAsyncAnimationRendererBase::startFrameRegeneration(KisImageSP image, int frame)
83 {
84     startFrameRegeneration(image, frame, KisRegion());
85 }
86 
isActive() const87 bool KisAsyncAnimationRendererBase::isActive() const
88 {
89     return m_d->requestedImage;
90 }
91 
cancelCurrentFrameRendering()92 void KisAsyncAnimationRendererBase::cancelCurrentFrameRendering()
93 {
94     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage);
95     frameCancelledCallback(m_d->requestedFrame);
96 }
97 
slotFrameRegenerationCancelled()98 void KisAsyncAnimationRendererBase::slotFrameRegenerationCancelled()
99 {
100     // the timeout can arrive in async way
101     if (!m_d->requestedImage) return;
102     frameCancelledCallback(m_d->requestedFrame);
103 }
104 
slotFrameRegenerationFinished(int frame)105 void KisAsyncAnimationRendererBase::slotFrameRegenerationFinished(int frame)
106 {
107     // We might have already cancelled the regeneration. We don't check
108     // isCancelled flag here because this code runs asynchronously.
109     if (!m_d->requestedImage) return;
110 
111     // WARNING: executed in the context of image worker thread!
112 
113     // probably a bit too strict...
114     KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() != this->thread());
115 
116     frameCompletedCallback(frame, m_d->requestedRegion);
117 }
118 
notifyFrameCompleted(int frame)119 void KisAsyncAnimationRendererBase::notifyFrameCompleted(int frame)
120 {
121     KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
122 
123     // the image events can come with a delay, even after
124     // the processing was cancelled
125     if (m_d->isCancelled) return;
126 
127     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage);
128     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame);
129 
130     clearFrameRegenerationState(false);
131     emit sigFrameCompleted(frame);
132 }
133 
notifyFrameCancelled(int frame)134 void KisAsyncAnimationRendererBase::notifyFrameCancelled(int frame)
135 {
136     KIS_SAFE_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
137 
138     // the image events can come with a delay, even after
139     // the processing was cancelled
140     if (m_d->isCancelled) return;
141 
142     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedImage);
143     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->requestedFrame == frame);
144 
145     clearFrameRegenerationState(true);
146     emit sigFrameCancelled(frame);
147 }
148 
clearFrameRegenerationState(bool isCancelled)149 void KisAsyncAnimationRendererBase::clearFrameRegenerationState(bool isCancelled)
150 {
151     // TODO: for some reason we mark the process as cancelled in any case, and it
152     //       seem to be a correct behavior
153     Q_UNUSED(isCancelled);
154 
155     m_d->imageRequestConnections.clear();
156     m_d->requestedImage = 0;
157     m_d->requestedFrame = -1;
158     m_d->regenerationTimeout.stop();
159     m_d->isCancelled = true;
160     m_d->requestedRegion = KisRegion();
161 }
162 
requestedImage() const163 KisImageSP KisAsyncAnimationRendererBase::requestedImage() const
164 {
165     return m_d->requestedImage;
166 }
167 
168 
169