Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_keyframe_channel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
3 * SPDX-FileCopyrightText: 2020 Emmet O 'Neill <emmetoneill.pdx@gmail.com>
4 * SPDX-FileCopyrightText: 2020 Eoin O 'Neill <eoinoneill1991@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
10#include "KoID.h"
11#include "kis_global.h"
12#include "kis_node.h"
13#include "kis_time_span.h"
14#include "kundo2command.h"
15#include "kis_image.h"
19#include "kis_mask.h"
20#include "kis_image.h"
21#include "kis_command_utils.h"
22
23#include <QMap>
24
25
26const KoID KisKeyframeChannel::Raster = KoID("content", ki18n("Content"));
27const KoID KisKeyframeChannel::Opacity = KoID("opacity", ki18n("Opacity"));
28const KoID KisKeyframeChannel::PositionX = KoID("transform_pos_x", ki18n("Position (X)"));
29const KoID KisKeyframeChannel::PositionY = KoID("transform_pos_y", ki18n("Position (Y)"));
30const KoID KisKeyframeChannel::ScaleX = KoID("transform_scale_x", ki18n("Scale (X)"));
31const KoID KisKeyframeChannel::ScaleY = KoID("transform_scale_y", ki18n("Scale (Y)"));
32const KoID KisKeyframeChannel::ShearX = KoID("transform_shear_x", ki18n("Shear (X)"));
33const KoID KisKeyframeChannel::ShearY = KoID("transform_shear_y", ki18n("Shear (Y)"));
34const KoID KisKeyframeChannel::RotationX = KoID("transform_rotation_x", ki18n("Rotation (X)"));
35const KoID KisKeyframeChannel::RotationY = KoID("transform_rotation_y", ki18n("Rotation (Y)"));
36const KoID KisKeyframeChannel::RotationZ = KoID("transform_rotation_z", ki18n("Rotation (Z)"));
37
39{
40 KoID channelId;
41
42 if (id == KisKeyframeChannel::Raster.id()) {
44 } else if (id == KisKeyframeChannel::Opacity.id()) {
46 } else if (id == KisKeyframeChannel::PositionX.id()) {
48 } else if (id == KisKeyframeChannel::PositionY.id()) {
50 } else if (id == KisKeyframeChannel::ScaleX.id()) {
52 } else if (id == KisKeyframeChannel::ScaleY.id()) {
54 } else if (id == KisKeyframeChannel::ShearX.id()) {
56 } else if (id == KisKeyframeChannel::ShearY.id()) {
58 } else if (id == KisKeyframeChannel::RotationX.id()) {
60 } else if (id == KisKeyframeChannel::RotationY.id()) {
62 } else if (id == KisKeyframeChannel::RotationZ.id()) {
64
65 } else {
66 channelId = KoID();
67 }
68
69 return channelId;
70}
71
72
74{
75 Private(const KoID &temp_id, KisDefaultBoundsBaseSP tmp_bounds) {
76 bounds = tmp_bounds;
77 id = temp_id;
78 }
79
80 Private(const Private &rhs) {
81 id = rhs.id;
83 }
84
86 QMap<int, KisKeyframeSP> keys;
91};
92
93
95 : m_d(new Private(id, bounds))
96{
97 // Added keyframes should fire channel updated signal..
99 Q_EMIT sigAnyKeyframeChange();
100 });
101
103 Q_EMIT sigAnyKeyframeChange();
104 });
105
107 Q_EMIT sigAnyKeyframeChange();
108 });
109}
110
112 : KisKeyframeChannel(rhs.m_d->id, new KisDefaultBounds(nullptr))
113{
114 m_d.reset(new Private(*rhs.m_d));
115}
116
120
121void KisKeyframeChannel::addKeyframe(int time, KUndo2Command *parentUndoCmd)
122{
123 KisKeyframeSP keyframe = createKeyframe();
124 insertKeyframe(time, keyframe, parentUndoCmd);
125}
126
127void KisKeyframeChannel::insertKeyframe(int time, KisKeyframeSP keyframe, KUndo2Command *parentUndoCmd)
128{
129 KIS_ASSERT(time >= 0);
130 KIS_ASSERT(keyframe);
131
132 if (m_d->keys.contains(time)) {
133 // Properly remove overwritten frames.
134 removeKeyframe(time, parentUndoCmd);
135 }
136
137 if (parentUndoCmd) {
138 KUndo2Command* cmd =
140 new KisInsertKeyframeCommand(this, time, keyframe), parentUndoCmd);
141 Q_UNUSED(cmd);
142 }
143
144 m_d->keys.insert(time, keyframe);
145 Q_EMIT sigAddedKeyframe(this, time);
146}
147
149{
150 if (parentUndoCmd) {
151 KUndo2Command* cmd =
153 new KisRemoveKeyframeCommand(this, time), parentUndoCmd);
154 Q_UNUSED(cmd);
155 }
156
157 m_d->keys.remove(time);
158 Q_EMIT sigKeyframeHasBeenRemoved(this, time);
159}
160
162{
163 Q_EMIT sigKeyframeAboutToBeRemoved(this, time);
164 removeKeyframeImpl(time, parentUndoCmd);
165}
166
167void KisKeyframeChannel::moveKeyframe(KisKeyframeChannel *sourceChannel, int sourceTime, KisKeyframeChannel *targetChannel, int targetTime, KUndo2Command *parentUndoCmd)
168{
169 KIS_ASSERT(sourceChannel && targetChannel);
170
171 KisKeyframeSP sourceKeyframe = sourceChannel->keyframeAt(sourceTime);
172 if (!sourceKeyframe) return;
173
174 sourceChannel->removeKeyframe(sourceTime, parentUndoCmd);
175
176 KisKeyframeSP targetKeyframe = sourceKeyframe;
177 if (sourceChannel != targetChannel) {
178 // When "moving" Keyframes between channels, a new copy is made for that channel.
179 targetKeyframe = sourceKeyframe->duplicate(targetChannel);
180 KIS_SAFE_ASSERT_RECOVER_RETURN(targetKeyframe);
181 }
182
183 targetChannel->insertKeyframe(targetTime, targetKeyframe, parentUndoCmd);
184}
185
186void KisKeyframeChannel::copyKeyframe(const KisKeyframeChannel *sourceChannel, int sourceTime, KisKeyframeChannel *targetChannel, int targetTime, KUndo2Command* parentUndoCmd)
187{
188 KIS_ASSERT(sourceChannel && targetChannel);
189
190 KisKeyframeSP sourceKeyframe = sourceChannel->keyframeAt(sourceTime);
191 if (!sourceKeyframe) return;
192
193 KisKeyframeSP copiedKeyframe = sourceKeyframe->duplicate(targetChannel);
194 KIS_SAFE_ASSERT_RECOVER_RETURN(copiedKeyframe);
195
196 targetChannel->insertKeyframe(targetTime, copiedKeyframe, parentUndoCmd);
197}
198
199void KisKeyframeChannel::swapKeyframes(KisKeyframeChannel *channelA, int timeA, KisKeyframeChannel *channelB, int timeB, KUndo2Command *parentUndoCmd)
200{
201 KIS_ASSERT(channelA && channelB);
202
203 // Store B.
204 KisKeyframeSP keyframeB = channelB->keyframeAt(timeB);
205 if (!keyframeB) return;
206
207 // Move A -> B
208 moveKeyframe(channelA, timeA, channelB, timeB, parentUndoCmd);
209
210 // Insert B -> A
211 if (channelA != channelB) {
212 keyframeB = keyframeB->duplicate(channelA);
214 }
215 channelA->insertKeyframe(timeA, keyframeB, parentUndoCmd);
216}
217
219{
220 QMap<int, KisKeyframeSP>::const_iterator iter = m_d->keys.constFind(time);
221 if (iter != m_d->keys.constEnd()) {
222 return iter.value();
223 } else {
224 return KisKeyframeSP();
225 }
226}
227
229{
230 return m_d->keys.count();
231}
232
234{
235 QMap<int, KisKeyframeSP>::const_iterator iter = const_cast<const QMap<int, KisKeyframeSP>*>(&m_d->keys)->upperBound(time);
236
237 // If the next keyframe is the first keyframe, that means there's no active frame.
238 if (iter == m_d->keys.constBegin()) {
239 return -1;
240 }
241
242 iter--;
243
244 if (iter == m_d->keys.constEnd()) {
245 return -1;
246 }
247
248 return iter.key();
249}
250
252{
253 int time = m_d->keys.key(toLookup, -1);
254 KIS_ASSERT(time >= 0);
255 return time;
256}
257
259{
260 if (m_d->keys.isEmpty()) {
261 return -1;
262 } else {
263 return m_d->keys.firstKey();
264 }
265}
266
268{
269 if (!keyframeAt(time)) {
270 return activeKeyframeTime(time);
271 }
272
273 QMap<int, KisKeyframeSP>::const_iterator iter = m_d->keys.constFind(time);
274
275 if (iter == m_d->keys.constBegin() || iter == m_d->keys.constEnd()) {
276 return -1;
277 }
278
279 iter--;
280 return iter.key();
281}
282
284{
285 QMap<int, KisKeyframeSP>::const_iterator iter = const_cast<const QMap<int, KisKeyframeSP>*>(&m_d->keys)->upperBound(time);
286
287 if (iter == m_d->keys.constEnd()) {
288 return -1;
289 }
290
291 return iter.key();
292}
293
295{
296 if (m_d->keys.isEmpty()) {
297 return -1;
298 }
299
300 return m_d->keys.lastKey();
301}
302
304{
305 QSet<int> frames;
306
307 TimeKeyframeMap::const_iterator it = m_d->keys.constBegin();
308 TimeKeyframeMap::const_iterator end = m_d->keys.constEnd();
309
310 while (it != end) {
311 frames.insert(it.key());
312 ++it;
313 }
314
315 return frames;
316}
317
319{
320 return m_d->id.id();
321}
322
324{
325 return m_d->id.name();
326}
327
347
349{
350 return m_d->parentNode;
351}
352
356
358{
359 TimeKeyframeMap::const_iterator it = m_d->keys.constBegin();
360 TimeKeyframeMap::const_iterator end = m_d->keys.constEnd();
361
362 int hash = 0;
363
364 while (it != end) {
365 hash += it.key();
366 ++it;
367 }
368
369 return hash;
370}
371
373{
374 if (m_d->keys.isEmpty()) return KisTimeSpan::infinite(0);
375
376 const int activeKeyTime = activeKeyframeTime(time);
377 const int nextKeyTime = nextKeyframeTime(time);
378
379 // Check for keyframe behind..
380 if (!keyframeAt(activeKeyTime)) {
381 return KisTimeSpan::fromTimeToTime(0, nextKeyTime - 1);
382 }
383
384 // Check for keyframe ahead..
385 if (!keyframeAt(nextKeyTime)) {
386 return KisTimeSpan::infinite(activeKeyTime);
387 }
388
389 return KisTimeSpan::fromTimeToTime(activeKeyTime, nextKeyTime - 1);
390}
391
393{
394 return affectedFrames(time);
395}
396
397QDomElement KisKeyframeChannel::toXML(QDomDocument doc, const QString &layerFilename)
398{
399 QDomElement channelElement = doc.createElement("channel");
400
401 channelElement.setAttribute("name", id());
402
403 Q_FOREACH (int time, m_d->keys.keys()) {
404 QDomElement keyframeElement = doc.createElement("keyframe");
405 KisKeyframeSP keyframe = keyframeAt(time);
406
407 keyframeElement.setAttribute("time", time);
408 keyframeElement.setAttribute("color-label", keyframe->colorLabel());
409
410 saveKeyframe(keyframe, keyframeElement, layerFilename);
411
412 channelElement.appendChild(keyframeElement);
413 }
414
415 return channelElement;
416}
417
418void KisKeyframeChannel::loadXML(const QDomElement &channelNode)
419{
420 for (QDomElement keyframeNode = channelNode.firstChildElement(); !keyframeNode.isNull(); keyframeNode = keyframeNode.nextSiblingElement()) {
421 if (keyframeNode.nodeName().toUpper() != "KEYFRAME") continue;
422
423 QPair<int, KisKeyframeSP> timeKeyPair = loadKeyframe(keyframeNode);
424 KIS_SAFE_ASSERT_RECOVER(timeKeyPair.second) { continue; }
425
426 if (keyframeNode.hasAttribute("color-label")) {
427 timeKeyPair.second->setColorLabel(keyframeNode.attribute("color-label").toUInt());
428 }
429
430 insertKeyframe(timeKeyPair.first, timeKeyPair.second);
431 }
432}
433
438
443
445{
446 return m_d->bounds->currentTime();
447}
448
450{
451 if (*time < 0) {
452 qWarning() << "WARNING: Loading a file with negative animation frames!";
453 qWarning() << " The file has been saved with a buggy version of Krita.";
454 qWarning() << " All the frames with negative ids will be dropped!";
455 qWarning() << " " << ppVar(this->id()) << ppVar(*time);
456
457 m_d->haveBrokenFrameTimeBug = true;
458 *time = 0;
459 }
460
461 if (m_d->haveBrokenFrameTimeBug) {
462 while (keyframeAt(*time)) {
463 (*time)++;
464 }
465 }
466}
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
QSet< int > allKeyframeTimes() const
Get a set of all integer times that map to a keyframe.
void setDefaultBounds(KisDefaultBoundsBaseSP bounds)
virtual KisTimeSpan identicalFrames(int time) const
Get a span of times for which the channel gives identical results compared to frame at a given time....
TimeKeyframeMap & keys()
static const KoID RotationY
static const KoID Raster
virtual KisKeyframeSP createKeyframe()=0
Virtual keyframe creation function. Derived classes implement this function based on the needs of the...
int previousKeyframeTime(const int time) const
virtual void insertKeyframe(int time, KisKeyframeSP keyframe, KUndo2Command *parentUndoCmd=nullptr)
Insert an existing keyframe into the channel at the specified time.
virtual void removeKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr)
Remove a keyframe from the channel at the specified time.
static const KoID ScaleY
static void swapKeyframes(KisKeyframeChannel *channelA, int timeA, KisKeyframeChannel *channelB, int timeB, KUndo2Command *parentUndoCmd=nullptr)
Swap two keyframes across channel(s) at the specified times.
void sigAnyKeyframeChange()
KisKeyframeSP keyframeAt(int time) const
Get a keyframe at specified time. Used primarily when the value of a given keyframe is needed.
static void copyKeyframe(const KisKeyframeChannel *sourceChannel, int sourceTime, KisKeyframeChannel *targetChannel, int targetTime, KUndo2Command *parentUndoCmd=nullptr)
Copy a keyframe across channel(s) at the specified times.
virtual QPair< int, KisKeyframeSP > loadKeyframe(const QDomElement &keyframeNode)=0
static const KoID RotationZ
virtual void loadXML(const QDomElement &channelNode)
QMap< int, KisKeyframeSP > TimeKeyframeMap
static const KoID Opacity
static KoID channelIdToKoId(const QString &id)
virtual QDomElement toXML(QDomDocument doc, const QString &layerFilename)
const TimeKeyframeMap & constKeys() const
QScopedPointer< Private > m_d
int channelHash() const
Calculates a pseudo-unique hash based on the relevant internal state of the channel.
static const KoID ScaleX
void addKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr)
Add a new keyframe to the channel at the specified time.
virtual void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename)=0
virtual void removeKeyframeImpl(int time, KUndo2Command *parentUndoCmd)
static const KoID RotationX
void sigKeyframeHasBeenRemoved(const KisKeyframeChannel *channel, int time)
This signal is emitted just AFTER a keyframe is removed from the channel.
virtual KisTimeSpan affectedFrames(int time) const
Get the set of frames affected by any changes to the value or content of the active keyframe at the g...
void setNode(KisNodeWSP node)
void sigKeyframeAboutToBeRemoved(const KisKeyframeChannel *channel, int time)
This signal is emitted just BEFORE a keyframe is removed from the channel.
static const KoID PositionX
Q_DECL_DEPRECATED void workaroundBrokenFrameTimeBug(int *time)
Between Krita 4.1 and 4.4 Krita had a bug which resulted in creating frames with negative time stamp....
static const KoID PositionY
KisKeyframeChannel(const KoID &id, KisDefaultBoundsBaseSP bounds)
void sigKeyframeChanged(const KisKeyframeChannel *channel, int time)
This signal is emitted just AFTER a non-raster keyframe was changed its value.
int nextKeyframeTime(const int time) const
static const KoID ShearX
static void moveKeyframe(KisKeyframeChannel *sourceChannel, int sourceTime, KisKeyframeChannel *targetChannel, int targetTime, KUndo2Command *parentUndoCmd=nullptr)
Move a keyframe across channel(s) at the specified times.
int lookupKeyframeTime(KisKeyframeSP toLookup)
Search for the time for a given keyframe. (Reverse map lookup, so use sparingly.)
static const KoID ShearY
void sigAddedKeyframe(const KisKeyframeChannel *channel, int time)
This signal is emitted just AFTER a keyframe was added to the channel.
static KisTimeSpan infinite(int start)
static KisTimeSpan fromTimeToTime(int start, int end)
Definition KoID.h:30
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define bounds(x, a, b)
#define ppVar(var)
Definition kis_debug.h:155
KisSharedPtr< KisDefaultBoundsNodeWrapper > KisDefaultBoundsNodeWrapperSP
QSharedPointer< KisKeyframe > KisKeyframeSP
Definition kis_types.h:296
QMap< int, KisKeyframeSP > keys
Private(const KoID &temp_id, KisDefaultBoundsBaseSP tmp_bounds)
virtual void handleKeyframeChannelFrameAdded(const KisKeyframeChannel *channel, int time)
Definition kis_node.cpp:647
void handleKeyframeChannelFrameHasBeenRemoved(const KisKeyframeChannel *channel, int time)
Definition kis_node.cpp:663
void handleKeyframeChannelFrameAboutToBeRemoved(const KisKeyframeChannel *channel, int time)
Definition kis_node.cpp:657
virtual void handleKeyframeChannelFrameChange(const KisKeyframeChannel *channel, int time)
Definition kis_node.cpp:642