diff --git a/libs/image/kis_animation_cycle.cpp b/libs/image/kis_animation_cycle.cpp index e7d39d44cb..5207c4cede 100644 --- a/libs/image/kis_animation_cycle.cpp +++ b/libs/image/kis_animation_cycle.cpp @@ -1,191 +1,221 @@ /* * 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(KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe) : m_firstSourceKeyframe(firstKeyframe) , m_lastSourceKeyframe(lastKeyframe) {} void KisAnimationCycle::setFirstKeyframe(KisKeyframeSP keyframe) { m_firstSourceKeyframe = keyframe; } void KisAnimationCycle::setLastKeyframe(KisKeyframeSP keyframe) { m_lastSourceKeyframe = keyframe; } KisKeyframeSP KisAnimationCycle::firstSourceKeyframe() const { return m_firstSourceKeyframe; } KisKeyframeSP KisAnimationCycle::lastSourceKeyframe() const { return m_lastSourceKeyframe; } KisTimeSpan KisAnimationCycle::originalRange() const { const KisKeyframeSP firstAfterCycle = m_lastSourceKeyframe->channel()->nextKeyframe(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) { 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 KisKeyframeSP next = original->channel()->nextKeyframe(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; KisKeyframeSP terminatingKey = repeat->channel()->nextKeyframe(*repeat); if (range.isEmpty()) { if (terminatingKey) { range = KisTimeSpan(0, terminatingKey->time() -1); } else { infiniteFrom = repeat->firstInstanceOf(originalTime); continue; } } KisTimeSpan repeatRange = terminatingKey ? range.truncateLeft(terminatingKey->time() - 1) : range; int firstInstance = repeat->firstInstanceOf(originalTime); if (firstInstance < repeatRange.start()) firstInstance += (range.start() - firstInstance) / interval * interval; for (int repeatTime = firstInstance; repeatTime <= repeatRange.end(); repeatTime += interval) { const int repeatEndTime = (frameDuration != -1 && repeatTime + frameDuration - 1 <= repeatRange.end()) ? 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; } KisKeyframeSP KisRepeatFrame::cloneFor(KisKeyframeChannel *channel) const { const int cycleBeginTime = m_cycle->firstSourceKeyframe()->time(); QSharedPointer targetCycle = channel->cycleAt(cycleBeginTime); return toQShared(new KisRepeatFrame(channel, time(), targetCycle)); } bool KisRepeatFrame::isRepeat(KisKeyframeSP keyframe) { return dynamic_cast(keyframe.data()) != nullptr; } KisRepeatFrame::KisRepeatFrame(KisKeyframeChannel *channel, int time, QSharedPointer cycle) : KisKeyframe(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(*this); 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 KisKeyframeSP next = channel()->nextKeyframe(*this); + if (next && next->time() <= nextFrameTime) return -1; + + return nextFrameTime; +} diff --git a/libs/image/kis_animation_cycle.h b/libs/image/kis_animation_cycle.h index 17496130bc..77191af60f 100644 --- a/libs/image/kis_animation_cycle.h +++ b/libs/image/kis_animation_cycle.h @@ -1,73 +1,85 @@ /* * 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: KisAnimationCycle(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; KisFrameSet instancesWithin(KisKeyframeSP original, KisTimeSpan range) const; private: friend class KisKeyframeChannel; void setFirstKeyframe(KisKeyframeSP keyframe); void setLastKeyframe(KisKeyframeSP keyframe); KisKeyframeSP m_firstSourceKeyframe, m_lastSourceKeyframe; QVector> m_repeats; }; class KRITAIMAGE_EXPORT KisRepeatFrame : public KisKeyframe { public: KisRepeatFrame(KisKeyframeChannel *channel, int time, QSharedPointer cycle); QSharedPointer cycle() const; QRect affectedRect() const override; KisKeyframeSP cloneFor(KisKeyframeChannel *channel) const override; int getOriginalTimeFor(int time) const; KisKeyframeSP getOriginalKeyframeFor(int time) const; /// 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; + static bool isRepeat(KisKeyframeSP keyframe); private: QSharedPointer m_cycle; }; #endif diff --git a/libs/image/kis_keyframe_channel.cpp b/libs/image/kis_keyframe_channel.cpp index 13aac9e937..516f1791e0 100644 --- a/libs/image/kis_keyframe_channel.cpp +++ b/libs/image/kis_keyframe_channel.cpp @@ -1,874 +1,956 @@ /* * 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 "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)")); 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; KisNodeWSP node; KoID id; KisDefaultBoundsBaseSP defaultBounds; bool haveBrokenFrameTimeBug = false; void addKeyframe(KisKeyframeSP keyframe) { keys.insert(keyframe->time(), keyframe); auto repeat = keyframe.dynamicCast(); if (repeat) { QSharedPointer cycle = repeat->cycle(); cycles.insert(keyframe->time(), cycle); cycle->addRepeat(repeat); } } void removeKeyframe(KisKeyframeSP keyframe) { keys.remove(keyframe->time()); auto repeat = keyframe.dynamicCast(); if (repeat) { cycles.remove(keyframe->time()); repeat->cycle()->removeRepeat(repeat); } } void moveKeyframe(KisKeyframeSP keyframe, int newTime) { removeKeyframe(keyframe); keyframe->setTime(newTime); addKeyframe(keyframe); } }; 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(const QSharedPointer rhsCycle, rhs.m_d->cycles) { const int time = rhsCycle->firstSourceKeyframe()->time(); QSharedPointer cycle = toQShared(new KisAnimationCycle(KisKeyframeSP(), KisKeyframeSP())); m_d->cycles.insert(time, cycle); } Q_FOREACH(KisKeyframeSP keyframe, rhs.m_d->keys) { const KisKeyframeSP clone = keyframe->cloneFor(this); m_d->addKeyframe(clone); } Q_FOREACH(const QSharedPointer rhsCycle, rhs.m_d->cycles) { QSharedPointer cycle = cycleAt(rhsCycle->firstSourceKeyframe()->time()); cycle->setFirstKeyframe(keyframeAt(rhsCycle->firstSourceKeyframe()->time())); cycle->setLastKeyframe(keyframeAt(rhsCycle->lastSourceKeyframe()->time())); } } 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); } KisKeyframeSP KisKeyframeChannel::copyKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); return insertKeyframe(newTime, keyframe, parentCommand); } KisKeyframeSP KisKeyframeChannel::linkKeyframe(const KisKeyframeSP, int, KUndo2Command*) { return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::insertKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) { KisKeyframeSP keyframe = keyframeAt(time); if (keyframe) { deleteKeyframeImpl(keyframe, parentCommand, false); } Q_ASSERT(parentCommand); keyframe = createKeyframe(time, copySrc, parentCommand); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, keyframe->time(), keyframe, parentCommand); cmd->redo(); return keyframe; } bool KisKeyframeChannel::deleteKeyframe(KisKeyframeSP keyframe, KUndo2Command *parentCommand) { return deleteKeyframeImpl(keyframe, parentCommand, true); } bool KisKeyframeChannel::moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); if (newTime == keyframe->time()) return false; KisKeyframeSP other = keyframeAt(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; KisKeyframeSP lhsFrame = keyframeAt(lhsTime); KisKeyframeSP rhsFrame = keyframeAt(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(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate) { LAZY_INITIALIZE_PARENT_COMMAND(parentCommand); Q_ASSERT(parentCommand); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, keyframe->time(), KisKeyframeSP(), parentCommand); cmd->redo(); destroyKeyframe(keyframe, parentCommand); if (recreate && keyframe->time() == 0) { addKeyframe(0, parentCommand); } return true; } void KisKeyframeChannel::moveKeyframeImpl(KisKeyframeSP keyframe, int newTime) { KIS_ASSERT_RECOVER_RETURN(keyframe); KIS_ASSERT_RECOVER_RETURN(!keyframeAt(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(KisKeyframeSP lhsKeyframe, KisKeyframeSP 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->removeKeyframe(lhsKeyframe); m_d->removeKeyframe(rhsKeyframe); rhsKeyframe->setTime(lhsTime); lhsKeyframe->setTime(rhsTime); m_d->addKeyframe(lhsKeyframe); m_d->addKeyframe(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); } KisKeyframeSP KisKeyframeChannel::replaceKeyframeAt(int time, KisKeyframeSP newKeyframe) { Q_ASSERT(newKeyframe.isNull() || time == newKeyframe->time()); KisKeyframeSP existingKeyframe = keyframeAt(time); if (!existingKeyframe.isNull()) { removeKeyframeLogical(existingKeyframe); } if (!newKeyframe.isNull()) { insertKeyframeLogical(newKeyframe); } return existingKeyframe; } void KisKeyframeChannel::insertKeyframeLogical(KisKeyframeSP keyframe) { const int time = keyframe->time(); emit sigKeyframeAboutToBeAdded(keyframe); m_d->addKeyframe(keyframe); emit sigKeyframeAdded(keyframe); QRect rect = keyframe->affectedRect(); KisFrameSet range = affectedFrames(time); requestUpdate(range, rect); } void KisKeyframeChannel::removeKeyframeLogical(KisKeyframeSP keyframe) { QRect rect = keyframe->affectedRect(); KisFrameSet range = affectedFrames(keyframe->time()); emit sigKeyframeAboutToBeRemoved(keyframe); m_d->removeKeyframe(keyframe); emit sigKeyframeRemoved(keyframe); requestUpdate(range, rect); } KisKeyframeSP KisKeyframeChannel::keyframeAt(int time) const { KeyframesMap::const_iterator i = m_d->keys.constFind(time); if (i != m_d->keys.constEnd()) { return i.value(); } return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::activeKeyframeAt(int time) const { KeyframesMap::const_iterator i = activeKeyIterator(time); if (i != m_d->keys.constEnd()) { return i.value(); } return KisKeyframeSP(); } KisKeyframeSP KisKeyframeChannel::visibleKeyframeAt(int time) const { KisKeyframeSP keyframe = activeKeyframeAt(time); const KisRepeatFrame *repeat = dynamic_cast(keyframe.data()); if (repeat) keyframe = repeat->getOriginalKeyframeFor(time); return keyframe; } 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); } KisKeyframeSP KisKeyframeChannel::nextKeyframe(const KisKeyframe &keyframe) const { KeyframesMap::const_iterator i = m_d->keys.constFind(keyframe.time()); if (i == m_d->keys.constEnd()) return KisKeyframeSP(0); i++; if (i == m_d->keys.constEnd()) return KisKeyframeSP(0); return i.value(); } KisKeyframeSP KisKeyframeChannel::previousKeyframe(KisKeyframeSP keyframe) const { return previousKeyframe(*keyframe); } KisKeyframeSP KisKeyframeChannel::previousKeyframe(const KisKeyframe &keyframe) const { KeyframesMap::const_iterator i = m_d->keys.constFind(keyframe.time()); if (i == m_d->keys.constBegin() || i == m_d->keys.constEnd()) return KisKeyframeSP(0); i--; return i.value(); } KisKeyframeSP KisKeyframeChannel::lastKeyframe() const { if (m_d->keys.isEmpty()) return KisKeyframeSP(0); return (m_d->keys.end()-1).value(); } +KisVisibleKeyframeIterator KisKeyframeChannel::visibleKeyframesFrom(int time) const +{ + return KisVisibleKeyframeIterator(visibleKeyframeAt(time)); +} + KisTimeSpan KisKeyframeChannel::cycledRangeAt(int time) const { QSharedPointer cycle = cycleAt(time); if (cycle.isNull()) return KisTimeSpan(); const KisKeyframeSP firstAfterCycle = nextKeyframe(cycle->lastSourceKeyframe()); int start = cycle->firstSourceKeyframe()->time(); KisTimeSpan originalRange; if (firstAfterCycle.isNull()) { originalRange = KisTimeSpan(start, lastKeyframe()->time()); } else { originalRange = KisTimeSpan(start, firstAfterCycle->time() - 1); } if (originalRange.contains(time) || KisRepeatFrame::isRepeat(activeKeyframeAt(time))) { return originalRange; } return KisTimeSpan(); } QSharedPointer KisKeyframeChannel::cycleAt(int time) const { if (!m_d->cycles.isEmpty()) { auto it = m_d->cycles.upperBound(time); if (it != m_d->cycles.begin()) it--; return it.value(); } return QSharedPointer(); }; 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 = activeKeyIterator(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 cycle = cycleAt(time); if (!cycle.isNull()) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(active != m_d->keys.constEnd() && active.value(), KisFrameSet()); if (cycle->originalRange().contains(time)) { frames = cycle->instancesWithin(active.value(), KisTimeSpan()); } else { const KisRepeatFrame *repeat = dynamic_cast(active.value().data()); if (repeat) { const KisKeyframeSP original = repeat->getOriginalKeyframeFor(time); return affectedFrames(original->time()); } } } 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 { KeyframesMap::const_iterator active = activeKeyIterator(time); QSharedPointer cycle = cycleAt(time); if (!cycle.isNull()) { if (cycle->originalRange().contains(time)) { return cycle->instancesWithin(active.value(), range); } else { const KisRepeatFrame *repeat = dynamic_cast(active.value().data()); if (repeat) { const KisKeyframeSP original = repeat->getOriginalKeyframeFor(time); return identicalFrames(original->time(), 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 (const QSharedPointer cycle, m_d->cycles.values()) { QDomElement cycleElement = doc.createElement("cycle"); cycleElement.setAttribute("firstKeyframe", cycle->firstSourceKeyframe()->time()); cycleElement.setAttribute("lastKeyframe", cycle->lastSourceKeyframe()->time()); channelElement.appendChild(cycleElement); } Q_FOREACH (KisKeyframeSP keyframe, m_d->keys.values()) { QDomElement keyframeElement = doc.createElement("keyframe"); keyframeElement.setAttribute("time", keyframe->time()); keyframeElement.setAttribute("color-label", keyframe->colorLabel()); KisRepeatFrame *repeat = dynamic_cast(keyframe.data()); if (repeat) { keyframeElement.setAttribute("type", "repeat"); const int sourceTime = repeat->cycle()->firstSourceKeyframe()->time(); keyframeElement.setAttribute("cycle", sourceTime); } else { saveKeyframe(keyframe, keyframeElement, layerFilename); } channelElement.appendChild(keyframeElement); } return channelElement; } void KisKeyframeChannel::loadXML(const QDomElement &channelNode) { QMap> cyclesByFirstKeyframe; QMap> cyclesByLastKeyframe; for (QDomElement childNode = channelNode.firstChildElement(); !childNode.isNull(); childNode = childNode.nextSiblingElement()) { const QString nodeName = childNode.nodeName().toUpper(); if (nodeName == "CYCLE") { const int firstKeyframeTime = childNode.attribute("firstKeyframe", "-1").toInt(); const int lastKeyframeTime = childNode.attribute("lastKeyframe", "-1").toInt(); if (firstKeyframeTime >= 0 && lastKeyframeTime > firstKeyframeTime) { QSharedPointer cycle = toQShared(new KisAnimationCycle(KisKeyframeSP(), KisKeyframeSP())); cyclesByFirstKeyframe.insert(firstKeyframeTime, cycle); cyclesByLastKeyframe.insert(lastKeyframeTime, cycle); } } } 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; if (keyframeType == "REPEAT") { keyframe = loadRepeatFrame(childNode, cyclesByFirstKeyframe); } else { keyframe = loadKeyframe(childNode); } KIS_SAFE_ASSERT_RECOVER(keyframe) { continue; } if (childNode.hasAttribute("color-label")) { keyframe->setColorLabel(childNode.attribute("color-label").toUInt()); } m_d->addKeyframe(keyframe); const int time = keyframe->time(); if (cyclesByFirstKeyframe.contains(time)) { cyclesByFirstKeyframe[time]->setFirstKeyframe(keyframe); addCycle(cyclesByFirstKeyframe[time]); } if (cyclesByLastKeyframe.contains(time)) { cyclesByLastKeyframe[time]->setLastKeyframe(keyframe); } } } } KisKeyframeSP KisKeyframeChannel::loadRepeatFrame(const QDomElement &keyframeNode, const QMap> &cyclesByFirstKeyframe) { const int original = keyframeNode.attribute("cycle", "-1").toInt(); const QSharedPointer cycle = cyclesByFirstKeyframe.value(original); const int time = keyframeNode.attribute("time").toInt(); if (cycle) { return toQShared(new KisRepeatFrame(this, time, cycle)); } return KisKeyframeSP(); } 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; } KUndo2Command * KisKeyframeChannel::createCycle(KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe, KUndo2Command *parentCommand) { const QSharedPointer cycle = toQShared(new KisAnimationCycle(firstKeyframe, lastKeyframe)); return new KisDefineCycleCommand(this, cycle, false, parentCommand); } void KisKeyframeChannel::addCycle(QSharedPointer cycle) { m_d->cycles.insert(cycle->firstSourceKeyframe()->time(), cycle); } KUndo2Command* KisKeyframeChannel::deleteCycle(QSharedPointer cycle, KUndo2Command *parentCommand) { Q_FOREACH(QWeakPointer repeat, cycle->repeats()) { auto repeatSP = repeat.toStrongRef(); deleteKeyframe(repeatSP, parentCommand); } return new KisDefineCycleCommand(this, cycle, true, parentCommand); } void KisKeyframeChannel::removeCycle(QSharedPointer cycle) { m_d->cycles.remove(cycle->firstSourceKeyframe()->time()); } KisKeyframeSP KisKeyframeChannel::addRepeat(int time, KisKeyframeSP source, KUndo2Command *parentCommand) { const QSharedPointer cycle = m_d->cycles.value(source->time()); if (cycle.isNull()) return QSharedPointer(); const QSharedPointer repeatFrame = toQShared(new KisRepeatFrame(this, time, cycle)); KUndo2Command *cmd = new KisReplaceKeyframeCommand(this, time, repeatFrame, parentCommand); cmd->redo(); return repeatFrame; } KisKeyframeChannel::KeyframesMap::const_iterator KisKeyframeChannel::activeKeyIterator(int time) const { KeyframesMap::const_iterator i = const_cast(&m_d->keys)->upperBound(time); if (i == m_d->keys.constBegin()) return m_d->keys.constEnd(); return --i; } 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 KisRepeatFrame *repeat = dynamic_cast(m_keyframe.data()); + + if (repeat) { + const int time = repeat->previousVisibleFrame(m_time); + if (time >= 0) { + m_time = time; + return *this; + } + } + + m_keyframe = m_channel->previousKeyframe(*m_keyframe); + if (!m_keyframe) return invalidate(); + + m_time = m_keyframe->time(); + return *this; +} + +KisVisibleKeyframeIterator& KisVisibleKeyframeIterator::operator++() +{ + const KisRepeatFrame *repeat = dynamic_cast(m_keyframe.data()); + + if (repeat) { + const int time = repeat->nextVisibleFrame(m_time); + if (time >= 0) { + m_time = time; + return *this; + } + } + + m_keyframe = m_channel->nextKeyframe(*m_keyframe); + 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 b610bca57d..a143e93d1e 100644 --- a/libs/image/kis_keyframe_channel.h +++ b/libs/image/kis_keyframe_channel.h @@ -1,196 +1,220 @@ /* * 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 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(KisKeyframeSP keyframe, KUndo2Command *parentCommand = 0); bool moveKeyframe(KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0); bool swapFrames(int lhsTime, int rhsTime, KUndo2Command *parentCommand = 0); KisKeyframeSP copyKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0); virtual KisKeyframeSP linkKeyframe(const KisKeyframeSP keyframe, int newTime, KUndo2Command *parentCommand = 0); KisKeyframeSP copyExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, int dstTime, KUndo2Command *parentCommand = 0); KUndo2Command * createCycle(KisKeyframeSP firstKeyframe, KisKeyframeSP lastKeyframe, KUndo2Command *parentCommand = 0); KUndo2Command * deleteCycle(QSharedPointer cycle, KUndo2Command *parentCommand = 0); KisKeyframeSP addRepeat(int time, KisKeyframeSP source, KUndo2Command *parentCommand = 0); 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(const KisKeyframe &keyframe) const; KisKeyframeSP previousKeyframe(const KisKeyframe &keyframe) const; KisKeyframeSP lastKeyframe() const; + KisVisibleKeyframeIterator visibleKeyframesFrom(int time) 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; QSharedPointer cycleAt(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(KisKeyframeSP keyframe); void sigKeyframeAdded(KisKeyframeSP keyframe); void sigKeyframeAboutToBeRemoved(KisKeyframeSP keyframe); void sigKeyframeRemoved(KisKeyframeSP keyframe); void sigKeyframeAboutToBeMoved(KisKeyframeSP keyframe, int toTime); void sigKeyframeMoved(KisKeyframeSP keyframe, int fromTime); void sigKeyframeChanged(KisKeyframeSP keyframe); protected: typedef QMap KeyframesMap; KeyframesMap &keys(); const KeyframesMap &constKeys() const; KeyframesMap::const_iterator activeKeyIterator(int time) 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: KisKeyframeSP replaceKeyframeAt(int time, KisKeyframeSP newKeyframe); void insertKeyframeLogical(KisKeyframeSP keyframe); void removeKeyframeLogical(KisKeyframeSP keyframe); bool deleteKeyframeImpl(KisKeyframeSP keyframe, KUndo2Command *parentCommand, bool recreate); void moveKeyframeImpl(KisKeyframeSP keyframe, int newTime); void swapKeyframesImpl(KisKeyframeSP lhsKeyframe, KisKeyframeSP rhsKeyframe); void addCycle(QSharedPointer cycle); void removeCycle(QSharedPointer cycle); KisKeyframeSP loadRepeatFrame(const QDomElement &keyframeNode, const QMap> &cyclesByFirstKeyframe); friend class KisMoveFrameCommand; friend class KisReplaceKeyframeCommand; friend class KisSwapFramesCommand; friend class KisDefineCycleCommand; private: KisKeyframeSP insertKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand); 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_onion_skin_compositor.cpp b/libs/image/kis_onion_skin_compositor.cpp index ed417576cc..fe05baab2c 100644 --- a/libs/image/kis_onion_skin_compositor.cpp +++ b/libs/image/kis_onion_skin_compositor.cpp @@ -1,227 +1,226 @@ /* * 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_onion_skin_compositor.h" #include "kis_paint_device.h" #include "kis_painter.h" #include "KoColor.h" #include "KoColorSpace.h" #include "KoCompositeOpRegistry.h" #include "KoColorSpaceConstants.h" #include "kis_image_config.h" #include "kis_raster_keyframe_channel.h" Q_GLOBAL_STATIC(KisOnionSkinCompositor, s_instance); struct KisOnionSkinCompositor::Private { int numberOfSkins = 0; int tintFactor = 0; QColor backwardTintColor; QColor forwardTintColor; QVector backwardOpacities; QVector forwardOpacities; int configSeqNo = 0; QList colorLabelFilter; int skinOpacity(int offset) { const QVector &bo = backwardOpacities; const QVector &fo = forwardOpacities; return offset > 0 ? fo[qAbs(offset) - 1] : bo[qAbs(offset) - 1]; } KisPaintDeviceSP setUpTintDevice(const QColor &tintColor, const KoColorSpace *colorSpace) { KisPaintDeviceSP tintDevice = new KisPaintDevice(colorSpace); KoColor color = KoColor(tintColor, colorSpace); tintDevice->setDefaultPixel(color); return tintDevice; } - KisKeyframeSP getNextFrameToComposite(KisKeyframeChannel *channel, KisKeyframeSP keyframe, bool backwards) + KisVisibleKeyframeIterator getNextFrameToComposite(KisVisibleKeyframeIterator keyframe, bool backwards) { - while (!keyframe.isNull()) { - keyframe = backwards ? channel->previousKeyframe(keyframe) : channel->nextKeyframe(keyframe); + while (keyframe.isValid()) { + keyframe = backwards ? --keyframe : ++keyframe; if (colorLabelFilter.isEmpty()) { return keyframe; - } else if (!keyframe.isNull()) { + } else if (keyframe.isValid()) { if (colorLabelFilter.contains(keyframe->colorLabel())) { return keyframe; } } } return keyframe; } void tryCompositeFrame(KisRasterKeyframeChannel *keyframes, KisKeyframeSP keyframe, KisPainter &gcFrame, KisPainter &gcDest, KisPaintDeviceSP tintSource, int opacity, const QRect &rect) { if (keyframe.isNull() || opacity == OPACITY_TRANSPARENT_U8) return; keyframes->fetchFrame(keyframe, gcFrame.device()); gcFrame.bitBlt(rect.topLeft(), tintSource, rect); gcDest.setOpacity(opacity); gcDest.bitBlt(rect.topLeft(), gcFrame.device(), rect); } void refreshConfig() { KisImageConfig config(true); numberOfSkins = config.numberOfOnionSkins(); tintFactor = config.onionSkinTintFactor(); backwardTintColor = config.onionSkinTintColorBackward(); forwardTintColor = config.onionSkinTintColorForward(); backwardOpacities.resize(numberOfSkins); forwardOpacities.resize(numberOfSkins); const int mainState = (int) config.onionSkinState(0); const qreal scaleFactor = mainState * config.onionSkinOpacity(0) / 255.0; for (int i = 0; i < numberOfSkins; i++) { int backwardState = (int) config.onionSkinState(-(i + 1)); int forwardState = (int) config.onionSkinState(i + 1); backwardOpacities[i] = scaleFactor * backwardState * config.onionSkinOpacity(-(i + 1)); forwardOpacities[i] = scaleFactor * forwardState * config.onionSkinOpacity(i + 1); } configSeqNo++; } }; KisOnionSkinCompositor *KisOnionSkinCompositor::instance() { return s_instance; } KisOnionSkinCompositor::KisOnionSkinCompositor() : m_d(new Private) { m_d->refreshConfig(); } KisOnionSkinCompositor::~KisOnionSkinCompositor() {} int KisOnionSkinCompositor::configSeqNo() const { return m_d->configSeqNo; } void KisOnionSkinCompositor::setColorLabelFilter(QList colors) { m_d->colorLabelFilter = colors; } void KisOnionSkinCompositor::composite(const KisPaintDeviceSP sourceDevice, KisPaintDeviceSP targetDevice, const QRect& rect) { KisRasterKeyframeChannel *keyframes = sourceDevice->keyframeChannel(); KisPaintDeviceSP frameDevice = new KisPaintDevice(sourceDevice->colorSpace()); KisPainter gcFrame(frameDevice); QBitArray channelFlags = targetDevice->colorSpace()->channelFlags(true, false); gcFrame.setChannelFlags(channelFlags); gcFrame.setOpacity(m_d->tintFactor); KisPaintDeviceSP backwardTintDevice = m_d->setUpTintDevice(m_d->backwardTintColor, sourceDevice->colorSpace()); KisPaintDeviceSP forwardTintDevice = m_d->setUpTintDevice(m_d->forwardTintColor, sourceDevice->colorSpace()); KisPainter gcDest(targetDevice); gcDest.setCompositeOp(sourceDevice->colorSpace()->compositeOp(COMPOSITE_BEHIND)); - KisKeyframeSP keyframeBck; - KisKeyframeSP keyframeFwd; - - int time = sourceDevice->defaultBounds()->currentTime(); - keyframeBck = keyframeFwd = keyframes->visibleKeyframeAt(time); + const int time = sourceDevice->defaultBounds()->currentTime(); + KisVisibleKeyframeIterator backward = keyframes->visibleKeyframesFrom(time); + KisVisibleKeyframeIterator forward = backward; for (int offset = 1; offset <= m_d->numberOfSkins; offset++) { - keyframeBck = m_d->getNextFrameToComposite(keyframes, keyframeBck, true); - keyframeFwd = m_d->getNextFrameToComposite(keyframes, keyframeFwd, false); + backward = m_d->getNextFrameToComposite(backward, true); + forward = m_d->getNextFrameToComposite(forward, false); - if (!keyframeBck.isNull()) { - m_d->tryCompositeFrame(keyframes, keyframeBck, gcFrame, gcDest, backwardTintDevice, m_d->skinOpacity(-offset), rect); + if (backward.isValid()) { + m_d->tryCompositeFrame(keyframes, *backward, gcFrame, gcDest, backwardTintDevice, m_d->skinOpacity(-offset), rect); } - if (!keyframeFwd.isNull()) { - m_d->tryCompositeFrame(keyframes, keyframeFwd, gcFrame, gcDest, forwardTintDevice, m_d->skinOpacity(offset), rect); + if (forward.isValid()) { + m_d->tryCompositeFrame(keyframes, *forward, gcFrame, gcDest, forwardTintDevice, m_d->skinOpacity(offset), rect); } } } QRect KisOnionSkinCompositor::calculateFullExtent(const KisPaintDeviceSP device) { QRect rect; KisRasterKeyframeChannel *channel = device->keyframeChannel(); if (!channel) return rect; KisKeyframeSP keyframe = channel->firstKeyframe(); while (keyframe) { rect |= channel->frameExtents(keyframe); keyframe = channel->nextKeyframe(keyframe); } return rect; } QRect KisOnionSkinCompositor::calculateExtent(const KisPaintDeviceSP device) { QRect rect; KisKeyframeSP keyframeBck; KisKeyframeSP keyframeFwd; KisRasterKeyframeChannel *channel = device->keyframeChannel(); - keyframeBck = keyframeFwd = channel->activeKeyframeAt(device->defaultBounds()->currentTime()); + KisVisibleKeyframeIterator forward = channel->visibleKeyframesFrom(device->defaultBounds()->currentTime()); + KisVisibleKeyframeIterator backward = forward; for (int offset = 1; offset <= m_d->numberOfSkins; offset++) { - if (!keyframeBck.isNull()) { - keyframeBck = channel->previousKeyframe(keyframeBck); + if (backward.isValid()) { + --backward; if (!keyframeBck.isNull()) { - rect |= channel->frameExtents(keyframeBck); + rect |= channel->frameExtents(*backward); } } - if (!keyframeFwd.isNull()) { - keyframeFwd = channel->nextKeyframe(keyframeFwd); + if (forward.isValid()) { + ++forward; - if (!keyframeFwd.isNull()) { - rect |= channel->frameExtents(keyframeFwd); + if (forward.isValid()) { + rect |= channel->frameExtents(*forward); } } } return rect; } void KisOnionSkinCompositor::configChanged() { m_d->refreshConfig(); emit sigOnionSkinChanged(); }