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 }