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 ®ionOfInterest)
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