Pencil2D Animation
Download Community News Docs Contribute
  • Overview
  • Articles
  • Code
  •  
  • Class List
  • Class Index
  • Class Hierarchy
  • Class Members
  • File List
Loading...
Searching...
No Matches
  • app
  • src
importimageseqdialog.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
18#include "importimageseqdialog.h"
19#include "ui_importimageseqoptions.h"
20#include "ui_importimageseqpreview.h"
21#include "util.h"
22#include "app_util.h"
23
24#include "editor.h"
25#include "errordialog.h"
26#include "predefinedsetmodel.h"
27#include "layermanager.h"
28#include "viewmanager.h"
29
30#include <QProgressDialog>
31#include <QMessageBox>
32#include <QDir>
33#include <QtDebug>
34#include <QDialogButtonBox>
35#include <QPushButton>
36
37ImportImageSeqDialog::ImportImageSeqDialog(QWidget* parent, Mode mode, FileType fileType, ImportCriteria importCriteria) :
38 ImportExportDialog(parent, mode, fileType), mParent(parent), mImportCriteria(importCriteria), mFileType(fileType)
39{
40
41 uiOptionsBox = new Ui::ImportImageSeqOptions;
42 uiOptionsBox->setupUi(getOptionsGroupBox());
43
44 uiGroupBoxPreview = new Ui::ImportImageSeqPreviewGroupBox;
45 uiGroupBoxPreview->setupUi(getPreviewGroupBox());
46
47 if (importCriteria == ImportCriteria::PredefinedSet) {
48 setupPredefinedLayout();
49 } else {
50 setupLayout();
51 }
52
53 getDialogButtonBox()->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(false);
54}
55
56void ImportImageSeqDialog::setupLayout()
57{
58
59 hideInstructionsLabel(true);
60
61 switch (mFileType)
62 {
63 case FileType::GIF:
64 setWindowTitle(tr("Import Animated GIF"));
65 break;
66 case FileType::IMAGE_SEQUENCE:
67 setWindowTitle(tr("Import image sequence"));
68 break;
69 default:
70 setWindowTitle(tr("Import animated image"));
71 }
72
73 connect(uiOptionsBox->spaceSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ImportImageSeqDialog::setSpace);
74 connect(this, &ImportImageSeqDialog::filePathsChanged, this, &ImportImageSeqDialog::validateFiles);
75}
76
77void ImportImageSeqDialog::setupPredefinedLayout()
78{
79 setWindowTitle(tr("Import predefined keyframe set"));
80 setInstructionsLabel(tr("Select an image that matches the criteria: MyFile000.png, eg. Joe001.png \n"
81 "The importer will search and find images matching the same criteria. You can see the result in the preview box below."));
82 hideOptionsGroupBox(true);
83 hidePreviewGroupBox(false);
84
85 connect(this, &ImportImageSeqDialog::filePathsChanged, this, &ImportImageSeqDialog::updatePreviewList);
86}
87
88ImportImageSeqDialog::~ImportImageSeqDialog()
89{
90 if (uiOptionsBox) {
91 delete uiOptionsBox;
92 }
93 if (uiGroupBoxPreview) {
94 delete uiGroupBoxPreview;
95 }
96}
97
98int ImportImageSeqDialog::getSpace()
99{
100 return uiOptionsBox->spaceSpinBox->value();
101}
102
103void ImportImageSeqDialog::updatePreviewList(const QStringList& list)
104{
105 Q_UNUSED(list)
106 if (mImportCriteria == ImportCriteria::PredefinedSet)
107 {
108 const PredefinedKeySet& keySet = generatePredefinedKeySet();
109
110 Status status = Status::OK;
111 status = validateKeySet(keySet, list);
112
113 QPushButton* okButton = getDialogButtonBox()->button(QDialogButtonBox::StandardButton::Ok);
114 if (status == Status::FAIL)
115 {
116 QMessageBox::warning(mParent,
117 status.title(),
118 status.description(),
119 QMessageBox::Ok,
120 QMessageBox::Ok);
121 okButton->setEnabled(false);
122 } else {
123 okButton->setEnabled(true);
124 }
125 setPreviewModel(keySet);
126 }
127}
128
129const PredefinedKeySet ImportImageSeqDialog::generatePredefinedKeySet() const
130{
131 PredefinedKeySet keySet;
132 const PredefinedKeySetParams& setParams = predefinedKeySetParams();
133
134 const QStringList& filenames = setParams.filenames;
135 const int& digits = setParams.digits;
136 const QString& folderPath = setParams.folderPath;
137
138 for (int i = 0; i < filenames.size(); i++)
139 {
140 const int& frameIndex = filenames[i].mid(setParams.dot - digits, digits).toInt();
141 const QString& absolutePath = folderPath + filenames[i];
142
143 keySet.insert(frameIndex, absolutePath);
144 }
145 keySet.setLayerName(setParams.prefix);
146 return keySet;
147}
148
149void ImportImageSeqDialog::setPreviewModel(const PredefinedKeySet& keySet)
150{
151 PredefinedSetModel* previewModel = new PredefinedSetModel(nullptr, keySet);
152 uiGroupBoxPreview->tableView->setModel(previewModel);
153 uiGroupBoxPreview->tableView->setColumnWidth(0, 500);
154 uiGroupBoxPreview->tableView->setColumnWidth(1, 100);
155}
156
157ImportExportDialog::Mode ImportImageSeqDialog::getMode()
158{
159 return ImportExportDialog::Import;
160}
161
162FileType ImportImageSeqDialog::getFileType()
163{
164 return mFileType;
165}
166
167void ImportImageSeqDialog::setSpace(int number)
168{
169 QSignalBlocker b1(uiOptionsBox->spaceSpinBox);
170 uiOptionsBox->spaceSpinBox->setValue(number);
171}
172
173void ImportImageSeqDialog::importArbitrarySequence(const ImportImageConfig importImageConfig)
174{
175 QStringList files = getFilePaths();
176 int number = getSpace();
177
178 // Show a progress dialog, as this can take a while if you have lots of images.
179 QProgressDialog progress(tr("Importing image sequence..."), tr("Abort"), 0, 100, mParent);
180 hideQuestionMark(progress);
181 progress.setWindowModality(Qt::WindowModal);
182 progress.show();
183
184 int totalImagesToImport = files.count();
185 int imagesImportedSoFar = 0;
186 progress.setMaximum(totalImagesToImport);
187
188 for (const QString& strImgFile : files)
189 {
190 Status st = mEditor->importImage(strImgFile, importImageConfig);
191 if (!st.ok())
192 {
193 ErrorDialog errorDialog(st.title(), st.description(), st.details().html());
194 errorDialog.exec();
195 break;
196 }
197
198 imagesImportedSoFar++;
199 progress.setValue(imagesImportedSoFar);
200 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // Required to make progress bar update
201
202 if (progress.wasCanceled())
203 {
204 break;
205 }
206
207 for (int i = 1; i < number; i++)
208 {
209 mEditor->scrubForward();
210 }
211 }
212
213
214 emit notifyAnimationLengthChanged();
215 progress.close();
216}
217
218const PredefinedKeySetParams ImportImageSeqDialog::predefinedKeySetParams() const
219{
220 QString strFilePath = getFilePath();
221 PredefinedKeySetParams setParams;
222
223 // local vars for testing file validity
224 int dot = strFilePath.lastIndexOf(".");
225 int slash = strFilePath.lastIndexOf("/");
226 QString path = strFilePath.left(slash + 1);
227 QString digit = strFilePath.mid(slash + 1, dot - slash - 1);
228
229 // Find number of digits (min: 1, max: digit.length - 1)
230 int digits = 0;
231 for (int i = digit.length() - 1; i > 0; i--)
232 {
233 if (digit.at(i).isDigit())
234 {
235 digits++;
236 }
237 else
238 {
239 break;
240 }
241 }
242
243 if (digits < 1)
244 {
245 return setParams;
246 }
247
248 digit = strFilePath.mid(dot - digits, digits);
249 QString prefix = strFilePath.mid(slash + 1, dot - slash - digits - 1);
250 QString suffix = strFilePath.mid(dot, strFilePath.length() - 1);
251
252 QDir dir = strFilePath.left(strFilePath.lastIndexOf("/"));
253 QStringList sList = dir.entryList(QDir::Files, QDir::Name);
254 if (sList.isEmpty()) { return setParams; }
255
256 // List of files is not empty. Let's go find the relevant files
257 QStringList finalList;
258 int validLength = prefix.length() + digit.length() + suffix.length();
259 for (int i = 0; i < sList.size(); i++)
260 {
261 if (sList[i].startsWith(prefix) &&
262 sList[i].length() == validLength &&
263 sList[i].mid(sList[i].lastIndexOf(".") - digits, digits).toInt() > 0 &&
264 sList[i].endsWith(suffix))
265 {
266 finalList.append(sList[i]);
267 }
268 }
269 if (finalList.isEmpty()) { return setParams; }
270
271 // List of relevant files is not empty. Let's validate them
272 dot = finalList[0].lastIndexOf(".");
273
274 QStringList absolutePaths;
275 for (const QString& fileName : finalList) {
276 absolutePaths << path + fileName;
277 }
278
279 setParams.dot = dot;
280 setParams.digits = digits;
281 setParams.filenames = finalList;
282 setParams.folderPath = path;
283 setParams.absolutePaths = absolutePaths;
284 setParams.prefix = prefix;
285 return setParams;
286}
287
288void ImportImageSeqDialog::importPredefinedSet(const ImportImageConfig importImageConfig)
289{
290 PredefinedKeySet keySet = generatePredefinedKeySet();
291
292 // Show a progress dialog, as this can take a while if you have lots of images.
293 QProgressDialog progress(tr("Importing images..."), tr("Abort"), 0, 100, mParent);
294 hideQuestionMark(progress);
295 progress.setWindowModality(Qt::WindowModal);
296 progress.show();
297
298 int totalImagesToImport = keySet.size();
299 int imagesImportedSoFar = 0;
300 progress.setMaximum(totalImagesToImport);
301
302 mEditor->layers()->createBitmapLayer(keySet.layerName());
303
304 for (int i = 0; i < keySet.size(); i++)
305 {
306 const int& frameIndex = keySet.keyFrameIndexAt(i);
307 const QString& filePath = keySet.filePathAt(i);
308
309 mEditor->scrubTo(frameIndex);
310 Status st = mEditor->importImage(filePath, importImageConfig);
311 if (!st.ok())
312 {
313 ErrorDialog errorDialog(st.title(), st.description(), st.details().html());
314 errorDialog.exec();
315 break;
316 }
317 imagesImportedSoFar++;
318
319 progress.setValue(imagesImportedSoFar);
320 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // Required to make progress bar update
321
322 if (progress.wasCanceled())
323 {
324 break;
325 }
326 }
327
328 emit notifyAnimationLengthChanged();
329}
330
331QStringList ImportImageSeqDialog::getFilePaths()
332{
333 return ImportExportDialog::getFilePaths();
334}
335
336Status ImportImageSeqDialog::validateKeySet(const PredefinedKeySet& keySet, const QStringList& filepaths)
337{
338 QString failedPathsString;
339
340 Status status = Status::OK;
341
342 if (filepaths.isEmpty()) { status = Status::FAIL; }
343
344 if (keySet.isEmpty())
345 {
346 status = Status::FAIL;
347 failedPathsString = QLocale().createSeparatedList(filepaths);
348 }
349
350 if (status == Status::FAIL)
351 {
352 status.setTitle(tr("Invalid path"));
353 status.setDescription(QString(tr("The following file did not meet the criteria: \n%1 \n\nRead the instructions and try again")).arg(failedPathsString));
354 }
355
356 return status;
357}
358
359Status ImportImageSeqDialog::validateFiles(const QStringList &filepaths)
360{
361 QString failedPathsString = "";
362
363 Status status = Status::OK;
364
365 if (filepaths.isEmpty()) { status = Status::FAIL; }
366
367 for (int i = 0; i < filepaths.count(); i++)
368 {
369 QFileInfo file(filepaths.at(i));
370 if (!file.exists())
371 failedPathsString += filepaths.at(i) + "\n";
372 }
373
374 if (!failedPathsString.isEmpty())
375 {
376 status = Status::FAIL;
377 status.setTitle(tr("Invalid path"));
378 status.setDescription(QString(tr("The following file(-s) did not meet the criteria: \n%1")).arg(failedPathsString));
379 }
380
381 if (status == Status::OK)
382 {
383 getDialogButtonBox()->button(QDialogButtonBox::StandardButton::Ok)->setEnabled(true);
384 }
385 return status;
386}
ErrorDialog
Definition: errordialog.h:28
ImportExportDialog
Definition: importexportdialog.h:32
PredefinedSetModel
Definition: predefinedsetmodel.h:81
Status
Definition: pencilerror.h:40
QChar::isDigit
bool isDigit() const const
QCoreApplication::processEvents
void processEvents(QEventLoop::ProcessEventsFlags flags)
QDialogButtonBox::button
QPushButton * button(QDialogButtonBox::StandardButton which) const const
QDir
QDir::Files
Files
QDir::Name
Name
QDir::entryList
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
QEventLoop::ExcludeUserInputEvents
ExcludeUserInputEvents
QFileInfo
QList::append
void append(const T &value)
QList::at
const T & at(int i) const const
QList::count
int count(const T &value) const const
QList::isEmpty
bool isEmpty() const const
QList::mid
QList< T > mid(int pos, int length) const const
QList::size
int size() const const
QLocale
QLocale::createSeparatedList
QString createSeparatedList(const QStringList &list) const const
QMessageBox::Ok
Ok
QMessageBox::warning
QMessageBox::StandardButton warning(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
QObject::connect
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject::tr
QString tr(const char *sourceText, const char *disambiguation, int n)
QProgressDialog
QPushButton
QSignalBlocker
QSpinBox
QSpinBox::valueChanged
void valueChanged(int i)
QString
QString::at
const QChar at(int position) const const
QString::isEmpty
bool isEmpty() const const
QString::lastIndexOf
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString::left
QString left(int n) const const
QString::length
int length() const const
QString::mid
QString mid(int position, int n) const const
QStringList
QStringList::lastIndexOf
int lastIndexOf(QStringView str, int from) const const
Qt::WindowModal
WindowModal
QWidget
QWidget::setEnabled
void setEnabled(bool)
QWidget::setupUi
void setupUi(QWidget *widget)
QWidget::setWindowTitle
void setWindowTitle(const QString &)
ImportImageConfig
Definition: importimageconfig.h:22
PredefinedKeySet
Definition: predefinedsetmodel.h:25
PredefinedKeySetParams
Definition: importimageseqdialog.h:34
Generated on Thu May 8 2025 04:47:53 for Pencil2D by doxygen 1.9.6 based on revision 4513250b1d5b1a3676ec0e67b06b7a885ceaae39