1 /* 2 * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 */ 18 19 #include "kis_animation_utils.h" 20 21 #include "kundo2command.h" 22 #include "kis_algebra_2d.h" 23 #include "kis_image.h" 24 #include "kis_node.h" 25 #include "kis_keyframe_channel.h" 26 #include "kis_post_execution_undo_adapter.h" 27 #include "kis_global.h" 28 #include "kis_tool_utils.h" 29 #include "kis_image_animation_interface.h" 30 #include "kis_command_utils.h" 31 #include "kis_processing_applicator.h" 32 #include "kis_transaction.h" 33 34 35 namespace KisAnimationUtils { 36 const QString lazyFrameCreationActionName = i18n("Auto Frame Mode"); 37 const QString dropFramesActionName = i18n("Drop Frames"); 38 39 const QString newLayerActionName = i18n("New Layer"); 40 const QString addExistingLayerActionName = i18n("Add Existing Layer"); 41 const QString removeLayerActionName = i18n("Remove Layer"); 42 43 const QString addTransformKeyframeActionName = i18n("Add transform keyframe"); 44 const QString removeTransformKeyframeActionName = i18n("Remove transform keyframe"); 45 createKeyframeCommand(KisImageSP image,KisNodeSP node,const QString & channelId,int time,bool copy,KUndo2Command * parentCommand)46 KUndo2Command* createKeyframeCommand(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy, KUndo2Command *parentCommand) { 47 KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( 48 copy ? kundo2_i18n("Copy Keyframe") : 49 kundo2_i18n("Add Keyframe"), 50 parentCommand, 51 52 [image, node, channelId, time, copy] () mutable -> KUndo2Command* { 53 bool result = false; 54 55 QScopedPointer<KUndo2Command> cmd(new KUndo2Command()); 56 57 KisKeyframeChannel *channel = node->getKeyframeChannel(channelId); 58 quint8 originalOpacity = node->opacity(); 59 bool createdChannel = false; 60 61 if (!channel) { 62 node->enableAnimation(); 63 channel = node->getKeyframeChannel(channelId, true); 64 if (!channel) return nullptr; 65 66 createdChannel = true; 67 } 68 69 if (copy) { 70 if (!channel->keyframeAt(time)) { 71 KisKeyframeSP srcFrame = channel->activeKeyframeAt(time); 72 channel->copyKeyframe(srcFrame, time, cmd.data()); 73 result = true; 74 } 75 } else { 76 if (channel->keyframeAt(time) && !createdChannel) { 77 if (image->animationInterface()->currentTime() == time && channelId == KisKeyframeChannel::Content.id()) { 78 79 //shortcut: clearing the image instead 80 KisPaintDeviceSP device = node->paintDevice(); 81 if (device) { 82 const QRect dirtyRect = device->extent(); 83 84 KisTransaction transaction(kundo2_i18n("Clear"), device, cmd.data()); 85 device->clear(); 86 (void) transaction.endAndTake(); // saved as 'parent' 87 88 node->setDirty(dirtyRect); 89 90 result = true; 91 } 92 } 93 } else { 94 channel->addKeyframe(time, cmd.data()); 95 result = true; 96 } 97 } 98 99 // when a new opacity keyframe is created, the opacity is set to 0 100 // this makes sure to use the opacity that was previously used 101 // maybe there is a better way to do this 102 node->setOpacity(originalOpacity); 103 104 return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : nullptr; 105 }); 106 107 return cmd; 108 } 109 createKeyframeLazy(KisImageSP image,KisNodeSP node,const QString & channelId,int time,bool copy)110 void createKeyframeLazy(KisImageSP image, KisNodeSP node, const QString &channelId, int time, bool copy) 111 { 112 KUndo2Command *cmd = createKeyframeCommand(image, node, channelId, time, copy); 113 KisProcessingApplicator::runSingleCommandStroke(image, cmd, 114 KisStrokeJobData::BARRIER, 115 KisStrokeJobData::EXCLUSIVE); 116 } 117 removeKeyframes(KisImageSP image,const FrameItemList & frames)118 void removeKeyframes(KisImageSP image, const FrameItemList &frames) { 119 KIS_SAFE_ASSERT_RECOVER_RETURN(!image->locked()); 120 121 KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( 122 kundo2_i18np("Remove Keyframe", 123 "Remove Keyframes", 124 frames.size()), 125 126 [image, frames] () { 127 bool result = false; 128 129 QScopedPointer<KUndo2Command> cmd(new KUndo2Command()); 130 131 Q_FOREACH (const FrameItem &item, frames) { 132 const int time = item.time; 133 KisNodeSP node = item.node; 134 KisKeyframeChannel *channel = 0; 135 if (node) { 136 channel = node->getKeyframeChannel(item.channel); 137 } 138 if (!channel) continue; 139 140 KisKeyframeSP keyframe = channel->keyframeAt(time); 141 if (!keyframe) continue; 142 143 channel->deleteKeyframe(keyframe, cmd.data()); 144 145 result = true; 146 } 147 148 return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; 149 }); 150 151 KisProcessingApplicator::runSingleCommandStroke(image, cmd, 152 KisStrokeJobData::BARRIER, 153 KisStrokeJobData::EXCLUSIVE); 154 } 155 removeKeyframe(KisImageSP image,KisNodeSP node,const QString & channel,int time)156 void removeKeyframe(KisImageSP image, KisNodeSP node, const QString &channel, int time) { 157 QVector<FrameItem> frames; 158 frames << FrameItem(node, channel, time); 159 removeKeyframes(image, frames); 160 } 161 162 163 struct LessOperator { LessOperatorKisAnimationUtils::LessOperator164 LessOperator(const QPoint &offset) 165 : m_columnCoeff(-KisAlgebra2D::signPZ(offset.x())), 166 m_rowCoeff(-1000000 * KisAlgebra2D::signZZ(offset.y())) 167 { 168 } 169 operator ()KisAnimationUtils::LessOperator170 bool operator()(const QModelIndex &lhs, const QModelIndex &rhs) { 171 return 172 m_columnCoeff * lhs.column() + m_rowCoeff * lhs.row() < 173 m_columnCoeff * rhs.column() + m_rowCoeff * rhs.row(); 174 } 175 176 private: 177 int m_columnCoeff; 178 int m_rowCoeff; 179 }; 180 sortPointsForSafeMove(QModelIndexList * points,const QPoint & offset)181 void sortPointsForSafeMove(QModelIndexList *points, const QPoint &offset) 182 { 183 std::sort(points->begin(), points->end(), LessOperator(offset)); 184 } 185 supportsContentFrames(KisNodeSP node)186 bool supportsContentFrames(KisNodeSP node) 187 { 188 return node->inherits("KisPaintLayer") || node->inherits("KisFilterMask") || node->inherits("KisTransparencyMask") || node->inherits("KisSelectionBasedLayer"); 189 } 190 swapOneFrameItem(const FrameItem & src,const FrameItem & dst,KUndo2Command * parentCommand)191 void swapOneFrameItem(const FrameItem &src, const FrameItem &dst, KUndo2Command *parentCommand) 192 { 193 const int srcTime = src.time; 194 KisNodeSP srcNode = src.node; 195 KisKeyframeChannel *srcChannel = srcNode->getKeyframeChannel(src.channel); 196 197 const int dstTime = dst.time; 198 KisNodeSP dstNode = dst.node; 199 KisKeyframeChannel *dstChannel = dstNode->getKeyframeChannel(dst.channel, true); 200 201 if (srcNode == dstNode) { 202 if (!srcChannel) return; // TODO: add warning! 203 204 srcChannel->swapFrames(srcTime, dstTime, parentCommand); 205 } else { 206 if (!srcChannel || !dstChannel) return; // TODO: add warning! 207 208 dstChannel->swapExternalKeyframe(srcChannel, srcTime, dstTime, parentCommand); 209 } 210 } 211 moveOneFrameItem(const FrameItem & src,const FrameItem & dst,bool copy,bool moveEmptyFrames,KUndo2Command * parentCommand)212 void moveOneFrameItem(const FrameItem &src, const FrameItem &dst, bool copy, bool moveEmptyFrames, KUndo2Command *parentCommand) 213 { 214 const int srcTime = src.time; 215 KisNodeSP srcNode = src.node; 216 KisKeyframeChannel *srcChannel = srcNode->getKeyframeChannel(src.channel); 217 218 const int dstTime = dst.time; 219 KisNodeSP dstNode = dst.node; 220 KisKeyframeChannel *dstChannel = dstNode->getKeyframeChannel(dst.channel, true); 221 222 if (srcNode == dstNode) { 223 if (!srcChannel) return; // TODO: add warning! 224 225 KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); 226 KisKeyframeSP dstKeyFrame = srcChannel->keyframeAt(dstTime); 227 if (srcKeyframe) { 228 if (copy) { 229 srcChannel->copyKeyframe(srcKeyframe, dstTime, parentCommand); 230 } else { 231 srcChannel->moveKeyframe(srcKeyframe, dstTime, parentCommand); 232 } 233 } else { 234 if (dstKeyFrame && moveEmptyFrames && !copy) { 235 //Destination is effectively replaced by an empty frame. 236 dstChannel->deleteKeyframe(dstKeyFrame, parentCommand); 237 } 238 } 239 } else { 240 if (!srcChannel || !dstChannel) return; // TODO: add warning! 241 242 KisKeyframeSP srcKeyframe = srcChannel->keyframeAt(srcTime); 243 244 if (!srcKeyframe) return; // TODO: add warning! 245 246 dstChannel->copyExternalKeyframe(srcChannel, srcTime, dstTime, parentCommand); 247 248 if (!copy) { 249 srcChannel->deleteKeyframe(srcKeyframe, parentCommand); 250 } 251 } 252 } 253 createMoveKeyframesCommand(const FrameItemList & srcFrames,const FrameItemList & dstFrames,bool copy,bool moveEmpty,KUndo2Command * parentCommand)254 KUndo2Command* createMoveKeyframesCommand(const FrameItemList &srcFrames, 255 const FrameItemList &dstFrames, 256 bool copy, 257 bool moveEmpty, 258 KUndo2Command *parentCommand) 259 { 260 FrameMovePairList srcDstPairs; 261 for (int i = 0; i < srcFrames.size(); i++) { 262 srcDstPairs << std::make_pair(srcFrames[i], dstFrames[i]); 263 } 264 return createMoveKeyframesCommand(srcDstPairs, copy, moveEmpty, parentCommand); 265 } 266 createMoveKeyframesCommand(const FrameMovePairList & srcDstPairs,bool copy,bool moveEmptyFrames,KUndo2Command * parentCommand)267 KUndo2Command* createMoveKeyframesCommand(const FrameMovePairList &srcDstPairs, 268 bool copy, 269 bool moveEmptyFrames, 270 KUndo2Command *parentCommand) 271 { 272 KUndo2Command *cmd = new KisCommandUtils::LambdaCommand( 273 274 !copy ? 275 kundo2_i18np("Move Keyframe", 276 "Move %1 Keyframes", 277 srcDstPairs.size()) : 278 kundo2_i18np("Copy Keyframe", 279 "Copy %1 Keyframes", 280 srcDstPairs.size()), 281 282 parentCommand, 283 284 [srcDstPairs, copy, moveEmptyFrames] () -> KUndo2Command* { 285 bool result = false; 286 287 QScopedPointer<KUndo2Command> cmd(new KUndo2Command()); 288 289 using MoveChain = QList<FrameItem>; 290 QHash<FrameItem, MoveChain> moveMap; 291 Q_FOREACH (const FrameMovePair &pair, srcDstPairs) { 292 moveMap.insert(pair.first, {pair.second}); 293 } 294 295 auto it = moveMap.begin(); 296 while (it != moveMap.end()) { 297 MoveChain &chain = it.value(); 298 const FrameItem &previousFrame = chain.last(); 299 300 auto tailIt = moveMap.find(previousFrame); 301 302 if (tailIt == it || tailIt == moveMap.end()) { 303 ++it; 304 continue; 305 } 306 307 chain.append(tailIt.value()); 308 tailIt = moveMap.erase(tailIt); 309 // no incrementing! we are going to check the new tail now! 310 } 311 312 for (it = moveMap.begin(); it != moveMap.end(); ++it) { 313 MoveChain &chain = it.value(); 314 chain.prepend(it.key()); 315 KIS_SAFE_ASSERT_RECOVER(chain.size() > 1) { continue; } 316 317 bool isCycle = false; 318 if (chain.last() == chain.first()) { 319 isCycle = true; 320 chain.takeLast(); 321 } 322 323 auto frameIt = chain.rbegin(); 324 325 FrameItem dstItem = *frameIt++; 326 327 while (frameIt != chain.rend()) { 328 FrameItem srcItem = *frameIt++; 329 330 if (!isCycle) { 331 moveOneFrameItem(srcItem, dstItem, copy, moveEmptyFrames, cmd.data()); 332 } else { 333 swapOneFrameItem(srcItem, dstItem, cmd.data()); 334 } 335 336 dstItem = srcItem; 337 result = true; 338 } 339 } 340 341 return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0; 342 }); 343 344 return cmd; 345 } 346 operator <<(QDebug dbg,const FrameItem & item)347 QDebug operator<<(QDebug dbg, const FrameItem &item) 348 { 349 dbg.nospace() << "FrameItem(" << item.node->name() << ", " << item.channel << ", " << item.time << ")"; 350 return dbg.space(); 351 } 352 353 } 354 355