Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAnimUtils.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "KisAnimUtils.h"
8
9#include "kundo2command.h"
10#include "kis_algebra_2d.h"
11#include "kis_image.h"
12#include "kis_node.h"
17#include "kis_global.h"
18#include "kis_tool_utils.h"
20#include "kis_command_utils.h"
22#include "kis_transaction.h"
23
24
25namespace KisAnimUtils {
26 const QString lazyFrameCreationActionName = i18n("Auto Frame Mode");
27 const QString dropFramesActionName = i18n("Drop Frames");
28
29 const QString newLayerActionName = i18n("New Layer");
30 const QString pinExistingLayerActionName = i18n("Pin Existing Layer");
31 const QString removeLayerActionName = i18n("Remove Layer");
32
33 const QString addTransformKeyframeActionName = i18n("Add transform keyframe");
34 const QString removeTransformKeyframeActionName = i18n("Remove transform keyframe");
35
36 KUndo2Command* createKeyframeCommand(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy, KUndo2Command *parentCommand) {
38 copy ? kundo2_i18n("Copy Keyframe") :
39 kundo2_i18n("Add Keyframe"),
40 parentCommand,
41
42 [image, node, channelId, time, copy] () mutable -> KUndo2Command* {
43 bool success = false;
44
45 QScopedPointer<KUndo2Command> cmd(new KUndo2Command());
46
47 KisKeyframeChannel *channel = node->getKeyframeChannel(channelId);
48 quint8 originalOpacity = node->opacity();
49
50 // Try get channel...
51 bool channelCreated = false;
52 if (!channel) {
53 node->enableAnimation();
54 channel = node->getKeyframeChannel(channelId, true);
55
56 if (!channel) return nullptr;
57
58 channelCreated = true;
59 }
60
61 bool shouldPreserveCanvas = channelCreated && time == 0;
62
63 if ((copy || shouldPreserveCanvas) && channel->activeKeyframeAt(time)) {
64 if (!channel->keyframeAt(time)) {
65 channel->copyKeyframe(channel->activeKeyframeTime(time), time, cmd.data());
66 success = true;
67 }
68 } else {
69 bool clearExistingFrame = channel->keyframeAt(time) && !channelCreated;
70 if (clearExistingFrame && channelId == KisKeyframeChannel::Raster.id()) { // Overwrite existing keyframe with a new blank one...
73
74 // Clear the frame...
75 KisPaintDeviceSP device = node->paintDevice();
76 if (device) {
77 const QRect dirtyRect = device->extent();
78
79 KisTransaction transaction(kundo2_i18n("Clear"), device, cmd.data());
80 device->clear();
81 (void) transaction.endAndTake(); // saved as 'parent'
82
83 node->setDirty(dirtyRect);
84
85 success = true;
86 }
87 } else { // Make a regular new blank keyframe...
88 KisKeyframeSP referenceKey = channel->activeKeyframeAt(time);
89
90 bool isScalar = (channelId != KisKeyframeChannel::Raster.id());
91
92 if (isScalar && !referenceKey) {
98 const QSet<int> allTimes = channel->allKeyframeTimes();
99
100 auto it = std::min_element(allTimes.begin(), allTimes.end());
101 if (it != allTimes.end()) {
102 referenceKey = channel->keyframeAt(*it);
103 }
104 }
105
106 if (isScalar && referenceKey) {
107 KisScalarKeyframeChannel* scalarChannel = static_cast<KisScalarKeyframeChannel*>(channel);
108 const qreal value = scalarChannel->valueAt(time); //Get interpolated value.
109 scalarChannel->addScalarKeyframe(time, value, cmd.data());
110 } else {
111 channel->addKeyframe(time, cmd.data());
112 }
113
114 // Use color label of previous key, if exists...
115 if (referenceKey && channel->keyframeAt(time)) {
116 channel->keyframeAt(time)->setColorLabel(referenceKey->colorLabel());
117 }
118
119 success = true;
120 }
121 }
122
123 // when a new opacity keyframe is created, the opacity is set to 0
124 // this makes sure to use the opacity that was previously used
125 // maybe there is a better way to do this
126 node->setOpacity(originalOpacity);
127
128 return success ? cmd.take() : nullptr;
129 });
130
131 return cmd;
132 }
133
134 void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy)
135 {
136 KUndo2Command *cmd = createKeyframeCommand(image, node, channelId, time, copy);
140 }
141
142 void removeKeyframes(KisImageSP image, const FrameItemList &frames) {
144
146 kundo2_i18np("Remove Keyframe",
147 "Remove Keyframes",
148 frames.size()),
149
150 [image, frames] () {
151 bool result = false;
152
153 QScopedPointer<KUndo2Command> cmd(new KUndo2Command());
154
155 Q_FOREACH (const FrameItem &item, frames) {
156 const int time = item.time;
157 KisNodeSP node = item.node;
158 KisKeyframeChannel *channel = 0;
159 if (node) {
160 channel = node->getKeyframeChannel(item.channel);
161 }
162 if (!channel) continue;
163
164 KisKeyframeSP keyframe = channel->keyframeAt(time);
165 if (!keyframe) continue;
166
167 channel->removeKeyframe(time, cmd.data());
168
169 result = true;
170 }
171
172 return result ? cmd.take() : 0;
173 });
174
178 }
179
180 void removeKeyframe(KisImageSP image, KisNodeSP node, const QString &channel, int time) {
181 QVector<FrameItem> frames;
182 frames << FrameItem(node, channel, time);
183 removeKeyframes(image, frames);
184 }
185
186 void resetChannels(KisImageSP image, KisNodeSP node, const QList<QString> &channelIDs) {
187 QVector<FrameItem> frames;
188
189 Q_FOREACH( const QString& channelID, channelIDs) {
190 KisKeyframeChannel* channel = node->getKeyframeChannel(channelID, false);
191 if (!channel)
192 continue;
193
194 Q_FOREACH( const int& time, channel->allKeyframeTimes()) {
195 frames << FrameItem(node, channelID, time);
196 }
197 }
198
199 removeKeyframes(image, frames);
200 }
201
202 void resetChannel(KisImageSP image, KisNodeSP node, const QString &channelID) {
203 QList<QString> channels;
204 channels << channelID;
205 resetChannels(image, node, channels);
206
207 }
208
210 LessOperator(const QPoint &offset)
211 : m_columnCoeff(-KisAlgebra2D::signPZ(offset.x())),
212 m_rowCoeff(-1000000 * KisAlgebra2D::signZZ(offset.y()))
213 {
214 }
215
216 bool operator()(const QModelIndex &lhs, const QModelIndex &rhs) {
217 return
218 m_columnCoeff * lhs.column() + m_rowCoeff * lhs.row() <
219 m_columnCoeff * rhs.column() + m_rowCoeff * rhs.row();
220 }
221
222 private:
225 };
226
227 void sortPointsForSafeMove(QModelIndexList *points, const QPoint &offset)
228 {
229 std::sort(points->begin(), points->end(), LessOperator(offset));
230 }
231
233 {
234 return node->inherits("KisPaintLayer") || node->inherits("KisFilterMask") || node->inherits("KisTransparencyMask") || node->inherits("KisSelectionBasedLayer");
235 }
236
237 void swapOneFrameItem(const FrameItem &src, const FrameItem &dst, KUndo2Command *parentCommand)
238 {
239 const int srcTime = src.time;
240 KisNodeSP srcNode = src.node;
241 KisKeyframeChannel *srcChannel = srcNode->getKeyframeChannel(src.channel);
242
243 const int dstTime = dst.time;
244 KisNodeSP dstNode = dst.node;
245 KisKeyframeChannel *dstChannel = dstNode->getKeyframeChannel(dst.channel, true);
246
247 if (srcNode == dstNode) {
248 if (!srcChannel) return; // TODO: add warning!
249
250 srcChannel->swapKeyframes(srcTime, dstTime, parentCommand);
251 } else {
252 if (!srcChannel || !dstChannel) return; // TODO: add warning!
253
254 KisKeyframeChannel::swapKeyframes(srcChannel, srcTime, dstChannel, dstTime, parentCommand);
255 }
256 }
257
258 void moveOneFrameItem(const FrameItem &src, const FrameItem &dst, bool copy, bool moveEmptyFrames, KUndo2Command *parentCommand)
259 {
260 const int srcTime = src.time;
261 KisNodeSP srcNode = src.node;
262 KisKeyframeChannel *srcChannel = srcNode->getKeyframeChannel(src.channel);
263
264 const int dstTime = dst.time;
265 KisNodeSP dstNode = dst.node;
266 KisKeyframeChannel *dstChannel = dstNode->getKeyframeChannel(dst.channel, true);
267
268 if (srcNode == dstNode) {
269 if (!srcChannel) return; // TODO: add warning!
270
271 if (srcChannel->keyframeAt(srcTime)) {
272 if (copy) {
273 srcChannel->copyKeyframe(srcTime, dstTime, parentCommand);
274 } else {
275 srcChannel->moveKeyframe(srcTime, dstTime, parentCommand);
276 }
277 } else {
278 if (srcChannel->keyframeAt(dstTime) && moveEmptyFrames && !copy) {
279 //Destination is effectively replaced by an empty frame.
280 dstChannel->removeKeyframe(dstTime, parentCommand);
281 }
282 }
283 } else {
284 if (!srcChannel || !dstChannel) return; // TODO: add warning!
285
286 KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime);
287
288 if (!srcKeyframe) return; // TODO: add warning!
289
290 KisKeyframeChannel::copyKeyframe(srcChannel, srcTime, dstChannel, dstTime, parentCommand);
291
292 if (!copy) {
293 srcChannel->removeKeyframe(srcTime, parentCommand);
294 }
295 }
296 }
297
299 const FrameItemList &dstFrames,
300 bool copy,
301 bool moveEmpty,
302 KUndo2Command *parentCommand)
303 {
304 FrameMovePairList srcDstPairs;
305 for (int i = 0; i < srcFrames.size(); i++) {
306 srcDstPairs << std::make_pair(srcFrames[i], dstFrames[i]);
307 }
308 return createMoveKeyframesCommand(srcDstPairs, copy, moveEmpty, parentCommand);
309 }
310
312 bool copy,
313 bool moveEmptyFrames,
314 KUndo2Command *parentCommand)
315 {
317
318 !copy ?
319 kundo2_i18np("Move Keyframe",
320 "Move %1 Keyframes",
321 srcDstPairs.size()) :
322 kundo2_i18ncp("Copy one or several keyframes",
323 "Copy Keyframe",
324 "Copy %1 Keyframes",
325 srcDstPairs.size()),
326
327 parentCommand,
328
329 [srcDstPairs, copy, moveEmptyFrames] () -> KUndo2Command* {
330 bool result = false;
331
332 QScopedPointer<KUndo2Command> cmd(new KUndo2Command());
333
334 using MoveChain = QList<FrameItem>;
335 QHash<FrameItem, MoveChain> moveMap;
336 Q_FOREACH (const FrameMovePair &pair, srcDstPairs) {
337 moveMap.insert(pair.first, {pair.second});
338 }
339
340 auto it = moveMap.begin();
341 while (it != moveMap.end()) {
342 MoveChain &chain = it.value();
343 const FrameItem &previousFrame = chain.last();
344
345 auto tailIt = moveMap.find(previousFrame);
346
347 if (tailIt == it || tailIt == moveMap.end()) {
348 ++it;
349 continue;
350 }
351
352 chain.append(tailIt.value());
353 tailIt = moveMap.erase(tailIt);
354 // no incrementing! we are going to check the new tail now!
355 }
356
357 for (it = moveMap.begin(); it != moveMap.end(); ++it) {
358 MoveChain &chain = it.value();
359 chain.prepend(it.key());
360 KIS_SAFE_ASSERT_RECOVER(chain.size() > 1) { continue; }
361
362 bool isCycle = false;
363 if (chain.last() == chain.first()) {
364 isCycle = true;
365 chain.takeLast();
366 }
367
368 auto frameIt = chain.rbegin();
369
370 FrameItem dstItem = *frameIt++;
371
372 while (frameIt != chain.rend()) {
373 FrameItem srcItem = *frameIt++;
374
375 if (!isCycle) {
376 moveOneFrameItem(srcItem, dstItem, copy, moveEmptyFrames, cmd.data());
377 } else {
378 swapOneFrameItem(srcItem, dstItem, cmd.data());
379 }
380
381 dstItem = srcItem;
382 result = true;
383 }
384 }
385
386 return result ? cmd.take() : nullptr;
387 });
388
389 return cmd;
390 }
391
393 KUndo2Command *parentCommand)
394 {
396 kundo2_i18np("Clone Keyframe",
397 "Clone %1 Keyframes",
398 srcDstPairs.size()),
399 parentCommand,
400 [srcDstPairs, parentCommand]() -> KUndo2Command*
401 {
402 Q_UNUSED(parentCommand);
403 QScopedPointer<KUndo2Command> cmd(new KUndo2Command());
404
405 foreach (const FrameMovePair &move, srcDstPairs) {
406 KisRasterKeyframeChannel *srcRasterChan = dynamic_cast<KisRasterKeyframeChannel*>(move.first.node->getKeyframeChannel(move.first.channel));
407 KisRasterKeyframeChannel *dstRasterChan = dynamic_cast<KisRasterKeyframeChannel*>(move.second.node->getKeyframeChannel(move.second.channel));
408
409 if (!srcRasterChan || !dstRasterChan) {
410 continue;
411 }
412
413 if (srcRasterChan == dstRasterChan) {
414 srcRasterChan->cloneKeyframe(move.first.time, move.second.time, cmd.data());
415 } else {
416 KisKeyframeChannel::copyKeyframe(srcRasterChan, move.first.time, dstRasterChan, move.second.time, cmd.data());
417 }
418 }
419
420 return cmd.take();
421 });
422 }
423
424 void makeClonesUnique(KisImageSP image, const FrameItemList &frames)
425 {
427 kundo2_i18n("Make clones Unique"),
428 [frames]() {
429 QScopedPointer<KUndo2Command> cmd(new KUndo2Command());
430
431 foreach (const FrameItem &frameItem, frames) {
432 KisRasterKeyframeChannel *rasterChan = dynamic_cast<KisRasterKeyframeChannel*>(frameItem.node->getKeyframeChannel(frameItem.channel));
433 if (!rasterChan) {
434 continue;
435 }
436
437 rasterChan->makeUnique(frameItem.time, cmd.data());
438 }
439
440 return cmd.take();
441 });
442
446 }
447
448 QDebug operator<<(QDebug dbg, const FrameItem &item)
449 {
450 dbg.nospace() << "FrameItem(" << item.node->name() << ", " << item.channel << ", " << item.time << ")";
451 return dbg.space();
452 }
453
454}
455
float value(const T *src, size_t ch)
QDebug KRITACOMMAND_EXPORT operator<<(QDebug dbg, const KisCumulativeUndoData &data)
KisImageAnimationInterface * animationInterface() const
bool locked() const
Definition kis_image.cc:751
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.
static const KoID Raster
virtual void removeKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr)
Remove a keyframe from the channel at the specified time.
static void swapKeyframes(KisKeyframeChannel *channelA, int timeA, KisKeyframeChannel *channelB, int timeB, KUndo2Command *parentUndoCmd=nullptr)
Swap two keyframes across channel(s) at the specified times.
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.
void addKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr)
Add a new keyframe to the channel at the specified time.
KisKeyframeSP activeKeyframeAt(int time) const
int activeKeyframeTime(int time) const
Get the time of the active keyframe. Useful for snapping any time to that of the most recent keyframe...
static void moveKeyframe(KisKeyframeChannel *sourceChannel, int sourceTime, KisKeyframeChannel *targetChannel, int targetTime, KUndo2Command *parentUndoCmd=nullptr)
Move a keyframe across channel(s) at the specified times.
virtual void clear()
QRect extent() const
static void runSingleCommandStroke(KisImageSP image, KUndo2Command *cmd, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
runSingleCommandStroke creates a stroke and runs cmd in it. The text() field of cmd is used as a titl...
The KisRasterKeyframeChannel is a concrete KisKeyframeChannel subclass that stores and manages KisRas...
void makeUnique(int time, KUndo2Command *parentUndoCmd=nullptr)
The KisScalarKeyframeChannel is a concrete KisKeyframeChannel subclass that stores and manages KisSca...
qreal valueAt(int time) const
Quickly get the interpolated value at the given time.
void addScalarKeyframe(int time, qreal value, KUndo2Command *parentUndoCmd=nullptr)
KUndo2Command * endAndTake()
QString id() const
Definition KoID.cpp:63
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1)
KUndo2MagicString kundo2_i18n(const char *text)
KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1)
void makeClonesUnique(KisImageSP image, const FrameItemList &frames)
void resetChannels(KisImageSP image, KisNodeSP node, const QList< QString > &channelIDs)
void removeKeyframes(KisImageSP image, const FrameItemList &frames)
void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy)
const QString newLayerActionName
void removeKeyframe(KisImageSP image, KisNodeSP node, const QString &channel, int time)
const QString addTransformKeyframeActionName
void resetChannel(KisImageSP image, KisNodeSP node, const QString &channelID)
const QString lazyFrameCreationActionName
void sortPointsForSafeMove(QModelIndexList *points, const QPoint &offset)
KUndo2Command * createKeyframeCommand(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy, KUndo2Command *parentCommand)
const QString removeLayerActionName
const QString removeTransformKeyframeActionName
bool supportsContentFrames(KisNodeSP node)
void swapOneFrameItem(const FrameItem &src, const FrameItem &dst, KUndo2Command *parentCommand)
const QString dropFramesActionName
KUndo2Command * createMoveKeyframesCommand(const FrameItemList &srcFrames, const FrameItemList &dstFrames, bool copy, bool moveEmpty, KUndo2Command *parentCommand)
std::pair< FrameItem, FrameItem > FrameMovePair
void moveOneFrameItem(const FrameItem &src, const FrameItem &dst, bool copy, bool moveEmptyFrames, KUndo2Command *parentCommand)
const QString pinExistingLayerActionName
KUndo2Command * createCloneKeyframesCommand(const FrameMovePairList &srcDstPairs, KUndo2Command *parentCommand)
bool operator()(const QModelIndex &lhs, const QModelIndex &rhs)
LessOperator(const QPoint &offset)
void setOpacity(quint8 val)
KisKeyframeChannel * getKeyframeChannel(const QString &id, bool create)
virtual KisPaintDeviceSP paintDevice() const =0
QString name() const
quint8 opacity() const
void enableAnimation()
The LambdaCommand struct is a shorthand for creation of AggregateCommand commands using C++ lambda fe...
virtual void setDirty()
Definition kis_node.cpp:577