Pencil2D Animation
Download Community News Docs Contribute
  • Overview
  • Articles
  • Code
  •  
  • Class List
  • Class Index
  • Class Hierarchy
  • Class Members
  • File List
Loading...
Searching...
No Matches
  • core_lib
  • src
  • structure
layer.cpp
1/*
2
3Pencil2D - Traditional Animation Software
4Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5Copyright (C) 2012-2020 Matthew Chiawen Chang
6
7This program is free software; you can redistribute it and/or
8modify it under the terms of the GNU General Public License
9as published by the Free Software Foundation; version 2 of the License.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16*/
17#include "layer.h"
18
19#include <QApplication>
20#include <QDebug>
21#include <QSettings>
22#include <QPainter>
23#include <QDomElement>
24#include "keyframe.h"
25
26// Used to sort the selected frames list
27bool sortAsc(int left, int right)
28{
29 return left < right;
30}
31
32Layer::Layer(int id, LAYER_TYPE eType)
33{
34 Q_ASSERT(eType != UNDEFINED);
35
36 meType = eType;
37 mName = QString(tr("Undefined Layer"));
38
39 mId = id;
40}
41
42Layer::~Layer()
43{
44 for (auto it : mKeyFrames)
45 {
46 KeyFrame* pKeyFrame = it.second;
47 delete pKeyFrame;
48 }
49 mKeyFrames.clear();
50}
51
52void Layer::foreachKeyFrame(std::function<void(KeyFrame*)> action) const
53{
54 for (auto pair : mKeyFrames)
55 {
56 action(pair.second);
57 }
58}
59
60bool Layer::keyExists(int position) const
61{
62 return (mKeyFrames.find(position) != mKeyFrames.end());
63}
64
65KeyFrame* Layer::getKeyFrameAt(int position) const
66{
67 auto it = mKeyFrames.find(position);
68 if (it == mKeyFrames.end())
69 {
70 return nullptr;
71 }
72 return it->second;
73}
74
75KeyFrame* Layer::getLastKeyFrameAtPosition(int position) const
76{
77 if (position < 1)
78 {
79 position = 1;
80 }
81 auto it = mKeyFrames.lower_bound(position);
82 if (it == mKeyFrames.end())
83 {
84 return nullptr;
85 }
86 return it->second;
87}
88
89int Layer::getPreviousKeyFramePosition(int position) const
90{
91 auto it = mKeyFrames.upper_bound(position);
92 if (it == mKeyFrames.end())
93 {
94 return firstKeyFramePosition();
95 }
96 return it->first;
97}
98
99int Layer::getNextKeyFramePosition(int position) const
100{
101 // workaround: bug with lower_bound?
102 // when position is before the first frame it == mKeyFrames.end() for some reason
103 if (position < firstKeyFramePosition())
104 {
105 return firstKeyFramePosition();
106 }
107
108 auto it = mKeyFrames.lower_bound(position);
109 if (it == mKeyFrames.end())
110 {
111 return getMaxKeyFramePosition();
112 }
113
114 if (it != mKeyFrames.begin())
115 {
116 --it;
117 }
118 return it->first;
119}
120
121int Layer::getPreviousFrameNumber(int position, bool isAbsolute) const
122{
123 int prevNumber;
124
125 if (isAbsolute)
126 prevNumber = getPreviousKeyFramePosition(position);
127 else
128 prevNumber = position - 1;
129
130 if (prevNumber >= position)
131 {
132 return -1; // There is no previous keyframe
133 }
134 return prevNumber;
135}
136
137int Layer::getNextFrameNumber(int position, bool isAbsolute) const
138{
139 int nextNumber;
140
141 if (isAbsolute)
142 nextNumber = getNextKeyFramePosition(position);
143 else
144 nextNumber = position + 1;
145
146 if (nextNumber <= position)
147 return -1; // There is no next keyframe
148
149 return nextNumber;
150}
151
152int Layer::firstKeyFramePosition() const
153{
154 if (!mKeyFrames.empty())
155 {
156 return mKeyFrames.rbegin()->first; // rbegin is the lowest key frame position
157 }
158 return 0;
159}
160
161int Layer::getMaxKeyFramePosition() const
162{
163 if (!mKeyFrames.empty())
164 {
165 return mKeyFrames.begin()->first; // begin is the highest key frame position
166 }
167 return 0;
168}
169
170bool Layer::addNewKeyFrameAt(int position)
171{
172 if (position <= 0) return false;
173
174 KeyFrame* key = createKeyFrame(position);
175 if (!addKeyFrame(position, key))
176 {
177 delete key;
178 return false;
179 }
180 return true;
181}
182
183void Layer::addOrReplaceKeyFrame(int position, KeyFrame* pKeyFrame)
184{
185 Q_ASSERT(position > 0);
186 pKeyFrame->setPos(position);
187 loadKey(pKeyFrame);
188 markFrameAsDirty(position);
189}
190
191bool Layer::addKeyFrame(int position, KeyFrame* pKeyFrame)
192{
193 if (keyExists(position))
194 {
195 return false;
196 }
197
198 pKeyFrame->setPos(position);
199 mKeyFrames.emplace(position, pKeyFrame);
200 markFrameAsDirty(position);
201 return true;
202}
203
204bool Layer::insertExposureAt(int position)
205{
206 if(position < 1 || position > getMaxKeyFramePosition() || !getKeyFrameAt(position))
207 {
208 return false;
209 }
210
211 newSelectionOfConnectedFrames(position + 1);
212 moveSelectedFrames(1);
213 deselectAll();
214
215 return true;
216}
217
218bool Layer::removeKeyFrame(int position)
219{
220 if (keyFrameCount() == 1 && this->type() != SOUND) { return false; }
221 auto frame = getKeyFrameWhichCovers(position);
222
223 if (frame)
224 {
225 removeFromSelectionList(frame->pos());
226 mKeyFrames.erase(frame->pos());
227 markFrameAsDirty(frame->pos());
228 delete frame;
229 }
230 return true;
231}
232
233void Layer::removeFromSelectionList(int position)
234{
235 mSelectedFrames_byLast.removeAll(position);
236 mSelectedFrames_byPosition.removeAll(position);
237}
238
239bool Layer::moveKeyFrame(int position, int offset)
240{
241 int newPos = position + offset;
242 if (newPos < 1) { return false; }
243
244 auto listOfFramesLast = mSelectedFrames_byLast;
245 auto listOfFramesPos = mSelectedFrames_byPosition;
246 bool frameSelected = isFrameSelected(position);
247
248 if (swapKeyFrames(position, newPos)) {
249
250 auto oldPosIndex = mSelectedFrames_byPosition.indexOf(position);
251 auto newPosIndex = mSelectedFrames_byPosition.indexOf(newPos);
252
253 auto oldLastIndex = mSelectedFrames_byLast.indexOf(position);
254 auto newLastndex = mSelectedFrames_byLast.indexOf(newPos);
255
256
257 // Old position is selected
258 if (oldPosIndex != -1) {
259 mSelectedFrames_byPosition[oldPosIndex] = newPos;
260 mSelectedFrames_byLast[oldLastIndex] = newPos;
261 }
262
263 // Old position is selected
264 if (newPosIndex != -1) {
265 mSelectedFrames_byPosition[newPosIndex] = position;
266 mSelectedFrames_byLast[newLastndex] = position;
267 }
268 return true;
269 }
270
271 mSelectedFrames_byLast.clear();
272 mSelectedFrames_byPosition.clear();
273
274 setFrameSelected(position, true);
275
276 // If the move fails, assume we can't move at all and revert to old selection
277 if (!moveSelectedFrames(offset)) {
278 mSelectedFrames_byLast = listOfFramesLast;
279 mSelectedFrames_byPosition = listOfFramesPos;
280 return false;
281 }
282
283 // Remove old position from selection list
284 listOfFramesLast.removeOne(position);
285 listOfFramesPos.removeOne(position);
286
287 mSelectedFrames_byLast = listOfFramesLast;
288 mSelectedFrames_byPosition = listOfFramesPos;
289
290 // If the frame was selected prior to moving, make sure it's still selected.
291 setFrameSelected(newPos, frameSelected);
292
293 return true;
294}
295
296// Current behaviour, need to refresh the swapped cels
297bool Layer::swapKeyFrames(int position1, int position2)
298{
299 KeyFrame* pFirstFrame = nullptr;
300 KeyFrame* pSecondFrame = nullptr;
301
302 if (mKeyFrames.count(position1) != 1 || mKeyFrames.count(position2) != 1)
303 {
304 return false;
305 }
306
307 // Both keys exist
308 pFirstFrame = mKeyFrames[position1];
309 pSecondFrame = mKeyFrames[position2];
310
311 mKeyFrames[position1] = pSecondFrame;
312 mKeyFrames[position2] = pFirstFrame;
313
314 pFirstFrame->setPos(position2);
315 pSecondFrame->setPos(position1);
316
317 pFirstFrame->modification();
318 pSecondFrame->modification();
319
320 markFrameAsDirty(position1);
321 markFrameAsDirty(position2);
322
323 return true;
324}
325
326bool Layer::loadKey(KeyFrame* pKey)
327{
328 auto it = mKeyFrames.find(pKey->pos());
329 if (it != mKeyFrames.end())
330 {
331 delete it->second;
332 mKeyFrames.erase(it);
333 }
334 mKeyFrames.emplace(pKey->pos(), pKey);
335 return true;
336}
337
338Status Layer::save(const QString& sDataFolder, QStringList& attachedFiles, ProgressCallback progressStep)
339{
340 DebugDetails dd;
341 dd << "\n[Layer SAVE diagnostics]\n";
342
343 bool ok = true;
344
345 for (auto pair : mKeyFrames)
346 {
347 KeyFrame* keyFrame = pair.second;
348 Status st = saveKeyFrameFile(keyFrame, sDataFolder);
349 if (st.ok())
350 {
351 //qDebug() << "Layer [" << name() << "] FN=" << keyFrame->fileName();
352 if (!keyFrame->fileName().isEmpty())
353 attachedFiles.append(keyFrame->fileName());
354 }
355 else
356 {
357 ok = false;
358 dd.collect(st.details());
359 dd << QString("- Keyframe[%1] failed to save").arg(keyFrame->pos());
360 }
361 progressStep();
362 }
363 if (!ok)
364 {
365 dd << "\nError: Failed to save one or more files";
366 return Status(Status::FAIL, dd);
367 }
368 return Status::OK;
369}
370
371void Layer::setModified(int position, bool modified) const
372{
373 KeyFrame* key = getKeyFrameAt(position);
374 if (key)
375 {
376 key->setModified(modified);
377 }
378}
379
380bool Layer::isFrameSelected(int position) const
381{
382 KeyFrame* keyFrame = getKeyFrameWhichCovers(position);
383 if (keyFrame == nullptr) { return false; }
384
385 return mSelectedFrames_byLast.contains(keyFrame->pos());
386}
387
388void Layer::setFrameSelected(int position, bool isSelected)
389{
390 KeyFrame* keyFrame = getKeyFrameWhichCovers(position);
391 if (keyFrame != nullptr)
392 {
393 int startPosition = keyFrame->pos();
394
395 if (isSelected && !mSelectedFrames_byLast.contains(startPosition))
396 {
397 // Add the selected frame to the lists
398 mSelectedFrames_byLast.insert(0, startPosition);
399 mSelectedFrames_byPosition.append(startPosition);
400
401 // We need to keep the list of selected frames sorted
402 // in order to easily handle their movement
403 std::sort(mSelectedFrames_byPosition.begin(), mSelectedFrames_byPosition.end(), sortAsc);
404 }
405 else if (!isSelected)
406 {
407 mSelectedFrames_byLast.removeOne(startPosition);
408 mSelectedFrames_byPosition.removeOne(startPosition);
409 }
410 }
411}
412
413void Layer::toggleFrameSelected(int position, bool allowMultiple)
414{
415 bool wasSelected = isFrameSelected(position);
416
417 if (!allowMultiple)
418 {
419 deselectAll();
420 }
421
422 setFrameSelected(position, !wasSelected);
423}
424
425void Layer::extendSelectionTo(int position)
426{
427 if (mSelectedFrames_byLast.count() > 0)
428 {
429 int lastSelected = mSelectedFrames_byLast[0];
430 int startPos;
431 int endPos;
432
433 if (lastSelected < position)
434 {
435 startPos = lastSelected;
436 endPos = position;
437 }
438 else
439 {
440 startPos = position;
441 endPos = lastSelected;
442 }
443
444 int i = startPos;
445 while (i <= endPos)
446 {
447 setFrameSelected(i, true);
448 i++;
449 }
450 }
451}
452
453bool Layer::newSelectionOfConnectedFrames(int position)
454{
455 // Deselect all before extending to make sure we don't get already
456 // selected frames
457 deselectAll();
458
459 if (!keyExists(position)) { return false; }
460
461 setFrameSelected(position, true);
462
463 // Find keyframes that are connected and make sure we're below max.
464 while (position < getMaxKeyFramePosition() && getKeyFrameWhichCovers(position) != nullptr) {
465 position++;
466 }
467
468 extendSelectionTo(position);
469
470 return true;
471}
472
473void Layer::selectAllFramesAfter(int position)
474{
475 int startPosition = position;
476 int endPosition = getMaxKeyFramePosition();
477
478 if (!keyExists(startPosition))
479 {
480 startPosition = getNextKeyFramePosition(startPosition);
481 }
482
483 if (startPosition > 0 && startPosition <= endPosition)
484 {
485 deselectAll();
486 setFrameSelected(startPosition, true);
487 extendSelectionTo(endPosition);
488 }
489}
490
491void Layer::deselectAll()
492{
493 mSelectedFrames_byLast.clear();
494 mSelectedFrames_byPosition.clear();
495}
496
497bool Layer::canMoveSelectedFramesToOffset(int offset) const
498{
499 QList<int> newByPositions = mSelectedFrames_byPosition;
500
501 for (int pos : newByPositions)
502 {
503 pos += offset;
504 if (keyExists(pos) && !newByPositions.contains(pos)) {
505 return false;
506 }
507 }
508
509 return true;
510}
511
512void Layer::setExposureForSelectedFrames(int offset)
513{
514 auto selectedFramesByLast = mSelectedFrames_byLast;
515 auto selectedFramesByPos = mSelectedFrames_byPosition;
516
517 int addSpaceBetweenFrames = offset;
518
519 if (selectedFramesByLast.isEmpty()) { return; }
520
521 const int max = selectedFramesByPos.count()-1;
522 const int posForIndex = selectedFramesByPos[max];
523 const int nextPos = getNextKeyFramePosition(selectedFramesByPos[max]);
524
525 // When exposing the frame in front of the right most element in the selection.
526 if (posForIndex != nextPos) {
527 selectedFramesByPos.append(nextPos);
528 }
529
530 auto initialLastList = selectedFramesByLast;
531 auto initialPosList = selectedFramesByPos;
532
533 auto offsetList = QList<int>();
534
535 // Create an offset list to have reference of how many frames should be moved
536 for (int offset = 0; offset < selectedFramesByPos.count(); offset++)
537 {
538 int pos = selectedFramesByPos[offset];
539 int nextKeyPos = getNextKeyFramePosition(pos);
540
541 if (pos >= getMaxKeyFramePosition()) {
542 offsetList << (pos - 1) - getPreviousKeyFramePosition(pos) + addSpaceBetweenFrames;
543 } else { // first frame doesn't move so only the space that's required
544 offsetList << nextKeyPos - (pos + 1) + addSpaceBetweenFrames;
545 }
546 }
547
548 // Either positive or negative
549 int offsetDirection = offset > 0 ? 1 : -1;
550
551 for (int i = 0; i < selectedFramesByPos.count(); i++) {
552 const int itPos = selectedFramesByPos[i];
553 const int nextIndex = i + 1;
554 const int positionInFront = itPos + 1;
555
556 // Index safety
557 if (nextIndex < 0 || nextIndex >= selectedFramesByPos.count()) {
558 continue;
559 }
560
561 // Offset above 0 will move frames forward
562 // Offset below 0 will move a frame backwards
563 while ((offset > 0 && getNextKeyFramePosition(itPos) - positionInFront < offsetList[i]) ||
564 (getNextKeyFramePosition(itPos) - positionInFront > offsetList[i] && getNextKeyFramePosition(itPos) - positionInFront > 0)) {
565
566 selectAllFramesAfter(getNextKeyFramePosition(itPos));
567
568 for (int selIndex = 0; selIndex < mSelectedFrames_byPosition.count(); selIndex++) {
569
570 if (nextIndex+selIndex >= selectedFramesByPos.count()) { break; }
571
572 int pos = selectedFramesByPos[nextIndex+selIndex];
573
574 if (!mSelectedFrames_byPosition.contains(pos)) { continue; }
575
576 selectedFramesByPos[nextIndex+selIndex] = pos + offsetDirection;
577
578 // To make the sure we get the correct index for last selection list
579 // use the initial list where values doesn't affect the index.
580 int initialPos = initialPosList[nextIndex+selIndex];
581 int indexOfLast = initialLastList.indexOf(initialPos);
582 if (indexOfLast == -1 || nextIndex+selIndex >= selectedFramesByLast.count()) {
583 continue;
584 }
585 selectedFramesByLast[indexOfLast] = selectedFramesByLast[indexOfLast] + offsetDirection;
586 }
587
588 moveSelectedFrames(offsetDirection);
589 }
590 }
591
592 deselectAll();
593
594 // Reselect frames again based on last selection list to ensure selection behaviour
595 // works correctly
596 for (int pos : selectedFramesByLast) {
597 Q_UNUSED(pos)
598 setFrameSelected(selectedFramesByLast.takeLast(), true);
599 }
600}
601
602bool Layer::reverseOrderOfSelection()
603{
604 QList<int> selectedIndexes = mSelectedFrames_byPosition;
605
606 if (selectedIndexes.isEmpty()) { return false; }
607
608 for (int swapBegin = 0, swapEnd = selectedIndexes.count()-1; swapBegin < swapEnd; swapBegin++, swapEnd--) {
609 int oldPos = selectedIndexes[swapBegin];
610 int newPos = selectedIndexes[swapEnd];
611 bool canSwap = swapKeyFrames(oldPos, newPos);
612 Q_ASSERT(canSwap);
613 }
614 return true;
615}
616
617bool Layer::moveSelectedFrames(int offset)
618{
619 if (offset == 0 || mSelectedFrames_byPosition.count() <= 0) {
620 return false;
621 }
622
623 // If we are moving to the right we start moving selected frames from the highest (right) to the lowest (left)
624 int indexInSelection = mSelectedFrames_byPosition.count() - 1;
625 int step = -1;
626
627 if (offset < 0)
628 {
629 // If we are moving to the left we start moving selected frames from the lowest (left) to the highest (right)
630 indexInSelection = 0;
631 step = 1;
632
633 // Check if we are not moving out of the timeline
634 if (mSelectedFrames_byPosition[0] + offset < 1) {
635 offset = 1 - mSelectedFrames_byPosition[0];
636 }
637 }
638
639 while (!canMoveSelectedFramesToOffset(offset)) { offset += 1; }
640 if (offset == 0) { return false; }
641
642 for (; indexInSelection > -1 && indexInSelection < mSelectedFrames_byPosition.count(); indexInSelection += step)
643 {
644 int fromPos = mSelectedFrames_byPosition[indexInSelection];
645 int toPos = fromPos + offset;
646
647 // Get the frame to move
648 KeyFrame* selectedFrame = getKeyFrameAt(fromPos);
649
650 Q_ASSERT(!keyExists(toPos));
651
652 mKeyFrames.erase(fromPos);
653 markFrameAsDirty(fromPos);
654
655 // Update the position of the selected frame
656 selectedFrame->setPos(toPos);
657 mKeyFrames.insert(std::make_pair(toPos, selectedFrame));
658 markFrameAsDirty(toPos);
659 }
660
661 // Update selection lists
662 for (int& pos : mSelectedFrames_byPosition)
663 {
664 pos += offset;
665 }
666 for (int& pos : mSelectedFrames_byLast)
667 {
668 pos += offset;
669 }
670 return true;
671}
672
673bool Layer::isPaintable() const
674{
675 return (type() == BITMAP || type() == VECTOR);
676}
677
678bool Layer::keyExistsWhichCovers(int frameNumber)
679{
680 return getKeyFrameWhichCovers(frameNumber) != nullptr;
681}
682
683KeyFrame* Layer::getKeyFrameWhichCovers(int frameNumber) const
684{
685 auto keyFrame = getLastKeyFrameAtPosition(frameNumber);
686 if (keyFrame != nullptr)
687 {
688 if (keyFrame->pos() + keyFrame->length() > frameNumber)
689 {
690 return keyFrame;
691 }
692 }
693 return nullptr;
694}
695
696QDomElement Layer::createBaseDomElement(QDomDocument& doc) const
697{
698 QDomElement layerTag = doc.createElement("layer");
699 layerTag.setAttribute("id", id());
700 layerTag.setAttribute("name", name());
701 layerTag.setAttribute("visibility", visible());
702 layerTag.setAttribute("type", type());
703 return layerTag;
704}
705
706void Layer::loadBaseDomElement(const QDomElement& elem)
707{
708 if (!elem.attribute("id").isNull())
709 {
710 int id = elem.attribute("id").toInt();
711 setId(id);
712 }
713 setName(elem.attribute("name", "untitled"));
714 setVisible(elem.attribute("visibility", "1").toInt());
715}
DebugDetails
Definition: pencilerror.h:25
KeyFrame
Definition: keyframe.h:30
Layer::markFrameAsDirty
void markFrameAsDirty(const int frameNumber)
Mark the frame position as dirty.
Definition: layer.h:170
Layer::newSelectionOfConnectedFrames
bool newSelectionOfConnectedFrames(int position)
Make a selection from specified position until a blank spot appears The search is only looking forwar...
Definition: layer.cpp:453
Layer::addNewKeyFrameAt
bool addNewKeyFrameAt(int position)
Creates a new keyframe at the given position, unless one already exists.
Definition: layer.cpp:170
Layer::reverseOrderOfSelection
bool reverseOrderOfSelection()
Reverse order of selected frames.
Definition: layer.cpp:602
Layer::addKeyFrame
virtual bool addKeyFrame(int position, KeyFrame *pKeyFrame)
Adds a keyframe at the given position, unless one already exists.
Definition: layer.cpp:191
Layer::setExposureForSelectedFrames
void setExposureForSelectedFrames(int offset)
Add or subtract exposure from selected frames.
Definition: layer.cpp:512
Layer::canMoveSelectedFramesToOffset
bool canMoveSelectedFramesToOffset(int offset) const
Predetermines whether the frames can be moved to a new position depending on the offset.
Definition: layer.cpp:497
Layer::insertExposureAt
bool insertExposureAt(int position)
Will insert an empty frame (exposure) after the given position.
Definition: layer.cpp:204
Status
Definition: pencilerror.h:40
QDomDocument
QDomDocument::createElement
QDomElement createElement(const QString &tagName)
QDomElement
QDomElement::attribute
QString attribute(const QString &name, const QString &defValue) const const
QDomElement::setAttribute
void setAttribute(const QString &name, const QString &value)
QList
QList::append
void append(const T &value)
QList::begin
QList::iterator begin()
QList::clear
void clear()
QList::contains
bool contains(const T &value) const const
QList::count
int count(const T &value) const const
QList::end
QList::iterator end()
QList::indexOf
int indexOf(const T &value, int from) const const
QList::insert
void insert(int i, const T &value)
QList::isEmpty
bool isEmpty() const const
QList::removeAll
int removeAll(const T &value)
QList::removeOne
bool removeOne(const T &value)
QString
QString::arg
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString::isEmpty
bool isEmpty() const const
QString::isNull
bool isNull() const const
QString::toInt
int toInt(bool *ok, int base) const const
QStringList
Qt::left
QTextStream & left(QTextStream &stream)
Qt::right
QTextStream & right(QTextStream &stream)
Generated on Thu Jun 19 2025 04:02:27 for Pencil2D by doxygen 1.9.6 based on revision c1e137dfccefab10d70198bedd9497784189cf49