1 /*
2 * Copyright (c) 2015 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 "kis_image_animation_interface.h"
20
21 #include <QFileInfo>
22
23 #include "kis_global.h"
24 #include "kis_image.h"
25 #include "kis_regenerate_frame_stroke_strategy.h"
26 #include "kis_switch_time_stroke_strategy.h"
27 #include "kis_keyframe_channel.h"
28 #include "kis_time_range.h"
29
30 #include "kis_post_execution_undo_adapter.h"
31 #include "commands_new/kis_switch_current_time_command.h"
32 #include "kis_layer_utils.h"
33
34
35 struct KisImageAnimationInterface::Private
36 {
PrivateKisImageAnimationInterface::Private37 Private()
38 : image(0),
39 externalFrameActive(false),
40 frameInvalidationBlocked(false),
41 cachedLastFrameValue(-1),
42 audioChannelMuted(false),
43 audioChannelVolume(0.5),
44 m_currentTime(0),
45 m_currentUITime(0)
46 {
47 }
48
PrivateKisImageAnimationInterface::Private49 Private(const Private &rhs, KisImage *newImage)
50 : image(newImage),
51 externalFrameActive(false),
52 frameInvalidationBlocked(false),
53 fullClipRange(rhs.fullClipRange),
54 playbackRange(rhs.playbackRange),
55 framerate(rhs.framerate),
56 cachedLastFrameValue(-1),
57 audioChannelFileName(rhs.audioChannelFileName),
58 audioChannelMuted(rhs.audioChannelMuted),
59 audioChannelVolume(rhs.audioChannelVolume),
60 m_currentTime(rhs.m_currentTime),
61 m_currentUITime(rhs.m_currentUITime)
62 {
63 }
64
65 KisImage *image;
66 bool externalFrameActive;
67 bool frameInvalidationBlocked;
68
69 KisTimeRange fullClipRange;
70 KisTimeRange playbackRange;
71 int framerate;
72 int cachedLastFrameValue;
73 QString audioChannelFileName;
74 bool audioChannelMuted;
75 qreal audioChannelVolume;
76
77 KisSwitchTimeStrokeStrategy::SharedTokenWSP switchToken;
78
currentTimeKisImageAnimationInterface::Private79 inline int currentTime() const {
80 return m_currentTime;
81 }
82
currentUITimeKisImageAnimationInterface::Private83 inline int currentUITime() const {
84 return m_currentUITime;
85 }
setCurrentTimeKisImageAnimationInterface::Private86 inline void setCurrentTime(int value) {
87 m_currentTime = value;
88 }
89
setCurrentUITimeKisImageAnimationInterface::Private90 inline void setCurrentUITime(int value) {
91 m_currentUITime = value;
92 }
93 private:
94 int m_currentTime;
95 int m_currentUITime;
96 };
97
98
KisImageAnimationInterface(KisImage * image)99 KisImageAnimationInterface::KisImageAnimationInterface(KisImage *image)
100 : QObject(image),
101 m_d(new Private)
102 {
103 m_d->image = image;
104
105 m_d->framerate = 24;
106 m_d->fullClipRange = KisTimeRange::fromTime(0, 100);
107
108 connect(this, SIGNAL(sigInternalRequestTimeSwitch(int,bool)), SLOT(switchCurrentTimeAsync(int,bool)));
109 }
110
KisImageAnimationInterface(const KisImageAnimationInterface & rhs,KisImage * newImage)111 KisImageAnimationInterface::KisImageAnimationInterface(const KisImageAnimationInterface &rhs, KisImage *newImage)
112 : m_d(new Private(*rhs.m_d, newImage))
113 {
114 connect(this, SIGNAL(sigInternalRequestTimeSwitch(int,bool)), SLOT(switchCurrentTimeAsync(int,bool)));
115 }
116
~KisImageAnimationInterface()117 KisImageAnimationInterface::~KisImageAnimationInterface()
118 {
119 }
120
hasAnimation() const121 bool KisImageAnimationInterface::hasAnimation() const
122 {
123 bool hasAnimation = false;
124
125 KisLayerUtils::recursiveApplyNodes(
126 m_d->image->root(),
127 [&hasAnimation](KisNodeSP node) {
128 hasAnimation |= node->isAnimated();
129 });
130
131 return hasAnimation;
132 }
133
currentTime() const134 int KisImageAnimationInterface::currentTime() const
135 {
136 return m_d->currentTime();
137 }
138
currentUITime() const139 int KisImageAnimationInterface::currentUITime() const
140 {
141 return m_d->currentUITime();
142 }
143
fullClipRange() const144 const KisTimeRange& KisImageAnimationInterface::fullClipRange() const
145 {
146 return m_d->fullClipRange;
147 }
148
setFullClipRange(const KisTimeRange range)149 void KisImageAnimationInterface::setFullClipRange(const KisTimeRange range)
150 {
151 KIS_SAFE_ASSERT_RECOVER_RETURN(!range.isInfinite());
152 m_d->fullClipRange = range;
153 emit sigFullClipRangeChanged();
154 }
155
setFullClipRangeStartTime(int column)156 void KisImageAnimationInterface::setFullClipRangeStartTime(int column)
157 {
158 KisTimeRange newRange(column, m_d->fullClipRange.end(), false);
159 setFullClipRange(newRange);
160 }
161
setFullClipRangeEndTime(int column)162 void KisImageAnimationInterface::setFullClipRangeEndTime(int column)
163 {
164 KisTimeRange newRange(m_d->fullClipRange.start(), column, false);
165 setFullClipRange(newRange);
166 }
167
playbackRange() const168 const KisTimeRange& KisImageAnimationInterface::playbackRange() const
169 {
170 return m_d->playbackRange.isValid() ? m_d->playbackRange : m_d->fullClipRange;
171 }
172
setPlaybackRange(const KisTimeRange range)173 void KisImageAnimationInterface::setPlaybackRange(const KisTimeRange range)
174 {
175 KIS_SAFE_ASSERT_RECOVER_RETURN(!range.isInfinite());
176 m_d->playbackRange = range;
177 emit sigPlaybackRangeChanged();
178 }
179
framerate() const180 int KisImageAnimationInterface::framerate() const
181 {
182 return m_d->framerate;
183 }
184
audioChannelFileName() const185 QString KisImageAnimationInterface::audioChannelFileName() const
186 {
187 return m_d->audioChannelFileName;
188 }
189
setAudioChannelFileName(const QString & fileName)190 void KisImageAnimationInterface::setAudioChannelFileName(const QString &fileName)
191 {
192 QFileInfo info(fileName);
193
194 KIS_SAFE_ASSERT_RECOVER_NOOP(fileName.isEmpty() || info.isAbsolute());
195 m_d->audioChannelFileName = fileName.isEmpty() ? fileName : info.absoluteFilePath();
196
197 emit sigAudioChannelChanged();
198 }
199
isAudioMuted() const200 bool KisImageAnimationInterface::isAudioMuted() const
201 {
202 return m_d->audioChannelMuted;
203 }
204
setAudioMuted(bool value)205 void KisImageAnimationInterface::setAudioMuted(bool value)
206 {
207 m_d->audioChannelMuted = value;
208 emit sigAudioChannelChanged();
209 }
210
audioVolume() const211 qreal KisImageAnimationInterface::audioVolume() const
212 {
213 return m_d->audioChannelVolume;
214 }
215
setAudioVolume(qreal value)216 void KisImageAnimationInterface::setAudioVolume(qreal value)
217 {
218 m_d->audioChannelVolume = value;
219 emit sigAudioVolumeChanged();
220 }
221
setFramerate(int fps)222 void KisImageAnimationInterface::setFramerate(int fps)
223 {
224 m_d->framerate = fps;
225 emit sigFramerateChanged();
226 }
227
image() const228 KisImageWSP KisImageAnimationInterface::image() const
229 {
230 return m_d->image;
231 }
232
externalFrameActive() const233 bool KisImageAnimationInterface::externalFrameActive() const
234 {
235 return m_d->externalFrameActive;
236 }
237
requestTimeSwitchWithUndo(int time)238 void KisImageAnimationInterface::requestTimeSwitchWithUndo(int time)
239 {
240 if (currentUITime() == time) return;
241 requestTimeSwitchNonGUI(time, true);
242 }
243
setDefaultProjectionColor(const KoColor & color)244 void KisImageAnimationInterface::setDefaultProjectionColor(const KoColor &color)
245 {
246 int savedTime = 0;
247 saveAndResetCurrentTime(currentTime(), &savedTime);
248
249 m_d->image->setDefaultProjectionColor(color);
250
251 restoreCurrentTime(&savedTime);
252 }
253
requestTimeSwitchNonGUI(int time,bool useUndo)254 void KisImageAnimationInterface::requestTimeSwitchNonGUI(int time, bool useUndo)
255 {
256 emit sigInternalRequestTimeSwitch(time, useUndo);
257 }
258
explicitlySetCurrentTime(int frameId)259 void KisImageAnimationInterface::explicitlySetCurrentTime(int frameId)
260 {
261 m_d->setCurrentTime(frameId);
262 }
263
switchCurrentTimeAsync(int frameId,bool useUndo)264 void KisImageAnimationInterface::switchCurrentTimeAsync(int frameId, bool useUndo)
265 {
266 if (currentUITime() == frameId) return;
267
268 const KisTimeRange range = KisTimeRange::calculateIdenticalFramesRecursive(m_d->image->root(), currentUITime());
269 const bool needsRegeneration = !range.contains(frameId);
270
271 KisSwitchTimeStrokeStrategy::SharedTokenSP token =
272 m_d->switchToken.toStrongRef();
273
274 if (!token || !token->tryResetDestinationTime(frameId, needsRegeneration)) {
275
276 {
277 KisPostExecutionUndoAdapter *undoAdapter = useUndo ?
278 m_d->image->postExecutionUndoAdapter() : 0;
279
280 KisSwitchTimeStrokeStrategy *strategy =
281 new KisSwitchTimeStrokeStrategy(frameId, needsRegeneration,
282 this, undoAdapter);
283
284 m_d->switchToken = strategy->token();
285
286 KisStrokeId stroke = m_d->image->startStroke(strategy);
287 m_d->image->endStroke(stroke);
288 }
289
290 if (needsRegeneration) {
291 KisStrokeStrategy *strategy =
292 new KisRegenerateFrameStrokeStrategy(this);
293
294 KisStrokeId strokeId = m_d->image->startStroke(strategy);
295 m_d->image->endStroke(strokeId);
296 }
297
298 }
299
300 m_d->setCurrentUITime(frameId);
301 emit sigUiTimeChanged(frameId);
302 }
303
requestFrameRegeneration(int frameId,const KisRegion & dirtyRegion)304 void KisImageAnimationInterface::requestFrameRegeneration(int frameId, const KisRegion &dirtyRegion)
305 {
306 KisStrokeStrategy *strategy =
307 new KisRegenerateFrameStrokeStrategy(frameId,
308 dirtyRegion,
309 this);
310
311 QList<KisStrokeJobData*> jobs = KisRegenerateFrameStrokeStrategy::createJobsData(m_d->image);
312
313 KisStrokeId stroke = m_d->image->startStroke(strategy);
314 Q_FOREACH (KisStrokeJobData* job, jobs) {
315 m_d->image->addJob(stroke, job);
316 }
317 m_d->image->endStroke(stroke);
318 }
319
saveAndResetCurrentTime(int frameId,int * savedValue)320 void KisImageAnimationInterface::saveAndResetCurrentTime(int frameId, int *savedValue)
321 {
322 m_d->externalFrameActive = true;
323 *savedValue = m_d->currentTime();
324 m_d->setCurrentTime(frameId);
325 }
326
restoreCurrentTime(int * savedValue)327 void KisImageAnimationInterface::restoreCurrentTime(int *savedValue)
328 {
329 m_d->setCurrentTime(*savedValue);
330 m_d->externalFrameActive = false;
331 }
332
notifyFrameReady()333 void KisImageAnimationInterface::notifyFrameReady()
334 {
335 emit sigFrameReady(m_d->currentTime());
336 }
337
notifyFrameCancelled()338 void KisImageAnimationInterface::notifyFrameCancelled()
339 {
340 emit sigFrameCancelled();
341 }
342
updatesFacade() const343 KisUpdatesFacade* KisImageAnimationInterface::updatesFacade() const
344 {
345 return m_d->image;
346 }
347
notifyNodeChanged(const KisNode * node,const QRect & rect,bool recursive)348 void KisImageAnimationInterface::notifyNodeChanged(const KisNode *node,
349 const QRect &rect,
350 bool recursive)
351 {
352 notifyNodeChanged(node, QVector<QRect>({rect}), recursive);
353 }
354
notifyNodeChanged(const KisNode * node,const QVector<QRect> & rects,bool recursive)355 void KisImageAnimationInterface::notifyNodeChanged(const KisNode *node,
356 const QVector<QRect> &rects,
357 bool recursive)
358 {
359 if (externalFrameActive() || m_d->frameInvalidationBlocked) return;
360
361 // even overlay selection masks are not rendered in the cache
362 if (node->inherits("KisSelectionMask")) return;
363
364 const int currentTime = m_d->currentTime();
365 KisTimeRange invalidateRange;
366
367 if (recursive) {
368 invalidateRange = KisTimeRange::calculateAffectedFramesRecursive(node, currentTime);
369 } else {
370 invalidateRange = KisTimeRange::calculateNodeAffectedFrames(node, currentTime);
371 }
372
373 // we compress the updated rect (atm, no one uses it anyway)
374 QRect unitedRect;
375 Q_FOREACH (const QRect &rc, rects) {
376 unitedRect |= rc;
377 }
378
379 invalidateFrames(invalidateRange, unitedRect);
380 }
381
invalidateFrames(const KisTimeRange & range,const QRect & rect)382 void KisImageAnimationInterface::invalidateFrames(const KisTimeRange &range, const QRect &rect)
383 {
384 m_d->cachedLastFrameValue = -1;
385 emit sigFramesChanged(range, rect);
386 }
387
blockFrameInvalidation(bool value)388 void KisImageAnimationInterface::blockFrameInvalidation(bool value)
389 {
390 m_d->frameInvalidationBlocked = value;
391 }
392
findLastKeyframeTimeRecursive(KisNodeSP node)393 int findLastKeyframeTimeRecursive(KisNodeSP node)
394 {
395 int time = 0;
396
397 KisKeyframeChannel *channel;
398 Q_FOREACH (channel, node->keyframeChannels()) {
399 KisKeyframeSP keyframe = channel->lastKeyframe();
400 if (keyframe) {
401 time = std::max(time, keyframe->time());
402 }
403 }
404
405 KisNodeSP child = node->firstChild();
406 while (child) {
407 time = std::max(time, findLastKeyframeTimeRecursive(child));
408 child = child->nextSibling();
409 }
410
411 return time;
412 }
413
totalLength()414 int KisImageAnimationInterface::totalLength()
415 {
416 if (m_d->cachedLastFrameValue < 0) {
417 m_d->cachedLastFrameValue = findLastKeyframeTimeRecursive(m_d->image->root());
418 }
419
420 int lastKey = m_d->cachedLastFrameValue;
421
422 lastKey = std::max(lastKey, m_d->fullClipRange.end());
423 lastKey = std::max(lastKey, m_d->currentUITime());
424
425 return lastKey + 1;
426 }
427