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