diff --git a/libs/image/kis_keyframe_channel.cpp b/libs/image/kis_keyframe_channel.cpp index ba2ed55c98..0e179a72af 100644 --- a/libs/image/kis_keyframe_channel.cpp +++ b/libs/image/kis_keyframe_channel.cpp @@ -1,1137 +1,1070 @@ /* * 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); } - - void addCycle(QSharedPointer cycle) { - cycles.insert(cycle->originalRange().start(), cycle); - - Q_FOREACH(QWeakPointer repeatWP, cycle->repeats()) { - KisKeyframeBaseSP repeat = repeatWP.toStrongRef(); - if (repeat) add(repeat); - } - } - - void removeCycle(QSharedPointer cycle) { - cycles.remove(cycle->originalRange().start()); - } }; 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) { - QSharedPointer cycle = toQShared(new KisAnimationCycle(this, rhsCycle->originalRange())); - 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->originalRange()))); - } - } - + Q_FOREACH(const QSharedPointer repeat, rhs.m_d->repeats) { + m_d->add(toQShared(new KisRepeatFrame(*repeat, this))); } + } 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; const int srcTime = keyframe->time(); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, newTime, keyframe, 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)); KIS_SAFE_ASSERT_RECOVER_NOOP(itemAt(keyframe->time()) == keyframe); 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; } KisKeyframeBaseSP KisKeyframeChannel::nextItem(int time) const { const KisKeyframeBaseSP activeItem = activeItemAt(time); return activeItem ? nextItem(*activeItem) : nullptr; } KisRangedKeyframeIterator KisKeyframeChannel::itemsWithin(KisTimeSpan range) const { return KisRangedKeyframeIterator(this, range); } KisVisibleKeyframeIterator KisKeyframeChannel::visibleKeyframesFrom(int time) const { return KisVisibleKeyframeIterator(visibleKeyframeAt(time)); } -QList> KisKeyframeChannel::cycles() const +QList> KisKeyframeChannel::cycles() const { - return m_d->cycles.values(); + return m_d->repeats.values(); } KisTimeSpan KisKeyframeChannel::cycledRangeAt(int time) const { QSharedPointer repeat = activeRepeatAt(time); if (repeat) return repeat->sourceRange(); - 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(); - - if (!it.value()->originalRange().contains(time)) return nullptr; - 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); + QSharedPointer activeRepeat = activeRepeatAt(time); - if (repeat) { - const KisKeyframeSP original = repeat->getOriginalKeyframeFor(time); + if (activeRepeat) { + const KisKeyframeSP original = activeRepeat->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); } + Q_FOREACH(QSharedPointer repeat, m_d->repeats) { + if (repeat->sourceRange().contains(time)) { + frames |= repeat->instancesWithin(active.value(), KisTimeSpan()); + } + } + return frames; } KisFrameSet KisKeyframeChannel::identicalFrames(int time, const KisTimeSpan range) const { - const QSharedPointer repeat = activeRepeatAt(time); - if (repeat) { - const KisKeyframeSP original = repeat->getOriginalKeyframeFor(time); + const QSharedPointer activeRepeat = activeRepeatAt(time); + if (activeRepeat) { + const KisKeyframeSP original = activeRepeat->getOriginalKeyframeFor(time); return identicalFrames(original->time(), range); } - KeyframesMap::const_iterator active = KisCollectionUtils::lastBeforeOrAt(m_d->keys, time); + const KeyframesMap::const_iterator active = KisCollectionUtils::lastBeforeOrAt(m_d->keys, time); + const KeyframesMap::const_iterator next = (active != m_d->keys.constEnd() ? (active + 1) : m_d->keys.constBegin()); + + KisFrameSet frames; - const QSharedPointer cycle = cycleAt(time); - if (cycle) { - return cycle->instancesWithin(active.value(), range); + if (active == m_d->keys.constEnd()) { + // No active keyframe, ie. time is before the first keyframe + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(next != m_d->keys.constEnd(), KisFrameSet()); + frames = KisFrameSet::between(0, (*next)->time()); + } else if (next == m_d->keys.constEnd()) { + frames = KisFrameSet::infiniteFrom((*active)->time()); + } else { + if (active->data()->interpolationMode() == KisKeyframe::Constant) { + KisKeyframeSP nextKeyframe = *next; + frames = KisFrameSet::between(time, nextKeyframe->time() - 1); + } else { + frames = KisFrameSet::between(time, time); + } } - if (active != m_d->keys.constEnd() && (active+1) != m_d->keys.constEnd()) { - if (active->data()->interpolationMode() != KisKeyframe::Constant) { - return KisFrameSet::between(time, time); + Q_FOREACH(QSharedPointer repeat, m_d->repeats) { + if (repeat->sourceRange().contains(time)) { + frames |= repeat->instancesWithin(active.value(), range); } } - return affectedFrames(time); + return frames; } 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("start", cycle->originalRange().start()); - cycleElement.setAttribute("end", cycle->originalRange().end()); - - 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); - } + Q_FOREACH(QSharedPointer repeat, m_d->repeats) { + QDomElement cycleElement = doc.createElement("repeat"); + cycleElement.setAttribute("time", repeat->time()); + cycleElement.setAttribute("rangeFrom", repeat->sourceRange().start()); + cycleElement.setAttribute("rangeTo", repeat->sourceRange().end()); 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 (childNode.nodeName().toUpper() == "REPEAT") { + QSharedPointer cycle = loadCycle(childNode); if (cycle) { - m_d->addCycle(cycle); + m_d->add(cycle); } } } } -QSharedPointer KisKeyframeChannel::loadCycle(const QDomElement &cycleElement) +QSharedPointer KisKeyframeChannel::loadCycle(const QDomElement &cycleElement) { - const int startTime = cycleElement.attribute("start", "-1").toInt(); - const int endTime = cycleElement.attribute("end", "-1").toInt(); + const int time = cycleElement.attribute("time", "-1").toInt(); + const int startTime = cycleElement.attribute("rangeFrom", "-1").toInt(); + const int endTime = cycleElement.attribute("rangeTo", "-1").toInt(); if (startTime < 0 || endTime <= startTime) { qWarning() << "Invalid cycle range: " << startTime << "to" << endTime; return nullptr; } - QSharedPointer cycle = toQShared(new KisAnimationCycle(this, KisTimeSpan(startTime, endTime))); - - 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->originalRange())); - m_d->add(repeat); - } - } + QSharedPointer cycle = toQShared(new KisRepeatFrame(this, time, KisTimeSpan(startTime, endTime))); 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(KisTimeSpan range, KUndo2Command *parentCommand) +QSharedPointer KisKeyframeChannel::createRepeat(int time, KisTimeSpan sourceRange, KUndo2Command *parentCommand) { - const QSharedPointer cycle = toQShared(new KisAnimationCycle(this, range)); - return new KisDefineCycleCommand(nullptr, cycle, parentCommand); -} + QSharedPointer repeatFrame = toQShared(new KisRepeatFrame(this, time, sourceRange)); -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); - } + KisKeyframeBaseSP oldItem = itemAt(time); + if (oldItem) { + deleteKeyframeImpl(oldItem, parentCommand, false); } - 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->originalRange())); 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); } KisKeyframeBaseSP firstKeyframeInRange(const KisKeyframeChannel *channel, KisTimeSpan range) { KisKeyframeBaseSP active = channel->activeItemAt(range.start()); if (active) { if (range.contains(active->time())) return active; if (active->time() < range.start()) { const KisKeyframeBaseSP next = channel->nextItem(*active); if (next && range.contains(next->time())) return next; } } return KisKeyframeBaseSP(); } KisRangedKeyframeIterator::KisRangedKeyframeIterator() {} KisRangedKeyframeIterator::KisRangedKeyframeIterator(const KisKeyframeChannel *channel, KisKeyframeBaseSP keyframe, KisTimeSpan range) : m_channel(channel) , m_keyframe(keyframe) , m_range(range) {} KisRangedKeyframeIterator::KisRangedKeyframeIterator(const KisKeyframeChannel *channel, KisTimeSpan range) : KisRangedKeyframeIterator(channel, firstKeyframeInRange(channel, range), range) {} KisRangedKeyframeIterator& KisRangedKeyframeIterator::operator++() { if (!m_keyframe) return *this; m_keyframe = m_channel->nextItem(*m_keyframe); if (!m_keyframe || !m_range.contains(m_keyframe->time())) { m_keyframe = nullptr; } return *this; } KisRangedKeyframeIterator& KisRangedKeyframeIterator::operator--() { if (!m_keyframe) { // One-past-end state: return to last keyframe in range const KisKeyframeBaseSP last = m_channel->activeItemAt(m_range.end()); if (m_range.contains(last->time())) m_keyframe = last; } else { const KisKeyframeBaseSP previousKeyframe = m_channel->previousItem(*m_keyframe); if (previousKeyframe && m_range.contains(previousKeyframe->time())) { m_keyframe = previousKeyframe; } } return *this; } KisKeyframeBaseSP KisRangedKeyframeIterator::operator*() const { return m_keyframe; } KisKeyframeBaseSP KisRangedKeyframeIterator::operator->() const { return m_keyframe; } KisRangedKeyframeIterator KisRangedKeyframeIterator::begin() const { return KisRangedKeyframeIterator(m_channel, m_range); } KisRangedKeyframeIterator KisRangedKeyframeIterator::end() const { return KisRangedKeyframeIterator(m_channel, nullptr, m_range); } bool KisRangedKeyframeIterator::isValid() const { return m_keyframe != nullptr; } bool KisRangedKeyframeIterator::operator==(const KisRangedKeyframeIterator &rhs) const { return m_keyframe == rhs.m_keyframe && m_range == rhs.m_range; } bool KisRangedKeyframeIterator::operator!=(const KisRangedKeyframeIterator &rhs) const { return !(rhs == *this); } 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 b63d7bed14..3f35b4816c 100644 --- a/libs/image/kis_keyframe_channel.h +++ b/libs/image/kis_keyframe_channel.h @@ -1,271 +1,257 @@ /* * 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" #include "kis_time_range.h" class KisFrameSet; class KisTimeSpan; -class KisAnimationCycle; class KisRepeatFrame; class KisRangedKeyframeIterator; 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(KisTimeSpan range, KUndo2Command *parentCommand = 0); - KUndo2Command * deleteCycle(QSharedPointer cycle, KUndo2Command *parentCommand = 0); - QSharedPointer addRepeat(QSharedPointer cycle, int time, KUndo2Command *parentCommand); + QSharedPointer createRepeat(int time, KisTimeSpan sourceRange, 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; KisRangedKeyframeIterator itemsWithin(KisTimeSpan range) const; KisKeyframeBaseSP nextItem(int time) const; KisVisibleKeyframeIterator visibleKeyframesFrom(int time) const; - QList> cycles() 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 KisReplaceKeyframeCommand; friend class KisSwapFramesCommand; - friend class KisDefineCycleCommand; - friend class KisRangedKeyframeIterator; private: KisKeyframeSP insertKeyframe(int time, const KisKeyframeBaseSP copySrc, KUndo2Command *parentCommand); - QSharedPointer loadCycle(const QDomElement &cycleElement); + QSharedPointer loadCycle(const QDomElement &cycleElement); struct Private; QScopedPointer m_d; }; class KisRangedKeyframeIterator { public: KisRangedKeyframeIterator(); KisRangedKeyframeIterator(const KisKeyframeChannel *channel, KisTimeSpan range); KisRangedKeyframeIterator& operator--(); KisRangedKeyframeIterator& operator++(); KisKeyframeBaseSP operator->() const; KisKeyframeBaseSP operator*() const; bool operator==(const KisRangedKeyframeIterator &rhs) const; bool operator!=(const KisRangedKeyframeIterator &rhs) const; KisRangedKeyframeIterator begin() const; KisRangedKeyframeIterator end() const; bool isValid() const; private: KisRangedKeyframeIterator(const KisKeyframeChannel *channel, KisKeyframeBaseSP keyframe, KisTimeSpan range); const KisKeyframeChannel *m_channel{nullptr}; KisKeyframeBaseSP m_keyframe; KisTimeSpan m_range; }; 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 9d1996f673..34a666cfc3 100644 --- a/libs/image/kis_keyframe_commands.cpp +++ b/libs/image/kis_keyframe_commands.cpp @@ -1,117 +1,80 @@ #include "kis_keyframe_commands.h" #include #include #include #include "kis_time_range.h" -#include "kis_animation_cycle.h" - -using CycleSP = QSharedPointer; KisReplaceKeyframeCommand::KisReplaceKeyframeCommand(KisKeyframeChannel *channel, int time, KisKeyframeBaseSP keyframe, KUndo2Command *parentCommand) : KUndo2Command(parentCommand) , m_channel(channel) , m_keyframe(keyframe) , m_newTime(time) {} void KisReplaceKeyframeCommand::redo() { if (m_newTime >= 0) { m_overwrittenKeyframe = m_channel->itemAt(m_newTime); if (m_overwrittenKeyframe) { m_channel->removeKeyframeLogical(m_overwrittenKeyframe); } } if (m_keyframe) { const bool currentlyOnChannel = m_channel->itemAt(m_keyframe->time()) == m_keyframe; m_oldTime = currentlyOnChannel ? m_keyframe->time() : -1; } moveKeyframeTo(m_newTime); } void KisReplaceKeyframeCommand::undo() { moveKeyframeTo(m_oldTime); if (m_overwrittenKeyframe) { m_overwrittenKeyframe->setTime(m_newTime); m_channel->insertKeyframeLogical(m_overwrittenKeyframe); m_overwrittenKeyframe = nullptr; } } void KisReplaceKeyframeCommand::moveKeyframeTo(int dstTime) { if (!m_keyframe) return; const bool currentlyOnChannel = m_channel->itemAt(m_keyframe->time()) == m_keyframe; if (dstTime < 0) { if (currentlyOnChannel) { m_channel->removeKeyframeLogical(m_keyframe); } } else { if (currentlyOnChannel) { m_channel->moveKeyframeImpl(m_keyframe, dstTime); } else { m_keyframe->setTime(dstTime); m_channel->insertKeyframeLogical(m_keyframe); } } } 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(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 264fb99f44..4416a8ba58 100644 --- a/libs/image/kis_keyframe_commands.h +++ b/libs/image/kis_keyframe_commands.h @@ -1,78 +1,62 @@ /* * 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" /** * Places the keyframe on its channel at the specified time. * If the time is negative, the keyframe is removed from the channel. * Otherwise, the keyframe is moved within or inserted onto the channel. * Any overwritten keyframe will be restored on undo(). */ 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; KisKeyframeBaseSP m_keyframe; KisKeyframeBaseSP m_overwrittenKeyframe; int m_oldTime{-1}; int m_newTime; void moveKeyframeTo(int dstTime); }; 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 = nullptr); - - QSharedPointer cycle() const; - - void redo() override; - void undo() override; - -private: - KisKeyframeChannel *m_channel; - QSharedPointer m_oldCycle; - QSharedPointer m_newCycle; -}; - #endif diff --git a/libs/image/tests/kis_keyframing_test.cpp b/libs/image/tests/kis_keyframing_test.cpp index 167ae0bf33..41f4d2dd52 100644 --- a/libs/image/tests/kis_keyframing_test.cpp +++ b/libs/image/tests/kis_keyframing_test.cpp @@ -1,583 +1,574 @@ /* * 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_keyframing_test.h" #include #include #include "kis_paint_device_frames_interface.h" #include "kis_keyframe_channel.h" #include "kis_scalar_keyframe_channel.h" #include "kis_raster_keyframe_channel.h" #include "kis_node.h" #include "kis_time_range.h" #include "kis_animation_cycle.h" #include "kundo2command.h" #include "kis_pointer_utils.h" #include #include "testing_timed_default_bounds.h" void KisKeyframingTest::initTestCase() { cs = KoColorSpaceRegistry::instance()->rgb8(); red = new quint8[cs->pixelSize()]; green = new quint8[cs->pixelSize()]; blue = new quint8[cs->pixelSize()]; cs->fromQColor(Qt::red, red); cs->fromQColor(Qt::green, green); cs->fromQColor(Qt::blue, blue); } void KisKeyframingTest::cleanupTestCase() { delete[] red; delete[] green; delete[] blue; } void KisKeyframingTest::testScalarChannel() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), -17, 31, 0); KisKeyframeSP key; bool ok; QCOMPARE(channel->hasScalarValue(), true); QCOMPARE(channel->minScalarValue(), -17.0); QCOMPARE(channel->maxScalarValue(), 31.0); QVERIFY(channel->keyframeAt(0) == 0); // Adding new keyframe key = channel->addKeyframe(42); channel->setScalarValue(key, 7.0); key = channel->keyframeAt(42); QCOMPARE(channel->scalarValue(key), 7.0); // Copying a keyframe KisKeyframeSP key2 = channel->copyAsKeyframe(key, 2, 13); QVERIFY(key2 != 0); QVERIFY(channel->keyframeAt(13) == key2); QCOMPARE(channel->scalarValue(key2), 7.0); // Adding a keyframe where one exists key2 = channel->addKeyframe(13); QVERIFY(key2 != key); QCOMPARE(channel->keyframeCount(), 2); // Moving keyframes ok = channel->moveKeyframe(key, 10); QCOMPARE(ok, true); QVERIFY(channel->keyframeAt(42) == 0); key = channel->keyframeAt(10); QCOMPARE(channel->scalarValue(key), 7.0); // Moving a keyframe where another one exists ok = channel->moveKeyframe(key, 13); QCOMPARE(ok, true); QVERIFY(channel->keyframeAt(13) != key2); // Deleting a keyframe channel->deleteKeyframe(key); QVERIFY(channel->keyframeAt(10) == 0); QCOMPARE(channel->keyframeCount(), 0); delete channel; } void KisKeyframingTest::testScalarChannelUndoRedo() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), -17, 31, 0); KisKeyframeSP key; QCOMPARE(channel->hasScalarValue(), true); QCOMPARE(channel->minScalarValue(), -17.0); QCOMPARE(channel->maxScalarValue(), 31.0); QVERIFY(channel->keyframeAt(0) == 0); // Adding new keyframe KUndo2Command addCmd; key = channel->addKeyframe(42, &addCmd); channel->setScalarValue(key, 7.0, &addCmd); key = channel->keyframeAt(42); QCOMPARE(channel->scalarValue(key), 7.0); addCmd.undo(); KisKeyframeSP newKey; newKey = channel->keyframeAt(42); QVERIFY(!newKey); addCmd.redo(); newKey = channel->keyframeAt(42); QVERIFY(newKey); QCOMPARE(channel->scalarValue(key), 7.0); QCOMPARE(channel->scalarValue(newKey), 7.0); delete channel; } void KisKeyframingTest::testScalarInterpolation() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), 0, 30, 0); KisKeyframeSP key1 = channel->addKeyframe(0); channel->setScalarValue(key1, 15); KisKeyframeSP key2 = channel->addKeyframe(10); channel->setScalarValue(key2, 30); // Constant key1->setInterpolationMode(KisKeyframe::Constant); QCOMPARE(channel->interpolatedValue(4), 15.0f); // Bezier key1->setInterpolationMode(KisKeyframe::Bezier); key1->setInterpolationTangents(QPointF(), QPointF(1,4)); key2->setInterpolationTangents(QPointF(-4,2), QPointF()); QVERIFY(qAbs(channel->interpolatedValue(4) - 24.9812f) < 0.1f); // Bezier, self-intersecting curve (auto-correct) channel->setScalarValue(key2, 15); key1->setInterpolationTangents(QPointF(), QPointF(13,10)); key2->setInterpolationTangents(QPointF(-13,10), QPointF()); QVERIFY(qAbs(channel->interpolatedValue(5) - 20.769f) < 0.1f); // Bezier, result outside allowed range (clamp) channel->setScalarValue(key2, 15); key1->setInterpolationTangents(QPointF(), QPointF(0, 50)); key2->setInterpolationTangents(QPointF(0, 50), QPointF()); QCOMPARE(channel->interpolatedValue(5), 30.0f); delete channel; } void KisKeyframingTest::testRasterChannel() { TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel * channel = dev->createKeyframeChannel(KoID()); QCOMPARE(channel->hasScalarValue(), false); QCOMPARE(channel->keyframeCount(), 1); QCOMPARE(dev->framesInterface()->frames().count(), 1); QCOMPARE(channel->frameIdAt(0), 0); QVERIFY(channel->keyframeAt(0) != 0); KisKeyframeSP key_0 = channel->keyframeAt(0); // New keyframe KisKeyframeSP key_10 = channel->addKeyframe(10); QCOMPARE(channel->keyframeCount(), 2); QCOMPARE(dev->framesInterface()->frames().count(), 2); QVERIFY(channel->frameIdAt(10) != 0); dev->fill(0, 0, 512, 512, red); QImage thumb1a = dev->createThumbnail(50, 50); bounds->testingSetTime(10); dev->fill(0, 0, 512, 512, green); QImage thumb2a = dev->createThumbnail(50, 50); bounds->testingSetTime(0); QImage thumb1b = dev->createThumbnail(50, 50); QVERIFY(thumb2a != thumb1a); QVERIFY(thumb1b == thumb1a); // Duplicate keyframe KisKeyframeSP key_20 = channel->copyAsKeyframe(key_0, 0, 20); bounds->testingSetTime(20); QImage thumb3a = dev->createThumbnail(50, 50); QVERIFY(thumb3a == thumb1b); dev->fill(0, 0, 512, 512, blue); QImage thumb3b = dev->createThumbnail(50, 50); bounds->testingSetTime(0); QImage thumb1c = dev->createThumbnail(50, 50); QVERIFY(thumb3b != thumb3a); QVERIFY(thumb1c == thumb1b); // Delete keyrame QCOMPARE(channel->keyframeCount(), 3); QCOMPARE(dev->framesInterface()->frames().count(), 3); channel->deleteKeyframe(key_20); QCOMPARE(channel->keyframeCount(), 2); QCOMPARE(dev->framesInterface()->frames().count(), 2); QVERIFY(channel->keyframeAt(20) == 0); channel->deleteKeyframe(key_10); QCOMPARE(channel->keyframeCount(), 1); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->keyframeAt(10) == 0); channel->deleteKeyframe(key_0); QCOMPARE(channel->keyframeCount(), 1); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->keyframeAt(0) != 0); } void KisKeyframingTest::testChannelSignals() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), -17, 31, 0); KisKeyframeBaseSP key; KisKeyframeBaseSP resKey; qRegisterMetaType("KisKeyframeBaseSP"); QSignalSpy spyPreAdd(channel, SIGNAL(sigKeyframeAboutToBeAdded(KisKeyframeBaseSP))); QSignalSpy spyPostAdd(channel, SIGNAL(sigKeyframeAdded(KisKeyframeBaseSP))); QSignalSpy spyPreRemove(channel, SIGNAL(sigKeyframeAboutToBeRemoved(KisKeyframeBaseSP))); QSignalSpy spyPostRemove(channel, SIGNAL(sigKeyframeRemoved(KisKeyframeBaseSP))); QSignalSpy spyPreMove(channel, SIGNAL(sigKeyframeAboutToBeMoved(KisKeyframeBaseSP,int))); QSignalSpy spyPostMove(channel, SIGNAL(sigKeyframeMoved(KisKeyframeBaseSP,int))); QVERIFY(spyPreAdd.isValid()); QVERIFY(spyPostAdd.isValid()); QVERIFY(spyPreRemove.isValid()); QVERIFY(spyPostRemove.isValid()); QVERIFY(spyPreMove.isValid()); QVERIFY(spyPostMove.isValid()); // Adding a keyframe QCOMPARE(spyPreAdd.count(), 0); QCOMPARE(spyPostAdd.count(), 0); key = channel->addKeyframe(10); QCOMPARE(spyPreAdd.count(), 1); QCOMPARE(spyPostAdd.count(), 1); resKey = spyPreAdd.at(0).at(0).value(); QVERIFY(resKey == key); resKey = spyPostAdd.at(0).at(0).value(); QVERIFY(resKey == key); // Moving a keyframe QCOMPARE(spyPreMove.count(), 0); QCOMPARE(spyPostMove.count(), 0); channel->moveKeyframe(key, 15); QCOMPARE(spyPreMove.count(), 1); QCOMPARE(spyPostMove.count(), 1); resKey = spyPreMove.at(0).at(0).value(); QVERIFY(resKey == key); QCOMPARE(spyPreMove.at(0).at(1).toInt(), 15); resKey = spyPostMove.at(0).at(0).value(); QVERIFY(resKey == key); // No-op move (no signals) channel->moveKeyframe(key, 15); QCOMPARE(spyPreMove.count(), 1); QCOMPARE(spyPostMove.count(), 1); // Deleting a keyframe QCOMPARE(spyPreRemove.count(), 0); QCOMPARE(spyPostRemove.count(), 0); channel->deleteKeyframe(key); QCOMPARE(spyPreRemove.count(), 1); QCOMPARE(spyPostRemove.count(), 1); delete channel; } void KisKeyframingTest::testRasterFrameFetching() { TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); KisPaintDeviceSP dev = new KisPaintDevice(cs); KisPaintDeviceSP devTarget = new KisPaintDevice(cs); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel * channel = dev->createKeyframeChannel(KoID()); channel->addKeyframe(0); channel->addKeyframe(10); channel->addKeyframe(50); bounds->testingSetTime(0); dev->fill(0, 0, 512, 512, red); QImage frame1 = dev->createThumbnail(50, 50); bounds->testingSetTime(10); dev->fill(0, 256, 512, 512, green); QImage frame2 = dev->createThumbnail(50, 50); bounds->testingSetTime(50); dev->fill(0, 0, 256, 512, blue); QImage frame3 = dev->createThumbnail(50, 50); bounds->testingSetTime(10); KisKeyframeSP keyframe = channel->activeKeyframeAt(0); channel->fetchFrame(keyframe, devTarget); QImage fetched1 = devTarget->createThumbnail(50, 50); keyframe = channel->activeKeyframeAt(10); channel->fetchFrame(keyframe, devTarget); QImage fetched2 = devTarget->createThumbnail(50, 50); keyframe = channel->activeKeyframeAt(50); channel->fetchFrame(keyframe, devTarget); QImage fetched3 = devTarget->createThumbnail(50, 50); QVERIFY(fetched1 == frame1); QVERIFY(fetched2 == frame2); QVERIFY(fetched3 == frame3); } void KisKeyframingTest::testDeleteFirstRasterChannel() { // Test Plan: // // delete // undo delete // move // undo move TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel * channel = dev->createKeyframeChannel(KoID()); QCOMPARE(channel->hasScalarValue(), false); QCOMPARE(channel->keyframeCount(), 1); QCOMPARE(dev->framesInterface()->frames().count(), 1); QCOMPARE(channel->frameIdAt(0), 0); QVERIFY(channel->keyframeAt(0) != 0); KisKeyframeSP key_0 = channel->keyframeAt(0); { KUndo2Command cmd; bool deleteResult = channel->deleteKeyframe(key_0, &cmd); QVERIFY(deleteResult); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->frameIdAt(0) != 0); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(0) != key_0); cmd.undo(); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->frameIdAt(0) == 0); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(0) == key_0); } { KUndo2Command cmd; bool moveResult = channel->moveKeyframe(key_0, 1, &cmd); QVERIFY(moveResult); QCOMPARE(dev->framesInterface()->frames().count(), 2); QVERIFY(channel->frameIdAt(0) != 0); QVERIFY(channel->frameIdAt(1) == 0); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(1)); QVERIFY(channel->keyframeAt(0) != key_0); QVERIFY(channel->keyframeAt(1) == key_0); cmd.undo(); QCOMPARE(dev->framesInterface()->frames().count(), 1); QVERIFY(channel->frameIdAt(0) == 0); QVERIFY(channel->keyframeAt(0)); QVERIFY(channel->keyframeAt(0) == key_0); } } void KisKeyframingTest::testAffectedFrames() { KisScalarKeyframeChannel *channel = new KisScalarKeyframeChannel(KoID(""), -17, 31, 0); KisFrameSet frames; channel->addKeyframe(10); channel->addKeyframe(20); channel->addKeyframe(30); // At a keyframe frames = channel->affectedFrames(20); QCOMPARE(frames, KisFrameSet::between(20, 29)); // Between frames frames = channel->affectedFrames(25); QCOMPARE(frames, KisFrameSet::between(20, 29)); // Before first frame frames = channel->affectedFrames(5); QCOMPARE(frames, KisFrameSet::between(0, 9)); // After last frame frames = channel->affectedFrames(35); QCOMPARE(frames, KisFrameSet::infiniteFrom(30)); // Linked keyframes KisPaintDeviceSP dev = new KisPaintDevice(cs); KisRasterKeyframeChannel * rasterChannel = dev->createKeyframeChannel(KoID()); auto key0 = rasterChannel->addKeyframe(0); auto key5 = rasterChannel->addKeyframe(5); auto key10 = rasterChannel->linkKeyframe(key0, 10, nullptr); QCOMPARE(rasterChannel->affectedFrames(5), KisFrameSet::between(5,9)); KisFrameSet result = rasterChannel->affectedFrames(1); QCOMPARE(result, KisFrameSet::between(0, 4) | KisFrameSet::infiniteFrom(10)); result = rasterChannel->affectedFrames(15); QCOMPARE(result, KisFrameSet::between(0, 4) | KisFrameSet::infiniteFrom(10)); } void KisKeyframingTest::testMovingFrames() { TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel * srcChannel = dev->createKeyframeChannel(KoID()); srcChannel->addKeyframe(0); srcChannel->addKeyframe(10); srcChannel->addKeyframe(50); KisPaintDeviceSP dev2 = new KisPaintDevice(*dev, KritaUtils::CopyAllFrames); KisRasterKeyframeChannel * dstChannel = dev->keyframeChannel(); for (int i = 0; i < 1000; i++) { const int srcTime = 50 + i; const int dstTime = 60 + i; const int src2Time = 51 + i; { KUndo2Command parentCommand; KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); KIS_ASSERT(srcKeyframe); dstChannel->copyExternalKeyframe(srcChannel, srcTime, dstTime, &parentCommand); srcChannel->deleteKeyframe(srcKeyframe, &parentCommand); } for (int j = qMax(0, i-15); j < i+5; ++j) { bounds->testingSetTime(j); QRect rc1 = dev->extent(); QRect rc2 = dev2->extent(); Q_UNUSED(rc1); Q_UNUSED(rc2); } { KUndo2Command parentCommand; KisKeyframeSP dstKeyframe = dstChannel->keyframeAt(dstTime); KIS_ASSERT(dstKeyframe); srcChannel->copyExternalKeyframe(dstChannel, dstTime, src2Time, &parentCommand); dstChannel->deleteKeyframe(dstKeyframe, &parentCommand); } } } void KisKeyframingTest::testCycles() { TestUtil::TestingTimedDefaultBounds *bounds = new TestUtil::TestingTimedDefaultBounds(); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->setDefaultBounds(bounds); KisRasterKeyframeChannel *channel = dev->createKeyframeChannel(KoID()); KisKeyframeSP frame10 = channel->addKeyframe(10); channel->addKeyframe(12); KisKeyframeSP frame16 = channel->addKeyframe(16); channel->addKeyframe(20); - auto cmd = channel->createCycle({10, 19}); - cmd->redo(); + QScopedPointer cmd(new KUndo2Command()); + channel->createRepeat(30, {10, 19}, cmd.data()); - // Cycled range can be queried from by any frame within it - QCOMPARE(channel->cycledRangeAt(9), KisTimeSpan()); - QCOMPARE(channel->cycledRangeAt(20), KisTimeSpan()); - QCOMPARE(channel->cycledRangeAt(10), KisTimeSpan(10, 19)); - QCOMPARE(channel->cycledRangeAt(19), KisTimeSpan(10, 19)); - - QSharedPointer repeatFrame = toQShared(new KisRepeatFrame(channel, 30, {10, 19})); - KisReplaceKeyframeCommand(channel, 30, repeatFrame, nullptr).redo(); - - // Repeats also resolve to the original cycled range + // Repeats resolve to the original cycled range QCOMPARE(channel->cycledRangeAt(29), KisTimeSpan()); QCOMPARE(channel->cycledRangeAt(30), KisTimeSpan(10, 19)); QCOMPARE(channel->cycledRangeAt(50), KisTimeSpan(10, 19)); QCOMPARE(channel->cycledRangeAt(100), KisTimeSpan(10, 19)); // Affected frames contain original frames and repeats QCOMPARE(channel->affectedFrames(15), KisFrameSet::between(12, 15) | KisFrameSet::infiniteFrom(32)); QCOMPARE(channel->affectedFrames(50), KisFrameSet::between(10, 11) | KisFrameSet::infiniteFrom(30)); // All repeats within the queried range are reported as identical. Original is always included. QCOMPARE(channel->identicalFrames(50, KisTimeSpan(40, 60)), KisFrameSet({{10, 11}, {40, 41}, {50, 51}, {60, 60}})); // Repeat ends at the next keyframe channel->addKeyframe(42); QCOMPARE(channel->cycledRangeAt(41), KisTimeSpan(10, 19)); QCOMPARE(channel->cycledRangeAt(42), KisTimeSpan()); // Finitely many repeats are all included separately as affected QCOMPARE(channel->affectedFrames(40), KisFrameSet({{10, 11}, {30, 31}, {40, 41}})); } QTEST_MAIN(KisKeyframingTest)