diff --git a/libs/image/kis_animation_cycle.cpp b/libs/image/kis_animation_cycle.cpp index a2f7467108..1ddaf9b18c 100644 --- a/libs/image/kis_animation_cycle.cpp +++ b/libs/image/kis_animation_cycle.cpp @@ -1,226 +1,233 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_animation_cycle.h" #include #include "kis_time_range.h" #include "kis_keyframe_channel.h" KisAnimationCycle::KisAnimationCycle(KisKeyframeChannel *channel, KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe) : KisKeyframeBase(channel, firstKeyframe->time()) , m_firstSourceKeyframe(firstKeyframe) , m_lastSourceKeyframe(lastKeyframe) {} +KisAnimationCycle::KisAnimationCycle(const KisAnimationCycle &cycle, KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe) + : KisKeyframeBase(cycle.channel(), firstKeyframe->time()) + , m_firstSourceKeyframe(firstKeyframe) + , m_lastSourceKeyframe(lastKeyframe) + , m_repeats(cycle.m_repeats) +{} + KisKeyframeSP KisAnimationCycle::firstSourceKeyframe() const { return m_firstSourceKeyframe; } KisKeyframeSP KisAnimationCycle::lastSourceKeyframe() const { return m_lastSourceKeyframe; } KisTimeSpan KisAnimationCycle::originalRange() const { const KisKeyframeBaseSP firstAfterCycle = m_lastSourceKeyframe->channel()->nextItem(*m_lastSourceKeyframe); KisTimeSpan range; if (firstAfterCycle.isNull()) { // TODO: semantics of repeat definition without a terminating keyframe? range = KisTimeSpan(m_firstSourceKeyframe->time(), m_lastSourceKeyframe->time()); } else { range = KisTimeSpan(m_firstSourceKeyframe->time(), firstAfterCycle->time() - 1); } return range; } int KisAnimationCycle::duration() const { return originalRange().duration(); } void KisAnimationCycle::addRepeat(QSharedPointer repeat) { if (m_repeats.contains(repeat)) return; m_repeats.append(repeat); } void KisAnimationCycle::removeRepeat(QSharedPointer repeat) { m_repeats.removeAll(repeat); } const QVector>& KisAnimationCycle::repeats() const { return m_repeats; } KisFrameSet KisAnimationCycle::instancesWithin(KisKeyframeSP original, KisTimeSpan range) const { KisFrameSet frames; const int originalTime = original->time(); const KisKeyframeBaseSP next = original->channel()->nextItem(*original); const int frameDuration = (next.isNull()) ? -1 : next->time() - originalTime; const int interval = duration(); int infiniteFrom = frameDuration == -1 ? originalTime : -1; QVector spans; Q_FOREACH(const QWeakPointer repeatFrame, m_repeats) { auto repeat = repeatFrame.toStrongRef(); if (!repeat) continue; const int endTime = repeat->end(); if (range.isEmpty()) { if (endTime != -1) { range = KisTimeSpan(0, endTime - 1); } else { infiniteFrom = repeat->firstInstanceOf(originalTime); continue; } } KisTimeSpan repeatRange = (endTime != -1) ? range & KisTimeSpan(repeat->time(), endTime) : range.truncateRight(repeat->time()); int firstInstance = repeat->firstInstanceOf(originalTime); if (firstInstance == -1) continue; if (firstInstance < repeatRange.start()) { firstInstance += interval * ((range.start() - firstInstance) / interval); } for (int repeatTime = firstInstance; repeatTime <= repeatRange.end(); repeatTime += interval) { bool endsWithinRange = frameDuration != -1 && repeatTime + frameDuration - 1 <= repeatRange.end(); const int repeatEndTime = endsWithinRange ? (repeatTime + frameDuration - 1) : repeatRange.end(); spans.append(KisTimeSpan(repeatTime, repeatEndTime)); } } frames |= KisFrameSet(spans); if (infiniteFrom != -1) { frames |= KisFrameSet::infiniteFrom(infiniteFrom); } else { frames |= KisFrameSet::between(originalTime, originalTime + frameDuration - 1); } return frames; } QRect KisAnimationCycle::affectedRect() const { QRect rect; KisKeyframeSP keyframe = m_firstSourceKeyframe; do { rect |= keyframe->affectedRect(); keyframe = channel()->nextKeyframe(keyframe); } while (keyframe && keyframe != m_lastSourceKeyframe); return rect; } KisKeyframeSP KisAnimationCycle::getOriginalKeyframeFor(int time) const { return channel()->activeKeyframeAt(time); } KisRepeatFrame::KisRepeatFrame(KisKeyframeChannel *channel, int time, QSharedPointer cycle) : KisKeyframeBase(channel, time) , m_cycle(cycle) {} QSharedPointer KisRepeatFrame::cycle() const { return m_cycle; } QRect KisRepeatFrame::affectedRect() const { KisKeyframeSP keyframe = m_cycle->firstSourceKeyframe(); QRect rect; while (!keyframe.isNull() && keyframe->time() <= m_cycle->lastSourceKeyframe()->time()) { rect |= keyframe->affectedRect(); keyframe = channel()->nextKeyframe(keyframe); } return rect; } int KisRepeatFrame::getOriginalTimeFor(int time) const { KisTimeSpan originalRange = m_cycle->originalRange(); int timeWithinCycle = (time - this->time()) % originalRange.duration(); return m_cycle->firstSourceKeyframe()->time() + timeWithinCycle; } KisKeyframeSP KisRepeatFrame::getOriginalKeyframeFor(int time) const { return channel()->activeKeyframeAt(getOriginalTimeFor(time)); } int KisRepeatFrame::firstInstanceOf(int originalTime) const { KisTimeSpan originalRange = m_cycle->originalRange(); const int timeWithinCycle = originalTime - originalRange.start(); const int first = this->time() + timeWithinCycle; const KisKeyframeSP next = channel()->nextKeyframe(time()); if (next && next->time() < first) return -1; return first; } int KisRepeatFrame::previousVisibleFrame(int time) const { if (time <= this->time()) return -1; const int earlierOriginalTime = getOriginalTimeFor(time - 1); int originalStart, originalEnd; channel()->activeKeyframeRange(earlierOriginalTime, &originalStart, &originalEnd); if (originalEnd == -1) return -1; const int durationOfOriginalKeyframe = originalEnd + 1 - originalStart; return time - durationOfOriginalKeyframe; } int KisRepeatFrame::nextVisibleFrame(int time) const { const int originalTime = getOriginalTimeFor(time); int originalStart, originalEnd; channel()->activeKeyframeRange(originalTime, &originalStart, &originalEnd); if (originalEnd == -1) return -1; const int durationOfOriginalKeyframe = originalEnd + 1 - originalStart; const int nextFrameTime = time + durationOfOriginalKeyframe; const int endTime = end(); return (endTime == -1 || nextFrameTime < endTime) ? nextFrameTime : -1; } int KisRepeatFrame::end() const { const KisKeyframeBaseSP next = channel()->nextItem(*this); return next ? next->time() - 1 : -1; } diff --git a/libs/image/kis_animation_cycle.h b/libs/image/kis_animation_cycle.h index 91d5169405..d4e490e978 100644 --- a/libs/image/kis_animation_cycle.h +++ b/libs/image/kis_animation_cycle.h @@ -1,91 +1,93 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_ANIMATION_CYCLE_H #define KIS_ANIMATION_CYCLE_H #include "kis_keyframe.h" class KisTimeSpan; class KisFrameSet; class KisRepeatFrame; class KRITAIMAGE_EXPORT KisAnimationCycle : public KisKeyframeBase { public: KisAnimationCycle(KisKeyframeChannel *channel, KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe); + KisAnimationCycle(const KisAnimationCycle &cycle, KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe); + KisKeyframeSP firstSourceKeyframe() const; KisKeyframeSP lastSourceKeyframe() const; KisTimeSpan originalRange() const; int duration() const; void addRepeat(QSharedPointer repeat); void removeRepeat(QSharedPointer repeat); const QVector>& repeats() const; QRect affectedRect() const override; KisKeyframeSP getOriginalKeyframeFor(int time) const override; KisFrameSet instancesWithin(KisKeyframeSP original, KisTimeSpan range) const; private: friend class KisKeyframeChannel; KisKeyframeSP m_firstSourceKeyframe, m_lastSourceKeyframe; QVector> m_repeats; }; class KRITAIMAGE_EXPORT KisRepeatFrame : public KisKeyframeBase { public: KisRepeatFrame(KisKeyframeChannel *channel, int time, QSharedPointer cycle); QSharedPointer cycle() const; QRect affectedRect() const override; int getOriginalTimeFor(int time) const; KisKeyframeSP getOriginalKeyframeFor(int time) const override; /// Returns the earliest time the original frame appears in this repeat, or -1 if it never does. int firstInstanceOf(int originalTime) const; /** Returns the time at which the previous frame within the repeat appears, * or -1 if time is at the first repeated frame. * NB: time should be at the start of a repeated frame */ int previousVisibleFrame(int time) const; /** Returns the time at which the next frame within the repeat appears, * or -1 if time is at the last repeated frame. * NB: time should be at the start of a repeated frame */ int nextVisibleFrame(int time) const; /** * Finds the time of the next keyframe if any. * Returns -1 if the cycle continues indefinitely. */ int end() const; private: QSharedPointer m_cycle; }; #endif diff --git a/libs/image/kis_keyframe_channel.cpp b/libs/image/kis_keyframe_channel.cpp index c1cb35236c..388df1a0cc 100644 --- a/libs/image/kis_keyframe_channel.cpp +++ b/libs/image/kis_keyframe_channel.cpp @@ -1,1056 +1,1061 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_keyframe_channel.h" #include "KoID.h" #include "kis_global.h" #include "kis_node.h" #include "KisCollectionUtils.h" #include "kis_time_range.h" #include "kundo2command.h" #include "kis_keyframe_commands.h" #include "kis_animation_cycle.h" #include const KoID KisKeyframeChannel::Content = KoID("content", ki18n("Content")); const KoID KisKeyframeChannel::Opacity = KoID("opacity", ki18n("Opacity")); const KoID KisKeyframeChannel::TransformArguments = KoID("transform_arguments", ki18n("Transform")); const KoID KisKeyframeChannel::TransformPositionX = KoID("transform_pos_x", ki18n("Position (X)")); const KoID KisKeyframeChannel::TransformPositionY = KoID("transform_pos_y", ki18n("Position (Y)")); const KoID KisKeyframeChannel::TransformScaleX = KoID("transform_scale_x", ki18n("Scale (X)")); const KoID KisKeyframeChannel::TransformScaleY = KoID("transform_scale_y", ki18n("Scale (Y)")); const KoID KisKeyframeChannel::TransformShearX = KoID("transform_shear_x", ki18n("Shear (X)")); const KoID KisKeyframeChannel::TransformShearY = KoID("transform_shear_y", ki18n("Shear (Y)")); const KoID KisKeyframeChannel::TransformRotationX = KoID("transform_rotation_x", ki18n("Rotation (X)")); const KoID KisKeyframeChannel::TransformRotationY = KoID("transform_rotation_y", ki18n("Rotation (Y)")); const KoID KisKeyframeChannel::TransformRotationZ = KoID("transform_rotation_z", ki18n("Rotation (Z)")); /** * Finds the last item the in the map with a key less than the given one. * Returns map.constEnd() if no such key exists. */ template typename QMap::const_iterator findActive(const QMap &map, int maximumKey) { typename QMap::const_iterator i = map.upperBound(maximumKey); if (i == map.constBegin()) return map.constEnd(); return i - 1; } /** * Finds the last item the in the map before the "active" item (see findActive) for the given key. * Returns map.constEnd() if no such key exists. */ template typename QMap::const_iterator findPrevious(const QMap &map, int currentKey) { typename QMap::const_iterator active = lastBeforeOrAt(map, currentKey); if (active == map.constEnd()) return map.constEnd(); if (currentKey > active.key()) return active; if (active == map.constBegin()) return map.constEnd(); return active - 1; } /** * Finds the first item the in the map with a key greater than the given one. * Returns map.constEnd() if no such key exists. */ template typename QMap::const_iterator findNext(const QMap &map, int currentKey) { return map.upperBound(currentKey); } struct KisKeyframeChannel::Private { Private() {} Private(const Private &rhs, KisNodeWSP newParentNode) { node = newParentNode; id = rhs.id; defaultBounds = rhs.defaultBounds; haveBrokenFrameTimeBug = rhs.haveBrokenFrameTimeBug; } KeyframesMap keys; QMap> cycles; QMap> repeats; KisNodeWSP node; KoID id; KisDefaultBoundsBaseSP defaultBounds; bool haveBrokenFrameTimeBug = false; void add(KisKeyframeBaseSP item) { auto repeat = item.dynamicCast(); if (repeat) { repeat->cycle()->addRepeat(repeat); repeats.insert(repeat->time(), repeat); } else { auto keyframe = item.dynamicCast(); KIS_ASSERT_RECOVER_RETURN(keyframe); keys.insert(item->time(), keyframe); } } void remove(KisKeyframeBaseSP item) { auto repeat = item.dynamicCast(); if (repeat) { repeat->cycle()->removeRepeat(repeat); repeats.remove(repeat->time()); } else { keys.remove(item->time()); } } void moveKeyframe(KisKeyframeBaseSP keyframe, int newTime) { const QSharedPointer cycle = cycles.value(keyframe->time()); remove(keyframe); keyframe->setTime(newTime); add(keyframe); if (cycle) { KIS_SAFE_ASSERT_RECOVER_NOOP(newTime < cycle->lastSourceKeyframe()->time()); // TODO: make sure this is the case cycles.remove(cycle->time()); cycle->setTime(newTime); cycles.insert(newTime, cycle); } } void addCycle(QSharedPointer cycle) { cycles.insert(cycle->time(), cycle); Q_FOREACH(QWeakPointer repeatWP, cycle->repeats()) { KisKeyframeBaseSP repeat = repeatWP.toStrongRef(); if (repeat) add(repeat); } } void removeCycle(QSharedPointer cycle) { KIS_SAFE_ASSERT_RECOVER_NOOP(cycle->repeats().isEmpty()); cycles.remove(cycle->time()); } }; KisKeyframeChannel::KisKeyframeChannel(const KoID &id, KisDefaultBoundsBaseSP defaultBounds) : m_d(new Private) { m_d->id = id; m_d->node = 0; m_d->defaultBounds = defaultBounds; } KisKeyframeChannel::KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNode *newParentNode) : m_d(new Private(*rhs.m_d, newParentNode)) { KIS_ASSERT_RECOVER_NOOP(&rhs != this); Q_FOREACH(KisKeyframeSP keyframe, rhs.m_d->keys) { const KisKeyframeSP clone = keyframe->cloneFor(this); m_d->add(clone); } Q_FOREACH(const QSharedPointer rhsCycle, rhs.m_d->cycles) { KisKeyframeSP firstFrame = keyframeAt(rhsCycle->firstSourceKeyframe()->time()); KisKeyframeSP lastFrame = keyframeAt(rhsCycle->lastSourceKeyframe()->time()); QSharedPointer cycle = toQShared(new KisAnimationCycle(this, firstFrame, lastFrame)); m_d->addCycle(cycle); Q_FOREACH(auto rhsRepeatWP, rhsCycle->repeats()) { const QSharedPointer rhsRepeat = rhsRepeatWP.toStrongRef(); if (rhsRepeat) { m_d->add(toQShared(new KisRepeatFrame(this, rhsRepeat->time(), cycle))); } } } } KisKeyframeChannel::~KisKeyframeChannel() {} QString KisKeyframeChannel::id() const { return m_d->id.id(); } QString KisKeyframeChannel::name() const { return m_d->id.name(); } void KisKeyframeChannel::setNode(KisNodeWSP node) { m_d->node = node; } KisNodeWSP KisKeyframeChannel::node() const { return m_d->node; } int KisKeyframeChannel::keyframeCount() const { return m_d->keys.count(); } KisKeyframeChannel::KeyframesMap& KisKeyframeChannel::keys() { return m_d->keys; } const KisKeyframeChannel::KeyframesMap& KisKeyframeChannel::constKeys() const { return m_d->keys; } #define LAZY_INITIALIZE_PARENT_COMMAND(cmd) \ QScopedPointer __tempCommand; \ if (!parentCommand) { \ __tempCommand.reset(new KUndo2Command()); \ cmd = __tempCommand.data(); \ } KisKeyframeSP KisKeyframeChannel::addKeyframe(int time, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); return insertKeyframe(time, KisKeyframeSP(), parentCommand); } KisKeyframeBaseSP KisKeyframeChannel::copyItem(const KisKeyframeBaseSP item, int newTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); return insertKeyframe(newTime, item, parentCommand); } KisKeyframeSP KisKeyframeChannel::copyAsKeyframe(const KisKeyframeBaseSP item, int originalTime, int newTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); KisKeyframeSP keyframe = item->getOriginalKeyframeFor(originalTime); return insertKeyframe(newTime, keyframe, parentCommand); } KisKeyframeSP KisKeyframeChannel::linkKeyframe(const KisKeyframeBaseSP, int, KUndo2Command*) { return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::insertKeyframe(int time, const KisKeyframeBaseSP copySrc, KUndo2Command *parentCommand) { KisKeyframeBaseSP oldItem = itemAt(time); if (oldItem) { deleteKeyframeImpl(oldItem, parentCommand, false); } Q_ASSERT(parentCommand); KisKeyframeSP sourceKeyframe = copySrc ? copySrc->getOriginalKeyframeFor(copySrc->time()) : KisKeyframeSP(); KisKeyframeSP keyframe = createKeyframe(time, sourceKeyframe, parentCommand); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, keyframe->time(), keyframe, parentCommand); cmd->redo(); return keyframe; } bool KisKeyframeChannel::deleteKeyframe(KisKeyframeBaseSP keyframe, KUndo2Command *parentCommand) { return deleteKeyframeImpl(keyframe, parentCommand, true); } bool KisKeyframeChannel::moveKeyframe(KisKeyframeBaseSP keyframe, int newTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); if (newTime == keyframe->time()) return false; KisKeyframeBaseSP other = itemAt(newTime); if (other) { deleteKeyframeImpl(other, parentCommand, false); } const int srcTime = keyframe->time(); KUndo2Command *cmd = new KisMoveFrameCommand(this, keyframe, srcTime, newTime, parentCommand); cmd->redo(); if (srcTime == 0) { addKeyframe(srcTime, parentCommand); } return true; } bool KisKeyframeChannel::swapFrames(int lhsTime, int rhsTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); if (lhsTime == rhsTime) return false; KisKeyframeBaseSP lhsFrame = itemAt(lhsTime); KisKeyframeBaseSP rhsFrame = itemAt(rhsTime); if (!lhsFrame && !rhsFrame) return false; if (lhsFrame && !rhsFrame) { moveKeyframe(lhsFrame, rhsTime, parentCommand); } else if (!lhsFrame && rhsFrame) { moveKeyframe(rhsFrame, lhsTime, parentCommand); } else { KUndo2Command *cmd = new KisSwapFramesCommand(this, lhsFrame, rhsFrame, parentCommand); cmd->redo(); } return true; } bool KisKeyframeChannel::deleteKeyframeImpl(KisKeyframeBaseSP item, KUndo2Command *parentCommand, bool recreate) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); Q_ASSERT(parentCommand); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, item->time(), KisKeyframeSP(), parentCommand); cmd->redo(); KisKeyframeSP keyframe = item.dynamicCast(); if (keyframe) { destroyKeyframe(keyframe, parentCommand); if (recreate && keyframe->time() == 0) { addKeyframe(0, parentCommand); } } return true; } void KisKeyframeChannel::moveKeyframeImpl(KisKeyframeBaseSP keyframe, int newTime) { KIS_ASSERT_RECOVER_RETURN(keyframe); KIS_ASSERT_RECOVER_RETURN(!itemAt(newTime)); KisFrameSet rangeSrc = affectedFrames(keyframe->time()); QRect rectSrc = keyframe->affectedRect(); emit sigKeyframeAboutToBeMoved(keyframe, newTime); int oldTime = keyframe->time(); m_d->moveKeyframe(keyframe, newTime); keyframe->setTime(newTime); emit sigKeyframeMoved(keyframe, oldTime); KisFrameSet rangeDst = affectedFrames(keyframe->time()); QRect rectDst = keyframe->affectedRect(); requestUpdate(rangeSrc, rectSrc); requestUpdate(rangeDst, rectDst); } void KisKeyframeChannel::swapKeyframesImpl(KisKeyframeBaseSP lhsKeyframe, KisKeyframeBaseSP rhsKeyframe) { KIS_ASSERT_RECOVER_RETURN(lhsKeyframe); KIS_ASSERT_RECOVER_RETURN(rhsKeyframe); KisFrameSet rangeLhs = affectedFrames(lhsKeyframe->time()); KisFrameSet rangeRhs = affectedFrames(rhsKeyframe->time()); const QRect rectLhsSrc = lhsKeyframe->affectedRect(); const QRect rectRhsSrc = rhsKeyframe->affectedRect(); const int lhsTime = lhsKeyframe->time(); const int rhsTime = rhsKeyframe->time(); emit sigKeyframeAboutToBeMoved(lhsKeyframe, rhsTime); emit sigKeyframeAboutToBeMoved(rhsKeyframe, lhsTime); m_d->remove(lhsKeyframe); m_d->remove(rhsKeyframe); rhsKeyframe->setTime(lhsTime); lhsKeyframe->setTime(rhsTime); m_d->add(lhsKeyframe); m_d->add(rhsKeyframe); emit sigKeyframeMoved(lhsKeyframe, lhsTime); emit sigKeyframeMoved(rhsKeyframe, rhsTime); const QRect rectLhsDst = lhsKeyframe->affectedRect(); const QRect rectRhsDst = rhsKeyframe->affectedRect(); requestUpdate(rangeLhs, rectLhsSrc | rectRhsDst); requestUpdate(rangeRhs, rectRhsSrc | rectLhsDst); } KisKeyframeBaseSP KisKeyframeChannel::replaceKeyframeAt(int time, KisKeyframeBaseSP newKeyframe) { Q_ASSERT(newKeyframe.isNull() || time == newKeyframe->time()); KisKeyframeBaseSP existingKeyframe = itemAt(time); if (!existingKeyframe.isNull()) { removeKeyframeLogical(existingKeyframe); } if (!newKeyframe.isNull()) { insertKeyframeLogical(newKeyframe); } return existingKeyframe; } void KisKeyframeChannel::insertKeyframeLogical(KisKeyframeBaseSP keyframe) { const int time = keyframe->time(); emit sigKeyframeAboutToBeAdded(keyframe); m_d->add(keyframe); emit sigKeyframeAdded(keyframe); QRect rect = keyframe->affectedRect(); KisFrameSet range = affectedFrames(time); requestUpdate(range, rect); } void KisKeyframeChannel::removeKeyframeLogical(KisKeyframeBaseSP keyframe) { QRect rect = keyframe->affectedRect(); KisFrameSet range = affectedFrames(keyframe->time()); emit sigKeyframeAboutToBeRemoved(keyframe); m_d->remove(keyframe); emit sigKeyframeRemoved(keyframe); requestUpdate(range, rect); } KisKeyframeSP KisKeyframeChannel::keyframeAt(int time) const { return m_d->keys.value(time); } KisKeyframeSP KisKeyframeChannel::activeKeyframeAt(int time) const { KeyframesMap::const_iterator i = KisCollectionUtils::lastBeforeOrAt(m_d->keys, time); if (i != m_d->keys.constEnd()) { return i.value(); } return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::visibleKeyframeAt(int time) const { const QSharedPointer repeat = activeRepeatAt(time); return repeat ? repeat->getOriginalKeyframeFor(time) : activeKeyframeAt(time); } KisKeyframeSP KisKeyframeChannel::currentlyActiveKeyframe() const { return activeKeyframeAt(currentTime()); } KisKeyframeSP KisKeyframeChannel::firstKeyframe() const { if (m_d->keys.isEmpty()) return KisKeyframeSP(); return m_d->keys.first(); } KisKeyframeSP KisKeyframeChannel::nextKeyframe(KisKeyframeSP keyframe) const { return nextKeyframe(keyframe->time()); } KisKeyframeSP KisKeyframeChannel::nextKeyframe(int time) const { KeyframesMap::const_iterator i = KisCollectionUtils::firstAfter(m_d->keys, time); if (i == m_d->keys.constEnd()) return KisKeyframeSP(0); return i.value(); } KisKeyframeSP KisKeyframeChannel::previousKeyframe(KisKeyframeSP keyframe) const { return previousKeyframe(keyframe->time()); } KisKeyframeSP KisKeyframeChannel::previousKeyframe(int time) const { KeyframesMap::const_iterator i = KisCollectionUtils::lastBefore(m_d->keys, time); if (i == m_d->keys.constEnd()) return KisKeyframeSP(0); return i.value(); } KisKeyframeSP KisKeyframeChannel::lastKeyframe() const { if (m_d->keys.isEmpty()) return KisKeyframeSP(0); return (m_d->keys.end()-1).value(); } KisKeyframeBaseSP KisKeyframeChannel::itemAt(int time) const { const KisKeyframeSP keyframe = keyframeAt(time); if (keyframe) return keyframe; const QSharedPointer repeat = activeRepeatAt(time); if (repeat && time == repeat->time()) return repeat; return KisKeyframeBaseSP(); } KisKeyframeBaseSP KisKeyframeChannel::activeItemAt(int time) const { const KisKeyframeSP keyframe = activeKeyframeAt(time); if (keyframe) return keyframe; return activeRepeatAt(time); } KisKeyframeBaseSP KisKeyframeChannel::nextItem(const KisKeyframeBase &item) const { const KisKeyframeSP keyframe = nextKeyframe(item.time()); auto repeatIter = KisCollectionUtils::firstAfter(m_d->repeats, item.time()); const auto repeat = (repeatIter != m_d->repeats.constEnd()) ? repeatIter.value() : QSharedPointer(); if (keyframe && (!repeat || repeat->time() > keyframe->time())) return keyframe; return repeat; } KisKeyframeBaseSP KisKeyframeChannel::previousItem(const KisKeyframeBase &item) const { const KisKeyframeSP keyframe = previousKeyframe(item.time()); auto repeatIter = KisCollectionUtils::lastBefore(m_d->repeats, item.time()); const auto repeat = (repeatIter != m_d->repeats.constEnd()) ? repeatIter.value() : QSharedPointer(); if (keyframe && (!repeat || repeat->time() > keyframe->time())) return keyframe; return repeat; } KisVisibleKeyframeIterator KisKeyframeChannel::visibleKeyframesFrom(int time) const { return KisVisibleKeyframeIterator(visibleKeyframeAt(time)); } +QList> KisKeyframeChannel::cycles() const +{ + return m_d->cycles.values(); +} + KisTimeSpan KisKeyframeChannel::cycledRangeAt(int time) const { QSharedPointer repeat = activeRepeatAt(time); if (repeat) return repeat->cycle()->originalRange(); QSharedPointer cycle = cycleAt(time); if (cycle) return cycle->originalRange(); return KisTimeSpan(); } QSharedPointer KisKeyframeChannel::cycleAt(int time) const { if (m_d->cycles.isEmpty()) return QSharedPointer(); const auto it = KisCollectionUtils::lastBeforeOrAt(m_d->cycles, time); if (it == m_d->cycles.constEnd()) return QSharedPointer(); const KisKeyframeBaseSP next = nextItem(*it.value()->lastSourceKeyframe()); if (next && next->time() <= time) return QSharedPointer(); return it.value(); }; QSharedPointer KisKeyframeChannel::activeRepeatAt(int time) const { const auto repeat = KisCollectionUtils::lastBeforeOrAt(m_d->repeats, time); if (repeat == m_d->repeats.constEnd()) return QSharedPointer(); const KisKeyframeSP lastKeyframe = activeKeyframeAt(time); if (lastKeyframe && lastKeyframe->time() > repeat.value()->time()) return QSharedPointer(); return repeat.value(); } void KisKeyframeChannel::activeKeyframeRange(int time, int *first, int *last) const { *first = *last = -1; const KisKeyframeSP currentKeyframe = activeKeyframeAt(time); if (currentKeyframe.isNull()) return; *first = currentKeyframe->time(); const KisKeyframeSP next = nextKeyframe(currentKeyframe); if (!next.isNull()) { *last = next->time() - 1; } } int KisKeyframeChannel::framesHash() const { KeyframesMap::const_iterator it = m_d->keys.constBegin(); KeyframesMap::const_iterator end = m_d->keys.constEnd(); int hash = 0; while (it != end) { hash += it.key(); ++it; } return hash; } QSet KisKeyframeChannel::allKeyframeIds() const { QSet frames; KeyframesMap::const_iterator it = m_d->keys.constBegin(); KeyframesMap::const_iterator end = m_d->keys.constEnd(); while (it != end) { frames.insert(it.key()); ++it; } return frames; } KisFrameSet KisKeyframeChannel::affectedFrames(int time) const { if (m_d->keys.isEmpty()) return KisFrameSet::infiniteFrom(0); KeyframesMap::const_iterator active = KisCollectionUtils::lastBeforeOrAt(m_d->keys, time); KeyframesMap::const_iterator next; int from; if (active == m_d->keys.constEnd()) { // No active keyframe, ie. time is before the first keyframe from = 0; next = m_d->keys.constBegin(); } else { from = active.key(); next = active + 1; } KisFrameSet frames; QSharedPointer repeat = activeRepeatAt(time); if (repeat) { const KisKeyframeSP original = repeat->getOriginalKeyframeFor(time); return affectedFrames(original->time()); } QSharedPointer cycle = cycleAt(time); if (cycle) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(active != m_d->keys.constEnd() && active.value(), KisFrameSet()); frames = cycle->instancesWithin(active.value(), KisTimeSpan()); } if (next == m_d->keys.constEnd()) { frames |= KisFrameSet::infiniteFrom(from); } else { frames |= KisFrameSet::between(from, next.key() - 1); } return frames; } KisFrameSet KisKeyframeChannel::identicalFrames(int time, const KisTimeSpan range) const { const QSharedPointer repeat = activeRepeatAt(time); if (repeat) { const KisKeyframeSP original = repeat->getOriginalKeyframeFor(time); return identicalFrames(original->time(), range); } KeyframesMap::const_iterator active = KisCollectionUtils::lastBeforeOrAt(m_d->keys, time); const QSharedPointer cycle = cycleAt(time); if (cycle) { return cycle->instancesWithin(active.value(), range); } if (active != m_d->keys.constEnd() && (active+1) != m_d->keys.constEnd()) { if (active->data()->interpolationMode() != KisKeyframe::Constant) { return KisFrameSet::between(time, time); } } return affectedFrames(time); } bool KisKeyframeChannel::areFramesIdentical(int time1, int time2) const { const KisFrameSet identical = identicalFrames(time1, KisTimeSpan(time2, time2)); return identical.contains(time2); } bool KisKeyframeChannel::isFrameAffectedBy(int targetFrame, int changedFrame) const { const KisFrameSet affected = affectedFrames(changedFrame); return affected.contains(targetFrame); } QDomElement KisKeyframeChannel::toXML(QDomDocument doc, const QString &layerFilename) { QDomElement channelElement = doc.createElement("channel"); channelElement.setAttribute("name", id()); Q_FOREACH (KisKeyframeSP keyframe, m_d->keys.values()) { QDomElement keyframeElement = doc.createElement("keyframe"); keyframeElement.setAttribute("time", keyframe->time()); keyframeElement.setAttribute("color-label", keyframe->colorLabel()); saveKeyframe(keyframe, keyframeElement, layerFilename); channelElement.appendChild(keyframeElement); } Q_FOREACH (const QSharedPointer cycle, m_d->cycles.values()) { QDomElement cycleElement = doc.createElement("cycle"); cycleElement.setAttribute("firstKeyframe", cycle->firstSourceKeyframe()->time()); cycleElement.setAttribute("lastKeyframe", cycle->lastSourceKeyframe()->time()); Q_FOREACH (auto repeatWP, cycle->repeats()) { const QSharedPointer repeat = repeatWP.toStrongRef(); if (!repeat) continue; QDomElement repeatElement = doc.createElement("repeat"); repeatElement.setAttribute("time", repeat->time()); cycleElement.appendChild(repeatElement); } channelElement.appendChild(cycleElement); } return channelElement; } void KisKeyframeChannel::loadXML(const QDomElement &channelNode) { for (QDomElement childNode = channelNode.firstChildElement(); !childNode.isNull(); childNode = childNode.nextSiblingElement()) { const QString nodeName = childNode.nodeName().toUpper(); if (nodeName == "KEYFRAME") { const QString keyframeType = childNode.attribute("type", "").toUpper(); KisKeyframeSP keyframe; keyframe = loadKeyframe(childNode); KIS_SAFE_ASSERT_RECOVER(keyframe) { continue; } if (childNode.hasAttribute("color-label")) { keyframe->setColorLabel(childNode.attribute("color-label").toUInt()); } m_d->add(keyframe); } } for (QDomElement childNode = channelNode.firstChildElement(); !childNode.isNull(); childNode = childNode.nextSiblingElement()) { if (childNode.nodeName().toUpper() == "CYCLE") { QSharedPointer cycle = loadCycle(childNode); if (cycle) { m_d->addCycle(cycle); } } } } QSharedPointer KisKeyframeChannel::loadCycle(const QDomElement &cycleElement) { const int firstKeyframeTime = cycleElement.attribute("firstKeyframe", "-1").toInt(); const int lastKeyframeTime = cycleElement.attribute("lastKeyframe", "-1").toInt(); if (firstKeyframeTime < 0 || lastKeyframeTime <= firstKeyframeTime) { qWarning() << "Invalid cycle range: " << firstKeyframeTime << "to" << lastKeyframeTime; return nullptr; } const KisKeyframeSP firstKeyframe = keyframeAt(firstKeyframeTime); const KisKeyframeSP lastKeyframe = keyframeAt(lastKeyframeTime); if (!firstKeyframe || !lastKeyframe) { qWarning() << "Could not find defining keyframes for cycle " << firstKeyframeTime << "to" << lastKeyframeTime; return nullptr; } QSharedPointer cycle = toQShared(new KisAnimationCycle(this, firstKeyframe, lastKeyframe)); for (QDomElement grandChildNode = cycleElement.firstChildElement(); !grandChildNode.isNull(); grandChildNode = grandChildNode.nextSiblingElement()) { if (grandChildNode.nodeName().toUpper() == "REPEAT") { const int time = grandChildNode.attribute("time").toInt(); const QSharedPointer repeat = toQShared(new KisRepeatFrame(this, time, cycle)); m_d->add(repeat); } } return cycle; } bool KisKeyframeChannel::swapExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand) { if (srcChannel->id() != id()) { warnKrita << "Cannot copy frames from different ids:" << ppVar(srcChannel->id()) << ppVar(id()); return KisKeyframeSP(); } LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); KisKeyframeSP srcFrame = srcChannel->keyframeAt(srcTime); KisKeyframeSP dstFrame = keyframeAt(dstTime); if (!dstFrame && srcFrame) { copyExternalKeyframe(srcChannel, srcTime, dstTime, parentCommand); srcChannel->deleteKeyframe(srcFrame, parentCommand); } else if (dstFrame && !srcFrame) { srcChannel->copyExternalKeyframe(this, dstTime, srcTime, parentCommand); deleteKeyframe(dstFrame, parentCommand); } else if (dstFrame && srcFrame) { const int fakeFrameTime = -1; KisKeyframeSP newKeyframe = createKeyframe(fakeFrameTime, KisKeyframeSP(), parentCommand); uploadExternalKeyframe(srcChannel, srcTime, newKeyframe); srcChannel->copyExternalKeyframe(this, dstTime, srcTime, parentCommand); // do not recreate frame! deleteKeyframeImpl(dstFrame, parentCommand, false); newKeyframe->setTime(dstTime); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, newKeyframe->time(), newKeyframe, parentCommand); cmd->redo(); } return true; } KisKeyframeSP KisKeyframeChannel::copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand) { if (srcChannel->id() != id()) { warnKrita << "Cannot copy frames from different ids:" << ppVar(srcChannel->id()) << ppVar(id()); return KisKeyframeSP(); } LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); KisKeyframeSP dstFrame = keyframeAt(dstTime); if (dstFrame) { deleteKeyframeImpl(dstFrame, parentCommand, false); } KisKeyframeSP newKeyframe = createKeyframe(dstTime, KisKeyframeSP(), parentCommand); uploadExternalKeyframe(srcChannel, srcTime, newKeyframe); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, newKeyframe->time(), newKeyframe, parentCommand); cmd->redo(); return newKeyframe; } KisDefineCycleCommand * KisKeyframeChannel::createCycle(KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe, KUndo2Command *parentCommand) { const QSharedPointer cycle = toQShared(new KisAnimationCycle(this, firstKeyframe, lastKeyframe)); return new KisDefineCycleCommand(nullptr, cycle, parentCommand); } void KisKeyframeChannel::addCycle(QSharedPointer cycle) { m_d->addCycle(cycle); } KUndo2Command* KisKeyframeChannel::deleteCycle(QSharedPointer cycle, KUndo2Command *parentCommand) { KisDefineCycleCommand *defineCycleCommand = new KisDefineCycleCommand(cycle, nullptr, parentCommand); // Remove repeats of the cycle Q_FOREACH(QWeakPointer repeatWP, cycle->repeats()) { auto repeat = repeatWP.toStrongRef(); if (repeat) { deleteKeyframe(repeat); } } return defineCycleCommand; } void KisKeyframeChannel::removeCycle(QSharedPointer cycle) { m_d->removeCycle(cycle); } QSharedPointer KisKeyframeChannel::addRepeat(QSharedPointer cycle, int time, KUndo2Command *parentCommand) { const QSharedPointer repeatFrame = toQShared(new KisRepeatFrame(this, time, cycle)); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, time, repeatFrame, parentCommand); cmd->redo(); return repeatFrame; } void KisKeyframeChannel::requestUpdate(const KisFrameSet &range, const QRect &rect) { if (m_d->node) { m_d->node->invalidateFrames(range, rect); int currentTime = m_d->defaultBounds->currentTime(); if (range.contains(currentTime)) { m_d->node->setDirty(rect); } } } void KisKeyframeChannel::workaroundBrokenFrameTimeBug(int *time) { /** * Between Krita 4.1 and 4.4 Krita had a bug which resulted in creating frames * with negative time stamp. The bug has been fixed, but there might be some files * still in the wild. * * TODO: remove this workaround in Krita 5.0, when no such file are left :) */ if (*time < 0) { qWarning() << "WARNING: Loading a file with negative animation frames!"; qWarning() << " The file has been saved with a buggy version of Krita."; qWarning() << " All the frames with negative ids will be dropped!"; qWarning() << " " << ppVar(this->id()) << ppVar(*time); m_d->haveBrokenFrameTimeBug = true; *time = 0; } if (m_d->haveBrokenFrameTimeBug) { while (keyframeAt(*time)) { (*time)++; } } } int KisKeyframeChannel::currentTime() const { return m_d->defaultBounds->currentTime(); } qreal KisKeyframeChannel::minScalarValue() const { return 0; } qreal KisKeyframeChannel::maxScalarValue() const { return 0; } qreal KisKeyframeChannel::scalarValue(const KisKeyframeSP keyframe) const { Q_UNUSED(keyframe); return 0; } void KisKeyframeChannel::setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand) { Q_UNUSED(keyframe); Q_UNUSED(value); Q_UNUSED(parentCommand); } KisVisibleKeyframeIterator::KisVisibleKeyframeIterator() = default; KisVisibleKeyframeIterator::KisVisibleKeyframeIterator(KisKeyframeSP keyframe) : m_channel(keyframe->channel()) , m_keyframe(keyframe) , m_time(keyframe->time()) {} KisVisibleKeyframeIterator& KisVisibleKeyframeIterator::operator--() { const QSharedPointer repeat = m_channel->activeRepeatAt(m_time); if (repeat) { const int time = repeat->previousVisibleFrame(m_time); if (time >= 0) { m_time = time; return *this; } } m_keyframe = m_channel->previousKeyframe(m_keyframe->time()); if (!m_keyframe) return invalidate(); m_time = m_keyframe->time(); return *this; } KisVisibleKeyframeIterator& KisVisibleKeyframeIterator::operator++() { const QSharedPointer repeat = m_channel->activeRepeatAt(m_time); if (repeat) { const int time = repeat->nextVisibleFrame(m_time); if (time >= 0) { m_time = time; return *this; } } m_keyframe = m_channel->nextKeyframe(m_keyframe->time()); if (!m_keyframe) return invalidate(); m_time = m_keyframe->time(); return *this; }; KisKeyframeSP KisVisibleKeyframeIterator::operator*() const { const KisRepeatFrame *repeat = dynamic_cast(m_keyframe.data()); if (repeat) { return repeat->getOriginalKeyframeFor(m_time); } return m_keyframe; } KisKeyframeSP KisVisibleKeyframeIterator::operator->() const { return operator*(); } bool KisVisibleKeyframeIterator::isValid() const { return m_channel && m_time >= 0; } KisVisibleKeyframeIterator& KisVisibleKeyframeIterator::invalidate() { m_channel = nullptr; m_keyframe = KisKeyframeSP(); m_time = -1; return *this; } diff --git a/libs/image/kis_keyframe_channel.h b/libs/image/kis_keyframe_channel.h index 6c2b837ed3..8cdaa4dc1b 100644 --- a/libs/image/kis_keyframe_channel.h +++ b/libs/image/kis_keyframe_channel.h @@ -1,236 +1,239 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_KEYFRAME_CHANNEL_H #define KIS_KEYFRAME_CHANNEL_H #include #include #include #include "kis_types.h" #include "KoID.h" #include "kritaimage_export.h" #include "kis_keyframe.h" #include "kis_default_bounds.h" class KisFrameSet; class KisTimeSpan; class KisAnimationCycle; class KisRepeatFrame; class KisVisibleKeyframeIterator; class KisDefineCycleCommand; class KRITAIMAGE_EXPORT KisKeyframeChannel : public QObject { Q_OBJECT public: // Standard Keyframe Ids static const KoID Content; static const KoID Opacity; static const KoID TransformArguments; static const KoID TransformPositionX; static const KoID TransformPositionY; static const KoID TransformScaleX; static const KoID TransformScaleY; static const KoID TransformShearX; static const KoID TransformShearY; static const KoID TransformRotationX; static const KoID TransformRotationY; static const KoID TransformRotationZ; public: KisKeyframeChannel(const KoID& id, KisDefaultBoundsBaseSP defaultBounds); KisKeyframeChannel(const KisKeyframeChannel &rhs, KisNode *newParentNode); ~KisKeyframeChannel() override; QString id() const; QString name() const; void setNode(KisNodeWSP node); KisNodeWSP node() const; KisKeyframeSP addKeyframe(int time, KUndo2Command *parentCommand = 0); bool deleteKeyframe(KisKeyframeBaseSP keyframe, KUndo2Command *parentCommand = 0); bool moveKeyframe(KisKeyframeBaseSP keyframe, int newTime, KUndo2Command *parentCommand = 0); bool swapFrames(int lhsTime, int rhsTime, KUndo2Command *parentCommand = 0); KisKeyframeBaseSP copyItem(const KisKeyframeBaseSP item, int newTime, KUndo2Command *parentCommand = 0); KisKeyframeSP copyAsKeyframe(const KisKeyframeBaseSP item, int originalTime, int newTime, KUndo2Command *parentCommand = 0); virtual KisKeyframeSP linkKeyframe(const KisKeyframeBaseSP keyframe, int newTime, KUndo2Command *parentCommand = 0); KisKeyframeSP copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand = 0); KisDefineCycleCommand * createCycle(KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe, KUndo2Command *parentCommand = 0); KUndo2Command * deleteCycle(QSharedPointer cycle, KUndo2Command *parentCommand = 0); QSharedPointer addRepeat(QSharedPointer cycle, int time, KUndo2Command *parentCommand); bool swapExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand = 0); KisKeyframeSP keyframeAt(int time) const; KisKeyframeSP activeKeyframeAt(int time) const; KisKeyframeSP visibleKeyframeAt(int time) const; KisKeyframeSP currentlyActiveKeyframe() const; KisKeyframeSP firstKeyframe() const; KisKeyframeSP nextKeyframe(KisKeyframeSP keyframe) const; KisKeyframeSP previousKeyframe(KisKeyframeSP keyframe) const; KisKeyframeSP nextKeyframe(int time) const; KisKeyframeSP previousKeyframe(int time) const; KisKeyframeSP lastKeyframe() const; KisKeyframeBaseSP itemAt(int time) const; KisKeyframeBaseSP activeItemAt(int time) const; KisKeyframeBaseSP nextItem(const KisKeyframeBase &item) const; KisKeyframeBaseSP previousItem(const KisKeyframeBase &item) const; KisVisibleKeyframeIterator visibleKeyframesFrom(int time) const; + QList> cycles() const; + /** * Finds the original range of the cycle defined or repeated at the given time. * @arg time a time at any frame within the original cycle or any repeat of it. */ KisTimeSpan cycledRangeAt(int time) const; /** * Finds the cycle defined at time, if any. * @arg time a time within the original range of the cycle. */ QSharedPointer cycleAt(int time) const; /** * Finds the repeat of a cycle at the time, if any. */ QSharedPointer activeRepeatAt(int time) const; /** * Finds the span of time of the keyframe active at given time. * If there is no active keyframe, first will be -1. * If the keyframe continues indefinitely, last will be -1. */ void activeKeyframeRange(int time, int *first, int *last) const; /** * Calculates a pseudo-unique keyframes hash. The hash changes * every time any frame is added/removed/moved */ int framesHash() const; QSet allKeyframeIds() const; /** * Get the set of frames affected by any changes to the value * of the active keyframe at the given time. */ virtual KisFrameSet affectedFrames(int time) const; /** * Get a set of frames for which the channel gives identical * results, compared to the given frame. * * Note: this set may be different than the set of affected frames * due to interpolation. */ virtual KisFrameSet identicalFrames(int time, const KisTimeSpan range) const; virtual bool areFramesIdentical(int time1, int time2) const; virtual bool isFrameAffectedBy(int targetFrame, int changedFrame) const; int keyframeCount() const; virtual bool hasScalarValue() const = 0; virtual qreal minScalarValue() const; virtual qreal maxScalarValue() const; virtual qreal scalarValue(const KisKeyframeSP keyframe) const; virtual void setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand = 0); virtual QDomElement toXML(QDomDocument doc, const QString &layerFilename); virtual void loadXML(const QDomElement &channelNode); int currentTime() const; Q_SIGNALS: void sigKeyframeAboutToBeAdded(KisKeyframeBaseSP keyframe); void sigKeyframeAdded(KisKeyframeBaseSP keyframe); void sigKeyframeAboutToBeRemoved(KisKeyframeBaseSP keyframe); void sigKeyframeRemoved(KisKeyframeBaseSP keyframe); void sigKeyframeAboutToBeMoved(KisKeyframeBaseSP keyframe, int toTime); void sigKeyframeMoved(KisKeyframeBaseSP keyframe, int fromTime); void sigKeyframeChanged(KisKeyframeBaseSP keyframe); protected: typedef QMap KeyframesMap; KeyframesMap &keys(); const KeyframesMap &constKeys() const; virtual KisKeyframeSP createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) = 0; virtual void destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) = 0; virtual void uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) = 0; virtual void requestUpdate(const KisFrameSet &range, const QRect &rect); virtual KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode) = 0; virtual void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) = 0; void workaroundBrokenFrameTimeBug(int *time); private: KisKeyframeBaseSP replaceKeyframeAt(int time, KisKeyframeBaseSP newKeyframe); void insertKeyframeLogical(KisKeyframeBaseSP keyframe); void removeKeyframeLogical(KisKeyframeBaseSP keyframe); bool deleteKeyframeImpl(KisKeyframeBaseSP keyframe, KUndo2Command *parentCommand, bool recreate); void moveKeyframeImpl(KisKeyframeBaseSP keyframe, int newTime); void swapKeyframesImpl(KisKeyframeBaseSP lhsKeyframe, KisKeyframeBaseSP rhsKeyframe); void addCycle(QSharedPointer cycle); void removeCycle(QSharedPointer cycle); + friend class KisMoveKeyframesCommand; friend class KisMoveFrameCommand; friend class KisReplaceKeyframeCommand; friend class KisSwapFramesCommand; friend class KisDefineCycleCommand; private: KisKeyframeSP insertKeyframe(int time, const KisKeyframeBaseSP copySrc, KUndo2Command *parentCommand); QSharedPointer loadCycle(const QDomElement &cycleElement); struct Private; QScopedPointer m_d; }; class KisVisibleKeyframeIterator { public: KisVisibleKeyframeIterator(); explicit KisVisibleKeyframeIterator(KisKeyframeSP keyframe); KisVisibleKeyframeIterator& operator--(); KisVisibleKeyframeIterator& operator++(); bool isValid() const; KisKeyframeSP operator->() const; KisKeyframeSP operator*() const; private: KisVisibleKeyframeIterator &invalidate(); KisKeyframeChannel *m_channel{nullptr}; KisKeyframeSP m_keyframe; int m_time{-1}; }; #endif // KIS_KEYFRAME_CHANNEL_H diff --git a/libs/image/kis_keyframe_commands.cpp b/libs/image/kis_keyframe_commands.cpp index 8163a7fddf..169c959314 100644 --- a/libs/image/kis_keyframe_commands.cpp +++ b/libs/image/kis_keyframe_commands.cpp @@ -1,91 +1,390 @@ #include "kis_keyframe_commands.h" #include +#include +#include "kis_time_range.h" #include "kis_animation_cycle.h" +using CycleSP = QSharedPointer; +using KeyframeMove = KisKeyframeCommands::KeyframeMove; + +KisKeyframeCommands::KeyframeMove::KeyframeMove(KisKeyframeBaseSP keyframe, int newTime) + : keyframe(keyframe) + , oldTime(keyframe->time()) + , newTime(newTime) +{} + +class KisMoveKeyframesCommand : public KUndo2Command +{ +public: + KisMoveKeyframesCommand(QVector moves, KUndo2Command *parentCommand) + : KUndo2Command(parentCommand) + , m_moves(moves) + {} + + void redo() override + { + Q_FOREACH(const KeyframeMove &move, m_moves) { + move.keyframe->channel()->moveKeyframeImpl(move.keyframe, move.newTime); + } + } + + void undo() override + { + Q_FOREACH(const KeyframeMove &move, m_moves) { + move.keyframe->channel()->moveKeyframeImpl(move.keyframe, move.oldTime); + } + } + +private: + QVector m_moves; +}; + +struct KeyframeMapping +{ + KisKeyframeChannel *channel; + QMap destinations; + QMap sources; + + KeyframeMapping(KisKeyframeChannel *channel, const QVector &moves) + : channel(channel) + { + const int count = moves.count(); + + for (int i = 0; i < count; i++) { + const int time = moves[i].newTime; + const KisKeyframeBaseSP key = moves[i].keyframe; + + if (key->channel() == channel) { + destinations[key] = time; + sources[time] = key; + } + } + } + + bool isEmpty() const + { + return destinations.isEmpty(); + } + + int destination(const KisKeyframeSP &keyframe) const + { + const int oldTime = keyframe->time(); + const int newTime = destinations.value(keyframe, -1); + + if (newTime == -1 && !sources.value(oldTime, KisKeyframeBaseSP())) { + return oldTime; + } + + return newTime; + } + + KisKeyframeSP firstTrueKeyframeAfter(int destinationTime) const + { + KisKeyframeSP unmovedKeyframe; + { + int time = destinationTime; + KisKeyframeSP key; + do { + key = channel->nextKeyframe(time); + time = key ? key->time() : -1; + } while(destinations.contains(key)); + unmovedKeyframe = key; + } + + KisKeyframeSP movedKeyframe; + int movedTime; + { + auto sourceIt = KisCollectionUtils::firstAfter(sources, destinationTime); + // Skip non-keyframes (e.g. repeat frames) + while (sourceIt != sources.cend() && !sourceIt.value().dynamicCast()) { + sourceIt++; + } + movedTime = (sourceIt != sources.cend()) ? sourceIt.key() : -1; + } + + if (movedKeyframe && unmovedKeyframe) { + return (movedTime < unmovedKeyframe->time()) ? movedKeyframe : unmovedKeyframe; + } else { + return movedKeyframe ? movedKeyframe : unmovedKeyframe; + } + } +}; + +bool areValidMoveSources(const KisKeyframeChannel *channel, QVector moves) +{ + Q_FOREACH(const KeyframeMove &move, moves) { + if (move.keyframe->channel() != channel) return false; + } + + std::sort(moves.begin(), moves.end(), + [](const KeyframeMove &lhs, const KeyframeMove &rhs){ return lhs.oldTime < rhs.oldTime; } + ); + + for (int i = 1; i < moves.size(); i++) { + if (moves[i - 1].keyframe == moves[i].keyframe) return false; + } + + return true; +} + +CycleSP cycleAfterMove(const CycleSP &cycle, const KeyframeMapping &movedKeyframes) +{ + const KisKeyframeChannel *channel = cycle->channel(); + + KisTimeSpan newRange; + int firstDestination = -1, lastDestination = -1; + KisKeyframeSP newFirstKeyframe, newLastKeyframe; + + KisKeyframeSP key = cycle->firstSourceKeyframe(); + while (!key.isNull() && key->time() <= cycle->lastSourceKeyframe()->time()) { + const int destination = movedKeyframes.destinations.value(key, -1); + const int newTime = (destination >=0) ? destination : key->time(); + + const KisKeyframeBaseSP overwritingKey = (destination >= 0) + ? nullptr + : movedKeyframes.sources.value(key->time(), nullptr); + + if (!overwritingKey) { + if (!newFirstKeyframe || newTime < firstDestination) { + firstDestination = newTime; + newFirstKeyframe = key; + } + + if (!newLastKeyframe || newTime > lastDestination) { + lastDestination = newTime; + newLastKeyframe = key; + } + } + + key = channel->nextKeyframe(key->time()); + } + + if (!newLastKeyframe) return CycleSP(); + + if (newFirstKeyframe == cycle->firstSourceKeyframe() && newLastKeyframe == cycle->lastSourceKeyframe()) { + return cycle; + } + + return toQShared(new KisAnimationCycle(*cycle, newFirstKeyframe, newLastKeyframe)); +} + +QVector resolveCycleOverlaps(QVector &cycles, const KeyframeMapping &movedKeyframes) +{ + std::sort(cycles.begin(), cycles.end(), [&movedKeyframes](const CycleSP &lhs, const CycleSP &rhs) { + const int lhsTime = movedKeyframes.destination(lhs->firstSourceKeyframe()); + const int rhsTime = movedKeyframes.destination(rhs->firstSourceKeyframe()); + return lhsTime < rhsTime; + }); + + CycleSP lhs = cycles[0]; + for (int i = 1; i < cycles.size(); i++) { + const CycleSP &rhs = cycles[i]; + + const int lhsEnd = movedKeyframes.destination(lhs->lastSourceKeyframe()); + const int rhsStart = movedKeyframes.destination(rhs->firstSourceKeyframe()); + + if (rhsStart < lhsEnd) { + const int rhsEnd = movedKeyframes.destination(rhs->lastSourceKeyframe()); + + if (rhsEnd < lhsEnd) { + // Rhs cycle is entirely inside lhs one: drop it + cycles[i] = CycleSP(); + continue; + } else { + // TODO: logic for picking the cycle to truncate? + + KisKeyframeSP truncatedStart = movedKeyframes.firstTrueKeyframeAfter(lhsEnd); + cycles[i] = toQShared(new KisAnimationCycle(*rhs, truncatedStart, rhs->lastSourceKeyframe())); + } + } + + lhs = cycles[i]; + } + return cycles; +} + +QVector cyclesAfterMoves(const KeyframeMapping &movedKeyframes) +{ + const KisKeyframeChannel *channel = movedKeyframes.destinations.begin().key()->channel(); + + QVector cycles; + Q_FOREACH(const CycleSP cycle, channel->cycles()) { + const CycleSP newCycle = cycleAfterMove(cycle, movedKeyframes); + if (newCycle) { + cycles << newCycle; + } + } + + return resolveCycleOverlaps(cycles, movedKeyframes); +} + +bool validateRepeats(const QVector &cycles, const KeyframeMapping &movedKeyframes) +{ + Q_FOREACH(const CycleSP &cycle, cycles) { + if (!cycle) continue; + + const int cycleStart = movedKeyframes.destination(cycle->firstSourceKeyframe()); + const int cycleEnd = movedKeyframes.destination(cycle->lastSourceKeyframe()); + + // If any repeat frame would land within the cycle, refuse the operation. + + auto keyIt = KisCollectionUtils::firstAfter(movedKeyframes.sources, cycleStart); + while (keyIt != movedKeyframes.sources.cend() && keyIt.key() < cycleEnd) { + const QSharedPointer repeat = keyIt.value().dynamicCast(); + if (repeat) return false; + + keyIt++; + } + } + return true; +} + +void updateCycles(const KisKeyframeChannel *channel, QVector cyclesAfter, KUndo2Command *parentCommand) +{ + const QList &cyclesBefore = channel->cycles(); + + // Remove out-of-date definitions + Q_FOREACH(const CycleSP &cycle, cyclesBefore) { + if (!cyclesAfter.contains(cycle)) { + new KisDefineCycleCommand(cycle, nullptr, parentCommand); + } + } + + // Add new cycle definitions + Q_FOREACH(const CycleSP &cycle, cyclesAfter) { + if (!cyclesBefore.contains(cycle)) { + new KisDefineCycleCommand(nullptr, cycle, parentCommand); + } + } +} + +void deleteOverwrittenKeys(KeyframeMapping moves, KUndo2Command *parentCommand) +{ + QVector deletedKeys; + + for (auto it = moves.destinations.cbegin(); it != moves.destinations.cend(); ++it) { + const KisKeyframeBaseSP &keyframe = it.key(); + const int newTime = it.value(); + + const KisKeyframeBaseSP &originalKey = keyframe->channel()->itemAt(newTime); + + if (originalKey) { + const bool isOverwritten = !moves.destinations.contains(originalKey); + if (isOverwritten) { + deletedKeys.append(originalKey); + } + } + } + + Q_FOREACH(KisKeyframeBaseSP keyframe, deletedKeys) { + new KisReplaceKeyframeCommand(keyframe->channel(), keyframe->time(), KisKeyframeBaseSP(), parentCommand); + } +} + +KUndo2CommandSP KisKeyframeCommands::tryMoveKeyframes(KisKeyframeChannel *channel, QVector moves, KUndo2Command *parentCommand) +{ + KUndo2Command *command = new KUndo2Command(parentCommand); + + if (!areValidMoveSources(channel, moves)) return nullptr; + + const KeyframeMapping movedKeyframes(channel, moves); + if (movedKeyframes.isEmpty()) return nullptr; + + const QVector cycles = cyclesAfterMoves(movedKeyframes); + + if (!validateRepeats(cycles, movedKeyframes)) return nullptr; + + deleteOverwrittenKeys(movedKeyframes, command); + new KisMoveKeyframesCommand(moves, command); + updateCycles(channel, cycles, command); + + return toQShared(command); +} + KisReplaceKeyframeCommand::KisReplaceKeyframeCommand(KisKeyframeChannel *channel, int time, KisKeyframeBaseSP keyframe, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_time(time), m_keyframe(keyframe), m_existingKeyframe(0) { } void KisReplaceKeyframeCommand::redo() { m_existingKeyframe = m_channel->replaceKeyframeAt(m_time, m_keyframe); } void KisReplaceKeyframeCommand::undo() { m_channel->replaceKeyframeAt(m_time, m_existingKeyframe); } KisMoveFrameCommand::KisMoveFrameCommand(KisKeyframeChannel *channel, KisKeyframeBaseSP keyframe, int oldTime, int newTime, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldTime(oldTime), m_newTime(newTime) { } void KisMoveFrameCommand::redo() { m_channel->moveKeyframeImpl(m_keyframe, m_newTime); } void KisMoveFrameCommand::undo() { m_channel->moveKeyframeImpl(m_keyframe, m_oldTime); } KisSwapFramesCommand::KisSwapFramesCommand(KisKeyframeChannel *channel, KisKeyframeBaseSP lhsFrame, KisKeyframeBaseSP rhsFrame, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_lhsFrame(lhsFrame), m_rhsFrame(rhsFrame) { } void KisSwapFramesCommand::redo() { m_channel->swapKeyframesImpl(m_lhsFrame, m_rhsFrame); } void KisSwapFramesCommand::undo() { m_channel->swapKeyframesImpl(m_lhsFrame, m_rhsFrame); } -KisDefineCycleCommand::KisDefineCycleCommand(QSharedPointer oldCycle, QSharedPointer newCycle, KUndo2Command *parentCommand) +KisDefineCycleCommand::KisDefineCycleCommand(CycleSP oldCycle, CycleSP newCycle, KUndo2Command *parentCommand) : KUndo2Command(parentCommand) , m_channel(oldCycle ? oldCycle->channel() : newCycle->channel()) , m_oldCycle(oldCycle) , m_newCycle(newCycle) {} void KisDefineCycleCommand::redo() { if (m_oldCycle) { m_channel->removeCycle(m_oldCycle); } if (m_newCycle) { m_channel->addCycle(m_newCycle); } } void KisDefineCycleCommand::undo() { if (m_newCycle) { m_channel->removeCycle(m_newCycle); } if (m_oldCycle) { m_channel->addCycle(m_oldCycle); } } QSharedPointer KisDefineCycleCommand::cycle() const { return m_newCycle; } diff --git a/libs/image/kis_keyframe_commands.h b/libs/image/kis_keyframe_commands.h index 111dbe3935..03697fd5c6 100644 --- a/libs/image/kis_keyframe_commands.h +++ b/libs/image/kis_keyframe_commands.h @@ -1,84 +1,101 @@ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_KEYFRAME_COMMANDS_H #define KIS_KEYFRAME_COMMANDS_H #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kritaimage_export.h" +namespace KisKeyframeCommands +{ + struct KRITAIMAGE_EXPORT KeyframeMove + { + KisKeyframeBaseSP keyframe; + int oldTime, newTime; + + KeyframeMove() = default; + KeyframeMove(KisKeyframeBaseSP keyframe, int newTime); + }; + + /** + * Returns either a new command for operations needed to move the keyframes or null if the operation is invalid against the current state of the channel + */ + KRITAIMAGE_EXPORT KUndo2CommandSP tryMoveKeyframes(KisKeyframeChannel *channel, QVector moves, KUndo2Command *parentCommand); +} + class KRITAIMAGE_EXPORT KisReplaceKeyframeCommand : public KUndo2Command { public: KisReplaceKeyframeCommand(KisKeyframeChannel *channel, int time, KisKeyframeBaseSP keyframe, KUndo2Command *parentCommand); void redo() override; void undo() override; private: KisKeyframeChannel *m_channel; int m_time; KisKeyframeBaseSP m_keyframe; KisKeyframeBaseSP m_existingKeyframe; }; class KRITAIMAGE_EXPORT KisMoveFrameCommand : public KUndo2Command { public: KisMoveFrameCommand(KisKeyframeChannel *channel, KisKeyframeBaseSP keyframe, int oldTime, int newTime, KUndo2Command *parentCommand); void redo() override; void undo() override; private: KisKeyframeChannel *m_channel; KisKeyframeBaseSP m_keyframe; int m_oldTime; int m_newTime; }; class KRITAIMAGE_EXPORT KisSwapFramesCommand : public KUndo2Command { public: KisSwapFramesCommand(KisKeyframeChannel *channel, KisKeyframeBaseSP lhsFrame, KisKeyframeBaseSP rhsFrame, KUndo2Command *parentCommand); void redo() override; void undo() override; private: KisKeyframeChannel *m_channel; KisKeyframeBaseSP m_lhsFrame; KisKeyframeBaseSP m_rhsFrame; }; class KRITAIMAGE_EXPORT KisDefineCycleCommand : public KUndo2Command { public: KisDefineCycleCommand(QSharedPointer oldCycle, QSharedPointer newCycle, KUndo2Command *parentCommand); QSharedPointer cycle() const; void redo() override; void undo() override; private: KisKeyframeChannel *m_channel; QSharedPointer m_oldCycle; QSharedPointer m_newCycle; }; #endif