Krita Source Code Documentation
Loading...
Searching...
No Matches
KisCanvasAnimationState.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
3 * SPDX-FileCopyrightText: 2021 Eoin O'Neill <eoinoneill1991@gmail.com>
4 * SPDX-FileCopyrightText: 2021 Emmet O'Neill <emmetoneill.pdx@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
10
11#include <QTimer>
12#include <QtMath>
13
14#include "kis_global.h"
15#include "kis_algebra_2d.h"
16
17#include "kis_config.h"
18#include "kis_config_notifier.h"
19#include "kis_image.h"
20#include "kis_canvas2.h"
24#include "kis_time_span.h"
27#include <KisDocument.h>
28#include <QFileInfo>
29#include <QThread>
31#include "KisImageBarrierLock.h"
32#include "kis_layer_utils.h"
35#include "kis_algebra_2d.h"
36#include "KisPlaybackEngine.h"
37
38#include "kis_image_config.h"
39#include <limits>
40
41#include "KisViewManager.h"
42#include "kis_icon_utils.h"
43
44#include "KisPart.h"
48
49#include <atomic>
50
51class SingleShotSignal : public QObject {
52 Q_OBJECT
53public:
54 SingleShotSignal(QObject* parent = nullptr)
55 : QObject(parent)
56 , lock(false)
57 {
58 }
59
61
62public Q_SLOTS:
63 void tryFire() {
64 if (!lock) {
65 lock = true;
66 Q_EMIT output();
67 }
68 }
69
70Q_SIGNALS:
71 void output();
72
73private:
74 bool lock;
75
76};
77
78
79//=====
80
87class CanvasPlaybackEnvironment : public QObject {
88 Q_OBJECT
89public:
91 : QObject(parent)
93 , m_canvas(canvas)
94 {
95 connect(&m_cancelTrigger, SIGNAL(output()), parent, SIGNAL(sigCancelPlayback()));
96
97 prepare();
98 }
99
103
107
108 int originFrame() { return m_originFrame; }
109
111 return m_playbackRange;
112 }
113
114 void setPlaybackRange(KisTimeSpan p_playbackRange) {
115 m_playbackRange = p_playbackRange;
116 }
117
118 void prepare()
119 {
120 KIS_ASSERT(m_canvas); // Sanity check...
121
123 setPlaybackRange(range);
124
125 // Initialize and optimize playback environment...
126 if (m_canvas->frameCache()) {
127 KisImageConfig cfg(true);
128
129 const int dimensionLimit = cfg.useAnimationCacheFrameSizeLimit() ?
130 cfg.animationCacheFrameSizeLimit() : std::numeric_limits<int>::max();
131
132 const int largestDimension = KisAlgebra2D::maxDimension(m_canvas->image()->bounds());
133
134 const QRect regionOfInterest =
135 cfg.useAnimationCacheRegionOfInterest() && largestDimension > dimensionLimit ?
137
138 const QRect minimalRect =
141
142 m_canvas->frameCache()->dropLowQualityFrames(range, regionOfInterest, minimalRect);
143 m_canvas->setRenderingLimit(regionOfInterest);
144
145 // Preemptively cache all frames...
147 dlg.setRegionOfInterest(regionOfInterest);
149 } else {
150 KisImageBarrierLock lock(m_canvas->image());
152 KisDecoratedNodeInterface* decoratedNode = dynamic_cast<KisDecoratedNodeInterface*>(node.data());
153 if (decoratedNode && decoratedNode->decorationsVisible()) {
154 decoratedNode->setDecorationsVisible(false, false);
155 m_disabledDecoratedNodes.append(node);
156 }
157 });
158 }
159
160 // Setup appropriate interrupt connections...
162 m_canvas->image().data(), SIGNAL(sigUndoDuringStrokeRequested()),
163 &m_cancelTrigger, SLOT(tryFire()));
164
166 m_canvas->image().data(), SIGNAL(sigStrokeCancellationRequested()),
167 &m_cancelTrigger, SLOT(tryFire()));
168
169 // We only want to stop on stroke end when running on a system
170 // without cache / opengl / graphics driver support!
171 if (m_canvas->frameCache()) {
173 m_canvas->image().data(), SIGNAL(sigStrokeEndRequested()),
174 &m_cancelTrigger, SLOT(tryFire()));
175 }
176 }
177
178 void restore() {
179 m_cancelStrokeConnections.clear();
180
181 if (m_canvas) {
182 if (m_canvas->frameCache()) {
183 m_canvas->setRenderingLimit(QRect());
184 } else {
185 KisImageBarrierLock lock(m_canvas->image());
186 Q_FOREACH(KisNodeWSP disabledNode, m_disabledDecoratedNodes) {
187 KisDecoratedNodeInterface* decoratedNode = dynamic_cast<KisDecoratedNodeInterface*>(disabledNode.data());
188 if (decoratedNode) {
189 decoratedNode->setDecorationsVisible(true, true);
190 }
191 }
192 m_disabledDecoratedNodes.clear();
193 }
194 }
195 }
196
197Q_SIGNALS:
199
200private:
205
207
209};
210
211// Needed for QObject definition outside of header file.
212#include "KisCanvasAnimationState.moc"
213
215{
216public:
218 : canvas(p_canvas)
219 , displayProxy( new KisFrameDisplayProxy(p_canvas) )
220 , playbackEnvironment( nullptr )
221 {
222 m_statsTimer.setInterval(1000);
223 }
224
227 QScopedPointer<KisFrameDisplayProxy> displayProxy;
228 QScopedPointer<QFileInfo> media; // TODO: Should we just get this from the document instead?
229 QScopedPointer<CanvasPlaybackEnvironment> playbackEnvironment;
230
232
233 qreal playbackSpeed {1.0};
234};
235
237 : QObject(canvas)
238 , m_d(new Private(canvas))
239{
241
242 // Handle image-internal frame change case...
243 connect(m_d->displayProxy.data(), SIGNAL(sigFrameChange()), this, SIGNAL(sigFrameChanged()));
244
245 // Grow to new playback range when new frames added (configurable)...
246 connect(m_d->canvas->image()->animationInterface(), &KisImageAnimationInterface::sigKeyframeAdded, this, [this](const KisKeyframeChannel*, int time){
247 if (m_d->canvas && m_d->canvas->image()) {
248 KisImageAnimationInterface* animInterface = m_d->canvas->image()->animationInterface();
249 KisConfig cfg(true);
250 if (animInterface && cfg.adaptivePlaybackRange()) {
251 KisTimeSpan desiredPlaybackRange = animInterface->documentPlaybackRange();
252 desiredPlaybackRange.include(time);
253 animInterface->setDocumentRange(desiredPlaybackRange);
254 }
255 }
256 });
257
258 connect(m_d->canvas->imageView()->document(), &KisDocument::sigAudioTracksChanged, this, &KisCanvasAnimationState::setupAudioTracks);
260 connect(&m_d->m_statsTimer, SIGNAL(timeout()), this, SIGNAL(sigPlaybackStatisticsUpdated()));
261}
262
266
271
272boost::optional<QFileInfo> KisCanvasAnimationState::mediaInfo()
273{
274 if (m_d->media) {
275 return boost::optional<QFileInfo>(*m_d->media);
276 } else {
277 return boost::none;
278 }
279}
280
282{
283 if (m_d->canvas && m_d->canvas->imageView() && m_d->canvas->imageView()->document()) {
284 return m_d->canvas->imageView()->document()->getAudioLevel();
285 } else {
286 return 0.0;
287 }
288}
289
291{
292 if (m_d->playbackEnvironment) {
293 return boost::optional<int>(m_d->playbackEnvironment->originFrame());
294 } else {
295 return boost::none;
296 }
297}
298
300{
301 return m_d->displayProxy.data();
302}
303
305{
306 if (!qFuzzyCompare(value, m_d->playbackSpeed)) {
307 m_d->playbackSpeed = value;
309 }
310}
311
313{
314 return m_d->playbackSpeed;
315}
316
318{
319 if (!m_d->canvas || !m_d->canvas->imageView()) {
320 return;
321 }
322
323 KisDocument* doc = m_d->canvas->imageView()->document();
324 if (doc) {
325 QVector<QFileInfo> files = doc->getAudioTracks();
326
327 if (doc->getAudioTracks().isEmpty()) {
328 m_d->media.reset();
329 } else {
330 //Only get first file for now and make that a producer...
331 QFileInfo toLoad = files.first();
332 KIS_SAFE_ASSERT_RECOVER_RETURN(toLoad.exists());
333 m_d->media.reset(new QFileInfo(toLoad));
334
335 // Once media is attached we upgrade to the MLT-based playbackEngine...
337 }
338
340 }
341}
342
343void KisCanvasAnimationState::showFrame(int frame, bool finalize)
344{
345 m_d->displayProxy->displayFrame(frame, finalize);
346}
347
349{
350 if (!m_d->canvas || !m_d->canvas->image()) {
351 return KisTimeSpan::infinite(0);
352 }
353
354 const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
355 return animation->activePlaybackRange();
356}
357
359{
360 if (m_d->state != p_state) {
361 m_d->state = p_state;
362 if (m_d->state == PLAYING) {
363 if (m_d->playbackEnvironment) {
364 m_d->playbackEnvironment->prepare(); // re-prepare
365 } else {
366 m_d->playbackEnvironment.reset(new CanvasPlaybackEnvironment(m_d->displayProxy->activeFrame(), m_d->canvas, this));
367
368 connect(m_d->playbackEnvironment.data(), SIGNAL(sigPlaybackStatisticsUpdated()),
369 this, SIGNAL(sigPlaybackStatisticsUpdated()));
370 }
371
372 m_d->m_statsTimer.start();
374 } else {
375 if (m_d->state == STOPPED) {
376 m_d->playbackEnvironment.reset();
377 } else if(m_d->playbackEnvironment) {
378 m_d->playbackEnvironment->restore();
379 }
380
381 m_d->m_statsTimer.stop();
383 }
384
385 Q_EMIT sigPlaybackStateChanged(m_d->state);
386 }
387}
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
PlaybackEnvironment (Private) Constructs and deconstructs the necessary viewing conditions when anima...
QVector< KisNodeWSP > m_disabledDecoratedNodes
CanvasPlaybackEnvironment & operator=(const CanvasPlaybackEnvironment &)=delete
KisSignalAutoConnectionsStore m_cancelStrokeConnections
CanvasPlaybackEnvironment(int originFrame, KisCanvas2 *canvas, KisCanvasAnimationState *parent=nullptr)
void setPlaybackRange(KisTimeSpan p_playbackRange)
CanvasPlaybackEnvironment(const CanvasPlaybackEnvironment &)=delete
int m_originFrame
The frame user started playback from.
void dropLowQualityFrames(const KisTimeSpan &range, const QRect &regionOfInterest, const QRect &minimalRect)
virtual Result regenerateRange(KisViewManager *viewManager)
start generation of frames and (if not in batch mode) show the dialog
QRect regionOfInterest
KisAnimationFrameCacheSP frameCache
KisCoordinatesConverter * coordinatesConverter
KisImageWSP image() const
KisViewManager * viewManager() const
QPointer< KisView > imageView() const
void setRenderingLimit(const QRect &rc)
The KisCanvasAnimationState class stores all of the canvas-specific animation state.
void sigAudioLevelChanged(qreal value)
void sigPlaybackStateChanged(PlaybackState state)
QScopedPointer< Private > m_d
boost::optional< QFileInfo > mediaInfo()
Get the media file info associated with this canvas, if available.
class KisFrameDisplayProxy * displayProxy()
void setPlaybackState(PlaybackState state)
setPlaybackState changes the animation playback state for this canvas. KisPlaybackEngine should respo...
void showFrame(int frame, bool finalize=false)
void setupAudioTracks()
Sets up the audio tracks for a given animation. (The only reason this is public is because we have to...
boost::optional< int > playbackOrigin()
Get the animation frame to return to (for this canvas) when playback is stopped, if available.
KisCanvasAnimationState(KisCanvas2 *canvas)
void sigPlaybackSpeedChanged(qreal value)
virtual void setDecorationsVisible(bool value, bool update)=0
void sigAudioLevelChanged(qreal level)
void sigAudioTracksChanged()
QVector< QFileInfo > getAudioTracks() const
The KisFrameDisplayProxy class sits between the KisCanvas (within its KisCanvasAnimationState) and it...
const KisTimeSpan & activePlaybackRange() const
activePlaybackRange
void sigKeyframeAdded(const KisKeyframeChannel *channel, int time)
bool useAnimationCacheFrameSizeLimit(bool defaultValue=false) const
int animationCacheFrameSizeLimit(bool defaultValue=false) const
bool useAnimationCacheRegionOfInterest(bool defaultValue=false) const
KisImageAnimationInterface * animationInterface() const
QRect bounds() const override
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
void upgradeToPlaybackEngineMLT(class KoCanvasBase *canvas)
Definition KisPart.cpp:691
static KisPart * instance()
Definition KisPart.cpp:131
void addConnection(Sender sender, Signal signal, Receiver receiver, Method method, Qt::ConnectionType type=Qt::AutoConnection)
static KisTimeSpan infinite(int start)
SingleShotSignal(QObject *parent=nullptr)
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
auto maxDimension(Size size) -> decltype(size.width())
void recursiveApplyNodes(NodePointer node, Functor func)
QScopedPointer< KisFrameDisplayProxy > displayProxy
QScopedPointer< CanvasPlaybackEnvironment > playbackEnvironment
KisCanvas2 * canvas