File indexing completed on 2025-03-09 04:06:53

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisAsyncAnimationFramesSaveDialog.h"
0008 
0009 #include <kis_image.h>
0010 #include <kis_time_span.h>
0011 
0012 #include <KisAsyncAnimationFramesSavingRenderer.h>
0013 #include "kis_properties_configuration.h"
0014 
0015 #include "KisMimeDatabase.h"
0016 
0017 #include <QFileInfo>
0018 #include <QDir>
0019 #include <QMessageBox>
0020 #include <QApplication>
0021 
0022 struct KisAsyncAnimationFramesSaveDialog::Private {
0023     Private(KisImageSP _image,
0024             const KisTimeSpan &_range,
0025             const QString &baseFilename,
0026             int _sequenceNumberingOffset,
0027             bool _onlyNeedsUniqueFrames,
0028             KisPropertiesConfigurationSP _exportConfiguration)
0029         : originalImage(_image),
0030           range(_range),
0031           onlyNeedsUniqueFrames(_onlyNeedsUniqueFrames),
0032           sequenceNumberingOffset(_sequenceNumberingOffset),
0033           exportConfiguration(_exportConfiguration)
0034     {
0035         int baseLength = baseFilename.lastIndexOf(".");
0036         if (baseLength > -1) {
0037             filenamePrefix = baseFilename.left(baseLength);
0038             filenameSuffix = baseFilename.right(baseFilename.length() - baseLength);
0039         } else {
0040             filenamePrefix = baseFilename;
0041         }
0042 
0043         outputMimeType = KisMimeDatabase::mimeTypeForFile(baseFilename, false).toLatin1();
0044     }
0045 
0046     KisImageSP originalImage;
0047     KisTimeSpan range;
0048 
0049     QString filenamePrefix;
0050     QString filenameSuffix;
0051     QByteArray outputMimeType;
0052     bool onlyNeedsUniqueFrames;
0053 
0054     int sequenceNumberingOffset;
0055     KisPropertiesConfigurationSP exportConfiguration;
0056 };
0057 
0058 KisAsyncAnimationFramesSaveDialog::KisAsyncAnimationFramesSaveDialog(KisImageSP originalImage,
0059                                                                      const KisTimeSpan &range,
0060                                                                      const QString &baseFilename,
0061                                                                      int startNumberingAt,
0062                                                                      bool onlyNeedsUniqueFrames,
0063                                                                      KisPropertiesConfigurationSP exportConfiguration)
0064     : KisAsyncAnimationRenderDialogBase(i18n("Saving frames..."), originalImage, 0),
0065       m_d(new Private(originalImage, range, baseFilename, qMax(startNumberingAt - range.start(), range.start() * -1), onlyNeedsUniqueFrames, exportConfiguration))
0066 {
0067 
0068 
0069 }
0070 
0071 KisAsyncAnimationFramesSaveDialog::~KisAsyncAnimationFramesSaveDialog()
0072 {
0073 }
0074 
0075 KisAsyncAnimationRenderDialogBase::Result KisAsyncAnimationFramesSaveDialog::regenerateRange(KisViewManager *viewManager)
0076 {
0077     QFileInfo info(savedFilesMaskWildcard());
0078 
0079     QDir dir(info.absolutePath());
0080 
0081     if (!dir.exists()) {
0082         dir.mkpath(info.absolutePath());
0083     }
0084     KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists());
0085 
0086     QStringList filesList = dir.entryList({ info.fileName() });
0087 
0088     if (!filesList.isEmpty()) {
0089         if (batchMode()) {
0090             return RenderFailed;
0091         }
0092 
0093         QStringList truncatedList = filesList;
0094 
0095         while (truncatedList.size() > 3) {
0096             truncatedList.takeLast();
0097         }
0098 
0099         QString exampleFiles = truncatedList.join(", ");
0100         if (truncatedList.size() != filesList.size()) {
0101             exampleFiles += QString(", ...");
0102         }
0103 
0104         QMessageBox::StandardButton result =
0105                 QMessageBox::warning(qApp->activeWindow(),
0106                                      i18n("Delete old frames?"),
0107                                      i18n("Frames with the same naming "
0108                                           "scheme exist in the destination "
0109                                           "directory. They are going to be "
0110                                           "deleted, continue?\n\n"
0111                                           "Directory: %1\n"
0112                                           "Files: %2",
0113                                           info.absolutePath(), exampleFiles),
0114                                      QMessageBox::Yes | QMessageBox::No,
0115                                      QMessageBox::No);
0116 
0117         if (result == QMessageBox::Yes) {
0118             Q_FOREACH (const QString &file, filesList) {
0119                 if (!dir.remove(file)) {
0120                     QMessageBox::critical(qApp->activeWindow(),
0121                                           i18n("Failed to delete"),
0122                                           i18n("Failed to delete an old frame file:\n\n"
0123                                                "%1\n\n"
0124                                                "Rendering cancelled.", dir.absoluteFilePath(file)));
0125 
0126                     return RenderFailed;
0127                 }
0128             }
0129         } else {
0130             return RenderCancelled;
0131         }
0132     }
0133 
0134     KisAsyncAnimationRenderDialogBase::Result renderingResult = KisAsyncAnimationRenderDialogBase::regenerateRange(viewManager);
0135 
0136     filesList = savedFiles();
0137 
0138     // If we cancel rendering or fail rendering process, lets clean up any files that may have been created
0139     // to keep the next render from having artifacts.
0140     if (renderingResult != RenderComplete) {
0141         Q_FOREACH (const QString &file, filesList) {
0142             if (dir.exists(file)) {
0143                 (void)dir.remove(file);
0144             }
0145         }
0146     }
0147 
0148     return renderingResult;
0149 }
0150 
0151 QList<int> KisAsyncAnimationFramesSaveDialog::calcDirtyFrames() const
0152 {
0153     QList<int> result;
0154     for (int frame = m_d->range.start(); frame <= m_d->range.end(); frame++) {
0155         KisTimeSpan heldFrameTimeRange = KisTimeSpan::calculateIdenticalFramesRecursive(m_d->originalImage->root(), frame);
0156 
0157         if (!m_d->onlyNeedsUniqueFrames) {
0158             // Clamp holds that begin before the rendered range onto it
0159             heldFrameTimeRange &= m_d->range;
0160         }
0161 
0162         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(heldFrameTimeRange.isValid(), result);
0163 
0164         result.append(heldFrameTimeRange.start());
0165 
0166         if (heldFrameTimeRange.isInfinite()) {
0167             break;
0168         } else {
0169             frame = heldFrameTimeRange.end();
0170         }
0171     }
0172     return result;
0173 }
0174 
0175 KisAsyncAnimationRendererBase *KisAsyncAnimationFramesSaveDialog::createRenderer(KisImageSP image)
0176 {
0177     return new KisAsyncAnimationFramesSavingRenderer(image,
0178                                                      m_d->filenamePrefix,
0179                                                      m_d->filenameSuffix,
0180                                                      m_d->outputMimeType,
0181                                                      m_d->range,
0182                                                      m_d->sequenceNumberingOffset,
0183                                                      m_d->onlyNeedsUniqueFrames,
0184                                                      m_d->exportConfiguration);
0185 }
0186 
0187 void KisAsyncAnimationFramesSaveDialog::initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame)
0188 {
0189     Q_UNUSED(renderer);
0190     Q_UNUSED(image);
0191     Q_UNUSED(frame);
0192 }
0193 
0194 QString KisAsyncAnimationFramesSaveDialog::savedFilesMask() const
0195 {
0196     return m_d->filenamePrefix + "%04d" + m_d->filenameSuffix;
0197 }
0198 
0199 QString KisAsyncAnimationFramesSaveDialog::savedFilesMaskWildcard() const
0200 {
0201     return m_d->filenamePrefix + "????" + m_d->filenameSuffix;
0202 }
0203 
0204 QStringList KisAsyncAnimationFramesSaveDialog::savedFiles() const
0205 {
0206     QStringList files;
0207 
0208     for (int i = m_d->range.start(); i <= m_d->range.end(); i++) {
0209         const int num = m_d->sequenceNumberingOffset + i;
0210         QString name = QString("%1").arg(num, 4, 10, QChar('0'));
0211         name = m_d->filenamePrefix + name + m_d->filenameSuffix;
0212         files.append(QFileInfo(name).fileName());
0213     }
0214 
0215     return files;
0216 }
0217 
0218 QStringList KisAsyncAnimationFramesSaveDialog::savedUniqueFiles() const
0219 {
0220     QStringList files;
0221 
0222     const QList<int> frames = calcDirtyFrames();
0223 
0224     Q_FOREACH (int frame, frames) {
0225         const int num = m_d->sequenceNumberingOffset + frame;
0226         QString name = QString("%1").arg(num, 4, 10, QChar('0'));
0227         name = m_d->filenamePrefix + name + m_d->filenameSuffix;
0228         files.append(QFileInfo(name).fileName());
0229     }
0230 
0231     return files;
0232 }
0233 
0234 QList<int> KisAsyncAnimationFramesSaveDialog::getUniqueFrames() const
0235 {
0236     return this->calcDirtyFrames();
0237 }