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
object.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 "object.h"
18
19#include <QDomDocument>
20#include <QTextStream>
21#include <QProgressDialog>
22#include <QApplication>
23#include <QFile>
24#include <QFileInfo>
25#include <QDir>
26#include <QDebug>
27#include <QDateTime>
28#include <QImageWriter>
29#include <QRegularExpression>
30
31#include "layer.h"
32#include "layerbitmap.h"
33#include "layervector.h"
34#include "layersound.h"
35#include "layercamera.h"
36
37#include "util.h"
38#include "bitmapimage.h"
39#include "vectorimage.h"
40#include "fileformat.h"
41#include "activeframepool.h"
42
43
44Object::Object()
45{
46 mActiveFramePool.reset(new ActiveFramePool);
47}
48
49Object::~Object()
50{
51 mActiveFramePool->clear();
52
53 for (Layer* layer : mLayers)
54 delete layer;
55 mLayers.clear();
56
57 deleteWorkingDir();
58}
59
60void Object::init()
61{
62 createWorkingDir();
63
64 // default palette
65 loadDefaultPalette();
66}
67
68QDomElement Object::saveXML(QDomDocument& doc) const
69{
70 QDomElement objectTag = doc.createElement("object");
71
72 for (Layer* layer : mLayers)
73 {
74 QDomElement layerTag = layer->createDomElement(doc);
75 objectTag.appendChild(layerTag);
76 }
77 return objectTag;
78}
79
80bool Object::loadXML(const QDomElement& docElem, ProgressCallback progressForward)
81{
82 if (docElem.isNull())
83 {
84 return false;
85 }
86
87 const QString dataDirPath = mDataDirPath;
88
89 for (QDomNode node = docElem.firstChild(); !node.isNull(); node = node.nextSibling())
90 {
91 QDomElement element = node.toElement(); // try to convert the node to an element.
92 if (element.tagName() != "layer")
93 {
94 continue;
95 }
96
97 Layer* newLayer;
98 switch (element.attribute("type").toInt())
99 {
100 case Layer::BITMAP:
101 newLayer = new LayerBitmap(getUniqueLayerID());
102 break;
103 case Layer::VECTOR:
104 newLayer = new LayerVector(getUniqueLayerID());
105 break;
106 case Layer::SOUND:
107 newLayer = new LayerSound(getUniqueLayerID());
108 break;
109 case Layer::CAMERA:
110 newLayer = new LayerCamera(getUniqueLayerID());
111 break;
112 default:
113 Q_UNREACHABLE();
114 }
115 mLayers.append(newLayer);
116 newLayer->loadDomElement(element, dataDirPath, progressForward);
117 }
118 return true;
119}
120
121LayerBitmap* Object::addNewBitmapLayer()
122{
123 LayerBitmap* layerBitmap = new LayerBitmap(getUniqueLayerID());
124 mLayers.append(layerBitmap);
125
126 layerBitmap->addNewKeyFrameAt(1);
127
128 return layerBitmap;
129}
130
131LayerVector* Object::addNewVectorLayer()
132{
133 LayerVector* layerVector = new LayerVector(getUniqueLayerID());
134 mLayers.append(layerVector);
135
136 layerVector->addNewKeyFrameAt(1);
137
138 return layerVector;
139}
140
141LayerSound* Object::addNewSoundLayer()
142{
143 LayerSound* layerSound = new LayerSound(getUniqueLayerID());
144 mLayers.append(layerSound);
145
146 // No default keyFrame at position 1 for Sound layer.
147
148 return layerSound;
149}
150
151LayerCamera* Object::addNewCameraLayer()
152{
153 LayerCamera* layerCamera = new LayerCamera(getUniqueLayerID());
154 mLayers.append(layerCamera);
155
156 layerCamera->addNewKeyFrameAt(1);
157
158 return layerCamera;
159}
160
161void Object::createWorkingDir()
162{
163 QString projectName;
164 if (mFilePath.isEmpty())
165 {
166 projectName = "Default";
167 }
168 else
169 {
170 QFileInfo fileInfo(mFilePath);
171 projectName = fileInfo.completeBaseName();
172 }
173 QDir dir(QDir::tempPath());
174
175 QString strWorkingDir;
176 do
177 {
178 strWorkingDir = QString("%1/Pencil2D/%2_%3_%4/").arg(QDir::tempPath(),
179 projectName,
180 PFF_TMP_DECOMPRESS_EXT,
181 uniqueString(8));
182 }
183 while(dir.exists(strWorkingDir));
184
185 dir.mkpath(strWorkingDir);
186 mWorkingDirPath = strWorkingDir;
187
188 QDir dataDir(strWorkingDir + PFF_DATA_DIR);
189 dataDir.mkpath(".");
190
191 mDataDirPath = dataDir.absolutePath();
192}
193
194void Object::deleteWorkingDir() const
195{
196 if (!mWorkingDirPath.isEmpty())
197 {
198 QDir dir(mWorkingDirPath);
199 bool ok = dir.removeRecursively();
200 Q_ASSERT(ok);
201 }
202}
203
204void Object::setWorkingDir(const QString& path)
205{
206 QDir dir(path);
207 Q_ASSERT(dir.exists());
208 mWorkingDirPath = path;
209}
210
211int Object::getMaxLayerID()
212{
213 int maxId = 0;
214 for (Layer* iLayer : mLayers)
215 {
216 if (iLayer->id() > maxId)
217 {
218 maxId = iLayer->id();
219 }
220 }
221 return maxId;
222}
223
224int Object::getUniqueLayerID()
225{
226 return 1 + getMaxLayerID();
227}
228
229Layer* Object::getLayer(int i) const
230{
231 if (i < 0 || i >= getLayerCount())
232 {
233 return nullptr;
234 }
235
236 return mLayers.at(i);
237}
238
239Layer* Object::getLayerBelow(int i, Layer::LAYER_TYPE type) const
240{
241 for (; i >= 0; --i)
242 {
243 Layer* layerCheck = getLayer(i);
244 Q_ASSERT(layerCheck);
245 if (layerCheck->type() == type)
246 {
247 return layerCheck;
248 }
249 }
250
251 return nullptr;
252}
253
254Layer* Object::findLayerById(int layerId) const
255{
256 for(Layer* layer : mLayers)
257 {
258 if (layer->id() == layerId)
259 {
260 return layer;
261 }
262 }
263 return nullptr;
264}
265
266Layer* Object::findLayerByName(const QString& strName, Layer::LAYER_TYPE type) const
267{
268 bool bCheckType = (type != Layer::UNDEFINED);
269 for (Layer* layer : mLayers)
270 {
271 bool isTypeMatch = (bCheckType) ? (type == layer->type()) : true;
272 if (isTypeMatch && layer->name() == strName)
273 {
274 return layer;
275 }
276 }
277 return nullptr;
278}
279
280Layer* Object::takeLayer(int layerId)
281{
282 // Removes the layer from this Object and returns it
283 // The ownership of this layer has been transfer to the caller
284 int index = -1;
285 for (int i = 0; i< mLayers.length(); ++i)
286 {
287 Layer* layer = mLayers[i];
288 if (layer->id() == layerId)
289 {
290 index = i;
291 break;
292 }
293 }
294
295 if (index == -1) { return nullptr; }
296
297 Layer* layer = mLayers.takeAt(index);
298 return layer;
299}
300
301bool Object::swapLayers(int i, int j)
302{
303 bool canSwap = canSwapLayers(i, j);
304 if (!canSwap) { return false; }
305
306 if (i != j)
307 {
308#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
309 mLayers.swapItemsAt(i, j);
310#else
311 mLayers.swap(i, j);
312#endif
313 }
314 return true;
315}
316
317bool Object::canSwapLayers(int layerIndexLeft, int layerIndexRight) const
318{
319 if (layerIndexLeft < 0 || layerIndexLeft >= mLayers.size())
320 {
321 return false;
322 }
323
324 if (layerIndexRight < 0 || layerIndexRight >= mLayers.size())
325 {
326 return false;
327 }
328
329 Layer* firstLayer = mLayers.first();
330 Layer* leftLayer = mLayers.at(layerIndexLeft);
331 Layer* rightLayer = mLayers.at(layerIndexRight);
332
333 // The bottom layer can't be swapped!
334 if ((leftLayer->type() == Layer::CAMERA ||
335 rightLayer->type() == Layer::CAMERA) &&
336 (firstLayer == leftLayer || firstLayer == rightLayer)) {
337 return false;
338 }
339 return true;
340}
341
342bool Object::canDeleteLayer(int index) const
343{
344 // We expect the first camera layer to be at the bottom and this layer must not be deleted!
345 if (index == 0) {
346 return false;
347 }
348
349 if (mLayers.at(index) == nullptr)
350 {
351 return false;
352 }
353
354 return true;
355}
356
357void Object::deleteLayer(int i)
358{
359 if (i > -1 && i < mLayers.size())
360 {
361 delete mLayers.takeAt(i);
362 }
363}
364
365void Object::deleteLayer(Layer* layer)
366{
367 auto it = std::find(mLayers.begin(), mLayers.end(), layer);
368
369 if (it != mLayers.end())
370 {
371 delete layer;
372 mLayers.erase(it);
373 }
374}
375
376bool Object::addLayer(Layer* layer)
377{
378 if (layer == nullptr || mLayers.contains(layer))
379 {
380 return false;
381 }
382 layer->setId(getUniqueLayerID());
383 mLayers.append(layer);
384 return true;
385}
386
387ColorRef Object::getColor(int index) const
388{
389 ColorRef result(Qt::white, tr("error"));
390 if (index > -1 && index < mPalette.size())
391 {
392 result = mPalette.at(index);
393 }
394 return result;
395}
396
397void Object::setColor(int index, const QColor& newColor)
398{
399 Q_ASSERT(index >= 0);
400
401 mPalette[index].color = newColor;
402}
403
404void Object::setColorRef(int index, const ColorRef& newColorRef)
405{
406 mPalette[index] = newColorRef;
407}
408
409void Object::movePaletteColor(int start, int end)
410{
411 mPalette.move(start, end);
412}
413
414void Object::moveVectorColor(int start, int end)
415{
416 for (Layer* layer : mLayers)
417 {
418 if (layer->type() == Layer::VECTOR)
419 {
420 static_cast<LayerVector*>(layer)->moveColor(start, end);
421 }
422 }
423}
424
425void Object::addColorAtIndex(int index, const ColorRef& newColor)
426{
427 mPalette.insert(index, newColor);
428}
429
430bool Object::isColorInUse(int index) const
431{
432 for (Layer* layer : mLayers)
433 {
434 if (layer->type() == Layer::VECTOR)
435 {
436 LayerVector* layerVector = static_cast<LayerVector*>(layer);
437
438 if (layerVector->usesColor(index))
439 {
440 return true;
441 }
442 }
443 }
444 return false;
445}
446
447void Object::removeColor(int index)
448{
449 for (Layer* layer : mLayers)
450 {
451 if (layer->type() == Layer::VECTOR)
452 {
453 LayerVector* layerVector = static_cast<LayerVector*>(layer);
454 layerVector->removeColor(index);
455 }
456 }
457
458 mPalette.removeAt(index);
459
460 // update the vector pictures using that color!
461}
462
463void Object::renameColor(int i, const QString& text)
464{
465 mPalette[i].name = text;
466}
467
468QString Object::savePalette(const QString& dataFolder) const
469{
470 QString fullPath = QDir(dataFolder).filePath(PFF_PALETTE_FILE);
471 bool ok = exportPalette(fullPath);
472 if (ok)
473 return fullPath;
474 return "";
475}
476
477void Object::exportPaletteGPL(QFile& file) const
478{
479 QString fileName = QFileInfo(file).baseName();
480 QTextStream out(&file);
481
482 out << "GIMP Palette" << "\n";
483 out << "Name: " << fileName << "\n";
484 out << "#" << "\n";
485
486 for (const ColorRef& ref : mPalette)
487 {
488 QColor toRgb = ref.color.toRgb();
489 out << QString("%1 %2 %3").arg(toRgb.red()).arg(toRgb.green()).arg(toRgb.blue());
490 out << " " << ref.name << "\n";
491 }
492}
493
494void Object::exportPalettePencil(QFile& file) const
495{
496 QTextStream out(&file);
497
498 QDomDocument doc("PencilPalette");
499 QDomElement root = doc.createElement("palette");
500 doc.appendChild(root);
501 for (const ColorRef& ref : mPalette)
502 {
503 QDomElement tag = doc.createElement("Color");
504 tag.setAttribute("name", ref.name);
505 tag.setAttribute("red", ref.color.red());
506 tag.setAttribute("green", ref.color.green());
507 tag.setAttribute("blue", ref.color.blue());
508 tag.setAttribute("alpha", ref.color.alpha());
509 root.appendChild(tag);
510 }
511 int indentSize = 2;
512 doc.save(out, indentSize);
513}
514
515bool Object::exportPalette(const QString& filePath) const
516{
517 QFile file(filePath);
518 if (!file.open(QFile::WriteOnly | QFile::Text))
519 {
520 qDebug("Error: cannot export palette");
521 return false;
522 }
523
524 if (file.fileName().endsWith(".gpl", Qt::CaseInsensitive))
525 exportPaletteGPL(file);
526 else
527 exportPalettePencil(file);
528
529 return true;
530}
531
532/* Import the .gpl GIMP palette format.
533 *
534 * This functions supports importing both the old and new .gpl formats.
535 * This should load colors the same as GIMP, with the following intentional exceptions:
536 * - Whitespace before and after a name does not appear in the name
537 * - The last line is processed, even if there is not a trailing newline
538 * - Colors without a name will use our automatic naming system rather than "Untitled"
539 */
540void Object::importPaletteGPL(QFile& file)
541{
542 QTextStream in(&file);
543 QString line;
544
545 // The first line must start with "GIMP Palette"
546 // Displaying an error here would be nice
547 in.readLineInto(&line);
548 if (!line.startsWith("GIMP Palette")) return;
549
550 in.readLineInto(&line);
551
552 // There are two GPL formats, the new one must start with "Name: " on the second line
553 if (line.startsWith("Name: "))
554 {
555 in.readLineInto(&line);
556 // The new format contains an optional third line starting with "Columns: "
557 if (line.startsWith("Columns: "))
558 {
559 // Skip to the next line
560 in.readLineInto(&line);
561 }
562 }
563
564 // Colors inherit the value from the previous color for missing channels
565 // Some palettes may rely on this behavior, so we should try to replicate it
566 QColor prevColor(Qt::black);
567
568 do
569 {
570 // Ignore comments and empty lines
571 if (line.isEmpty() || line.startsWith("#")) continue;
572
573 int red = 0;
574 int green = 0;
575 int blue = 0;
576
577 int countInLine = 0;
578 QString name = "";
579#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
580 for(const QString& snip : line.split(QRegularExpression("\\s|\\t"), Qt::SkipEmptyParts))
581#else
582 for(const QString& snip : line.split(QRegularExpression("\\s|\\t"), QString::SkipEmptyParts))
583#endif
584 {
585 switch (countInLine)
586 {
587 case 0:
588 red = snip.toInt();
589 break;
590 case 1:
591 green = snip.toInt();
592 break;
593 case 2:
594 blue = snip.toInt();
595 break;
596 default:
597 name += snip + " ";
598 }
599 countInLine++;
600 }
601
602 // trim additional spaces
603 name = name.trimmed();
604
605 // Get values from previous color if necessary
606 if (countInLine < 2) green = prevColor.green();
607 if (countInLine < 3) blue = prevColor.blue();
608
609 // GIMP assigns colors the name "Untitled" by default now,
610 // so in addition to missing names, we also use automatic
611 // naming for this
612 if (name.isEmpty() || name == "Untitled") name = QString();
613
614 QColor color(red, green, blue);
615 if (color.isValid())
616 {
617 mPalette.append(ColorRef(color, name));
618 prevColor = color;
619 }
620 } while (in.readLineInto(&line));
621}
622
623void Object::importPalettePencil(QFile& file)
624{
625 QDomDocument doc;
626 doc.setContent(&file);
627
628 QDomElement docElem = doc.documentElement();
629 QDomNode tag = docElem.firstChild();
630 while (!tag.isNull())
631 {
632 QDomElement e = tag.toElement(); // try to convert the node to an element.
633 if (!e.isNull())
634 {
635 QString name = e.attribute("name");
636 int r = e.attribute("red").toInt();
637 int g = e.attribute("green").toInt();
638 int b = e.attribute("blue").toInt();
639 int a = e.attribute("alpha", "255").toInt();
640 mPalette.append(ColorRef(QColor(r, g, b, a), name));
641 }
642 tag = tag.nextSibling();
643 }
644}
645
646void Object::openPalette(const QString& filePath)
647{
648 if (!QFile::exists(filePath))
649 {
650 return;
651 }
652
653 mPalette.clear();
654 importPalette(filePath);
655}
656
657/*
658 * Imports palette, e.g. appends to palette
659*/
660bool Object::importPalette(const QString& filePath)
661{
662 QFile file(filePath);
663
664 if (!file.open(QFile::ReadOnly))
665 {
666 return false;
667 }
668
669 if (file.fileName().endsWith(".gpl", Qt::CaseInsensitive))
670 {
671 importPaletteGPL(file);
672 } else {
673 importPalettePencil(file);
674 }
675 return true;
676}
677
678
679void Object::loadDefaultPalette()
680{
681 mPalette.clear();
682 addColor(ColorRef(QColor(Qt::black), tr("Black")));
683 addColor(ColorRef(QColor(Qt::red), tr("Red")));
684 addColor(ColorRef(QColor(Qt::darkRed), tr("Dark Red")));
685 addColor(ColorRef(QColor(255, 128, 0), tr("Orange")));
686 addColor(ColorRef(QColor(128, 64, 0), tr("Dark Orange")));
687 addColor(ColorRef(QColor(Qt::yellow), tr("Yellow")));
688 addColor(ColorRef(QColor(Qt::darkYellow), tr("Dark Yellow")));
689 addColor(ColorRef(QColor(Qt::green), tr("Green")));
690 addColor(ColorRef(QColor(Qt::darkGreen), tr("Dark Green")));
691 addColor(ColorRef(QColor(Qt::cyan), tr("Cyan")));
692 addColor(ColorRef(QColor(Qt::darkCyan), tr("Dark Cyan")));
693 addColor(ColorRef(QColor(Qt::blue), tr("Blue")));
694 addColor(ColorRef(QColor(Qt::darkBlue), tr("Dark Blue")));
695 addColor(ColorRef(QColor(255, 255, 255), tr("White")));
696 addColor(ColorRef(QColor(220, 220, 229), tr("Very Light Grey")));
697 addColor(ColorRef(QColor(Qt::lightGray), tr("Light Grey")));
698 addColor(ColorRef(QColor(Qt::gray), tr("Grey")));
699 addColor(ColorRef(QColor(Qt::darkGray), tr("Dark Grey")));
700 addColor(ColorRef(QColor(255, 227, 187), tr("Pale Orange Yellow")));
701 addColor(ColorRef(QColor(221, 196, 161), tr("Pale Grayish Orange Yellow")));
702 addColor(ColorRef(QColor(255, 214, 156), tr("Orange Yellow ")));
703 addColor(ColorRef(QColor(207, 174, 127), tr("Grayish Orange Yellow")));
704 addColor(ColorRef(QColor(255, 198, 116), tr("Light Orange Yellow")));
705 addColor(ColorRef(QColor(227, 177, 105), tr("Light Grayish Orange Yellow")));
706}
707
708void Object::paintImage(QPainter& painter,int frameNumber,
709 bool background,
710 bool antialiasing) const
711{
712 updateActiveFrames(frameNumber);
713
714 painter.setRenderHint(QPainter::Antialiasing, true);
715 painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
716 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
717
718 // paints the background
719 if (background)
720 {
721 painter.setPen(Qt::NoPen);
722 painter.setBrush(Qt::white);
723 painter.setWorldMatrixEnabled(false);
724 painter.drawRect(QRect(0, 0, painter.device()->width(), painter.device()->height()));
725 painter.setWorldMatrixEnabled(true);
726 }
727
728 for (Layer* layer : mLayers)
729 {
730 if (!layer->visible())
731 {
732 continue;
733 }
734
735 painter.setOpacity(1.0);
736
737 if (layer->type() == Layer::BITMAP)
738 {
739
740 LayerBitmap* layerBitmap = static_cast<LayerBitmap*>(layer);
741 BitmapImage* bitmap = layerBitmap->getLastBitmapImageAtFrame(frameNumber);
742 if (bitmap)
743 {
744 painter.setOpacity(bitmap->getOpacity());
745 bitmap->paintImage(painter);
746 }
747
748 }
749 // paints the vector images
750 if (layer->type() == Layer::VECTOR)
751 {
752 LayerVector* layerVector = static_cast<LayerVector*>(layer);
753 VectorImage* vec = layerVector->getLastVectorImageAtFrame(frameNumber, 0);
754 if (vec)
755 {
756 painter.setOpacity(vec->getOpacity());
757 vec->paintImage(painter, *this, false, false, antialiasing);
758 }
759 }
760 }
761}
762
763QString Object::copyFileToDataFolder(const QString& strFilePath)
764{
765 if (!QFile::exists(strFilePath))
766 {
767 qDebug() << "[Object] sound file doesn't exist: " << strFilePath;
768 return "";
769 }
770
771 QString sNewFileName = "sound_";
772 sNewFileName += QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss_zzz.");
773 sNewFileName += QFileInfo(strFilePath).suffix();
774
775 QString destFile = QDir(mDataDirPath).filePath(sNewFileName);
776
777 if (QFile::exists(destFile))
778 {
779 QFile::remove(destFile);
780 }
781
782 bool bCopyOK = QFile::copy(strFilePath, destFile);
783 if (!bCopyOK)
784 {
785 qDebug() << "[Object] couldn't copy sound file to data folder: " << strFilePath;
786 return "";
787 }
788
789 return destFile;
790}
791
792Status Object::exportFrames(int frameStart, int frameEnd,
793 const LayerCamera* cameraLayer,
794 QSize exportSize,
795 QString filePath,
796 QString format,
797 bool transparency,
798 bool exportKeyframesOnly,
799 const QString& layerName,
800 bool antialiasing,
801 QProgressDialog* progress = nullptr,
802 int progressMax = 50) const
803{
804 Q_ASSERT(cameraLayer);
805
806 QString extension = "";
807 QString formatStr = format;
808 if (formatStr == "PNG" || formatStr == "png")
809 {
810 format = "PNG";
811 extension = ".png";
812 }
813 if (formatStr == "JPG" || formatStr == "jpg" || formatStr == "JPEG" || formatStr == "jpeg")
814 {
815 format = "JPG";
816 extension = ".jpg";
817 transparency = false; // JPG doesn't support transparency, so we have to include the background
818 }
819 if (formatStr == "TIFF" || formatStr == "tiff" || formatStr == "TIF" || formatStr == "tif")
820 {
821 format = "TIFF";
822 extension = ".tiff";
823 }
824 if (formatStr == "BMP" || formatStr == "bmp")
825 {
826 format = "BMP";
827 extension = ".bmp";
828 transparency = false;
829 }
830 if (formatStr == "WEBP" || formatStr == "webp") {
831 format = "WEBP";
832 extension = ".webp";
833 }
834 if (filePath.endsWith(extension, Qt::CaseInsensitive))
835 {
836 filePath.chop(extension.size());
837 }
838
839 qDebug() << "Exporting frames from "
840 << frameStart << "to"
841 << frameEnd
842 << "at size " << exportSize;
843
844 DebugDetails dd;
845 dd << "\n[Export frames diagnostics]\n";
846 bool ok = true;
847
848 for (int currentFrame = frameStart; currentFrame <= frameEnd; currentFrame++)
849 {
850 if (progress != nullptr)
851 {
852 int totalFramesToExport = (frameEnd - frameStart) + 1;
853 if (totalFramesToExport != 0) // Avoid dividing by zero.
854 {
855 progress->setValue((currentFrame - frameStart + 1) * progressMax / totalFramesToExport);
856 QApplication::processEvents(); // Required to make progress bar update on-screen.
857 }
858
859 if (progress->wasCanceled())
860 {
861 break;
862 }
863 }
864
865 QTransform view = cameraLayer->getViewAtFrame(currentFrame);
866 QSize camSize = cameraLayer->getViewSize();
867
868 QString frameNumberString = QString::number(currentFrame);
869 while (frameNumberString.length() < 4)
870 {
871 frameNumberString.prepend("0");
872 }
873 QString sFileName = filePath + frameNumberString + extension;
874 Layer* layer = findLayerByName(layerName);
875 Status st = Status::SAFE;
876 if (exportKeyframesOnly)
877 {
878 if (layer->keyExists(currentFrame))
879 {
880 st = exportIm(currentFrame, view, camSize, exportSize, sFileName, format, antialiasing, transparency);
881 }
882 }
883 else
884 {
885 st = exportIm(currentFrame, view, camSize, exportSize, sFileName, format, antialiasing, transparency);
886 }
887
888 if (!st.ok())
889 {
890 ok = false;
891 dd.collect(st.details());
892 }
893 }
894
895 if (!ok)
896 {
897 dd << "\nError: Failed to export one or more frames";
898 return Status(Status::FAIL, dd);
899 }
900
901 return Status::OK;
902}
903
904Status Object::exportIm(int frame, const QTransform& view, QSize cameraSize, QSize exportSize, const QString& filePath, const QString& format, bool antialiasing, bool transparency) const
905{
906 QImage imageToExport(exportSize, QImage::Format_ARGB32_Premultiplied);
907
908 QColor bgColor = Qt::white;
909 if (transparency)
910 bgColor.setAlpha(0);
911 imageToExport.fill(bgColor);
912
913 QTransform centralizeCamera;
914 centralizeCamera.translate(cameraSize.width() / 2, cameraSize.height() / 2);
915
916 QPainter painter(&imageToExport);
917 painter.setWorldTransform(view * centralizeCamera);
918 painter.setWindow(QRect(0, 0, cameraSize.width(), cameraSize.height()));
919
920 paintImage(painter, frame, false, antialiasing);
921
922 QImageWriter writer(filePath, format.toStdString().c_str());
923 bool b = writer.write(imageToExport);
924 if (b) {
925 return Status::OK;
926 } else {
927 DebugDetails dd;
928 dd << "Object::exportIm";
929 dd << QString("&nbsp;&nbsp;filePath: ").append(filePath);
930 dd << QString("&nbsp;&nbsp;Error: %1 (code %2)").arg(writer.errorString()).arg(static_cast<int>(writer.error()));
931 return Status(Status::FAIL, dd);
932 }
933}
934
935int Object::getLayerCount() const
936{
937 return mLayers.size();
938}
939
940void Object::setData(const ObjectData& d)
941{
942 mData = d;
943}
944
945int Object::totalKeyFrameCount() const
946{
947 int sum = 0;
948 for (const Layer* layer : mLayers)
949 {
950 sum += layer->keyFrameCount();
951 }
952 return sum;
953}
954
955void Object::updateActiveFrames(int frame) const
956{
957 const int beginFrame = std::max(frame - 3, 1);
958 const int endFrame = frame + 4;
959
960 const int minFrameCount = getLayerCount() * (endFrame - beginFrame);
961 mActiveFramePool->setMinFrameCount(minFrameCount);
962
963 for (Layer* layer : mLayers)
964 {
965 if (layer->visible())
966 {
967 for (int k = beginFrame; k < endFrame; ++k)
968 {
969 KeyFrame* key = layer->getKeyFrameAt(k);
970 mActiveFramePool->put(key);
971 }
972 }
973 }
974}
975
976void Object::setActiveFramePoolSize(int sizeInMB)
977{
978 // convert MB to Byte
979 mActiveFramePool->resize(qint64(sizeInMB) * 1024 * 1024);
980}
ActiveFramePool
ActiveFramePool implemented a LRU cache to keep tracking the most recent accessed key frames A key fr...
Definition: activeframepool.h:34
BitmapImage
Definition: bitmapimage.h:28
ColorRef
Definition: colorref.h:26
DebugDetails
Definition: pencilerror.h:25
KeyFrame
Definition: keyframe.h:30
LayerBitmap
Definition: layerbitmap.h:26
LayerCamera
Definition: layercamera.h:30
Layer
Definition: layer.h:33
Layer::addNewKeyFrameAt
bool addNewKeyFrameAt(int position)
Creates a new keyframe at the given position, unless one already exists.
Definition: layer.cpp:170
LayerSound
Definition: layersound.h:26
LayerVector
Definition: layervector.h:26
ObjectData
Definition: objectdata.h:27
Object::canDeleteLayer
bool canDeleteLayer(int index) const
Allows you to check whether the layer at the given index can be deleted.
Definition: object.cpp:342
Object::canSwapLayers
bool canSwapLayers(int layerIndexLeft, int layerIndexRight) const
Allows you to check whether two layers can be swappped, before doing the actual operation.
Definition: object.cpp:317
Status
Definition: pencilerror.h:40
VectorImage
Definition: vectorimage.h:32
VectorImage::paintImage
void paintImage(QPainter &painter, const Object &object, bool simplified, bool showThinCurves, bool antialiasing)
VectorImage::paintImage.
Definition: vectorimage.cpp:1191
QColor
QColor::blue
int blue() const const
QColor::green
int green() const const
QColor::red
int red() const const
QColor::setAlpha
void setAlpha(int alpha)
QColor::toRgb
QColor toRgb() const const
QCoreApplication::processEvents
void processEvents(QEventLoop::ProcessEventsFlags flags)
QDateTime::currentDateTime
QDateTime currentDateTime()
QDateTime::toString
QString toString(Qt::DateFormat format) const const
QDir
QDir::filePath
QString filePath(const QString &fileName) const const
QDir::tempPath
QString tempPath()
QDomDocument
QDomDocument::createElement
QDomElement createElement(const QString &tagName)
QDomDocument::documentElement
QDomElement documentElement() const const
QDomDocument::setContent
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
QDomElement
QDomElement::attribute
QString attribute(const QString &name, const QString &defValue) const const
QDomElement::setAttribute
void setAttribute(const QString &name, const QString &value)
QDomElement::tagName
QString tagName() const const
QDomNode
QDomNode::appendChild
QDomNode appendChild(const QDomNode &newChild)
QDomNode::firstChild
QDomNode firstChild() const const
QDomNode::isNull
bool isNull() const const
QDomNode::nextSibling
QDomNode nextSibling() const const
QDomNode::save
void save(QTextStream &stream, int indent, QDomNode::EncodingPolicy encodingPolicy) const const
QDomNode::toElement
QDomElement toElement() const const
QFile
QFileDevice::fileName
virtual QString fileName() const const
QFileInfo
QFileInfo::baseName
QString baseName() const const
QFileInfo::suffix
QString suffix() const const
QImage
QImage::Format_ARGB32_Premultiplied
Format_ARGB32_Premultiplied
QImageWriter
QIODevice::WriteOnly
WriteOnly
QIODevice::open
virtual bool open(QIODevice::OpenMode mode)
QList::at
const T & at(int i) const const
QList::insert
void insert(int i, const T &value)
QList::move
void move(int from, int to)
QList::removeAt
void removeAt(int i)
QList::size
int size() const const
QPaintDevice::height
int height() const const
QPaintDevice::width
int width() const const
QPainter
QPainter::CompositionMode_SourceOver
CompositionMode_SourceOver
QPainter::Antialiasing
Antialiasing
QPainter::device
QPaintDevice * device() const const
QPainter::drawRect
void drawRect(const QRectF &rectangle)
QPainter::setBrush
void setBrush(const QBrush &brush)
QPainter::setCompositionMode
void setCompositionMode(QPainter::CompositionMode mode)
QPainter::setOpacity
void setOpacity(qreal opacity)
QPainter::setPen
void setPen(const QColor &color)
QPainter::setRenderHint
void setRenderHint(QPainter::RenderHint hint, bool on)
QPainter::setWindow
void setWindow(const QRect &rectangle)
QPainter::setWorldMatrixEnabled
void setWorldMatrixEnabled(bool enable)
QPainter::setWorldTransform
void setWorldTransform(const QTransform &matrix, bool combine)
QProgressDialog
QRect
QRegularExpression
QSize
QSize::height
int height() const const
QSize::width
int width() const const
QString::SkipEmptyParts
SkipEmptyParts
QString::split
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString
QString::append
QString & append(QChar ch)
QString::arg
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString::chop
void chop(int n)
QString::endsWith
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString::isEmpty
bool isEmpty() const const
QString::length
int length() const const
QString::number
QString number(int n, int base)
QString::prepend
QString & prepend(QChar ch)
QString::size
int size() const const
QString::startsWith
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString::toInt
int toInt(bool *ok, int base) const const
QString::toStdString
int toStdString() const const
QString::trimmed
QString trimmed() const const
Qt::CaseInsensitive
CaseInsensitive
Qt::white
white
Qt::NoPen
NoPen
Qt::SkipEmptyParts
SkipEmptyParts
QTextStream
QTransform
QTransform::translate
QTransform & translate(qreal dx, qreal dy)
Generated on Fri Dec 19 2025 07:54:21 for Pencil2D by doxygen 1.9.6 based on revision 7fd8cd9e03f2d31750e199ecec202e5c0f30e532