diff --git a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp index faeace2022..6e5054d161 100644 --- a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp +++ b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.cpp @@ -1,137 +1,147 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAsyncAnimationCacheRenderDialog.h" #include "KisAsyncAnimationCacheRenderer.h" #include "kis_animation_frame_cache.h" #include #include #include namespace { QList calcDirtyFramesList(KisAnimationFrameCacheSP cache, const KisTimeRange &playbackRange) { QList result; KisImageSP image = cache->image(); if (!image) return result; KisImageAnimationInterface *animation = image->animationInterface(); if (!animation->hasAnimation()) return result; if (playbackRange.isValid()) { KIS_ASSERT_RECOVER_RETURN_VALUE(!playbackRange.isInfinite(), result); // TODO: optimize check for fully-cached case for (int frame = playbackRange.start(); frame <= playbackRange.end(); frame++) { KisTimeRange stillFrameRange = KisTimeRange::infinite(0); KisTimeRange::calculateTimeRangeRecursive(image->root(), frame, stillFrameRange, true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(stillFrameRange.isValid(), result); if (cache->frameStatus(stillFrameRange.start()) == KisAnimationFrameCache::Uncached) { result.append(stillFrameRange.start()); } if (stillFrameRange.isInfinite()) { break; } else { frame = stillFrameRange.end(); } } } return result; } } int KisAsyncAnimationCacheRenderDialog::calcFirstDirtyFrame(KisAnimationFrameCacheSP cache, const KisTimeRange &playbackRange, const KisTimeRange &skipRange) { int result = -1; KisImageSP image = cache->image(); if (!image) return result; KisImageAnimationInterface *animation = image->animationInterface(); if (!animation->hasAnimation()) return result; if (playbackRange.isValid()) { KIS_ASSERT_RECOVER_RETURN_VALUE(!playbackRange.isInfinite(), result); // TODO: optimize check for fully-cached case for (int frame = playbackRange.start(); frame <= playbackRange.end(); frame++) { if (skipRange.contains(frame)) { if (skipRange.isInfinite()) { break; } else { frame = skipRange.end(); continue; } } if (cache->frameStatus(frame) != KisAnimationFrameCache::Cached) { result = frame; break; } } } return result; } struct KisAsyncAnimationCacheRenderDialog::Private { Private(KisAnimationFrameCacheSP _cache, const KisTimeRange &_range) : cache(_cache), range(_range) { } KisAnimationFrameCacheSP cache; KisTimeRange range; }; KisAsyncAnimationCacheRenderDialog::KisAsyncAnimationCacheRenderDialog(KisAnimationFrameCacheSP cache, const KisTimeRange &range, int busyWait) : KisAsyncAnimationRenderDialogBase(i18n("Regenerating cache..."), cache->image(), busyWait), m_d(new Private(cache, range)) { } KisAsyncAnimationCacheRenderDialog::~KisAsyncAnimationCacheRenderDialog() { } QList KisAsyncAnimationCacheRenderDialog::calcDirtyFrames() const { return calcDirtyFramesList(m_d->cache, m_d->range); } KisAsyncAnimationRendererBase *KisAsyncAnimationCacheRenderDialog::createRenderer(KisImageSP image) { Q_UNUSED(image); + return new KisAsyncAnimationCacheRenderer(); +} + +void KisAsyncAnimationCacheRenderDialog::initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame) +{ + Q_UNUSED(image); + Q_UNUSED(frame); + + KisAsyncAnimationCacheRenderer *cacheRenderer = + dynamic_cast(renderer); + + KIS_SAFE_ASSERT_RECOVER_RETURN(cacheRenderer); - KisAsyncAnimationCacheRenderer *renderer = new KisAsyncAnimationCacheRenderer(); - renderer->setFrameCache(m_d->cache); - return renderer; + cacheRenderer->setFrameCache(m_d->cache); } diff --git a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.h b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.h index c96eb85a8a..6c698b1c2e 100644 --- a/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.h +++ b/libs/ui/dialogs/KisAsyncAnimationCacheRenderDialog.h @@ -1,43 +1,45 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISASYNCANIMATIONCACHERENDERDIALOG_H #define KISASYNCANIMATIONCACHERENDERDIALOG_H #include "KisAsyncAnimationRenderDialogBase.h" #include "kis_types.h" class KisAsyncAnimationCacheRenderDialog : public KisAsyncAnimationRenderDialogBase { public: KisAsyncAnimationCacheRenderDialog(KisAnimationFrameCacheSP cache, const KisTimeRange &range, int busyWait = 200); ~KisAsyncAnimationCacheRenderDialog(); static int calcFirstDirtyFrame(KisAnimationFrameCacheSP cache, const KisTimeRange &playbackRange, const KisTimeRange &skipRange); protected: QList calcDirtyFrames() const override; KisAsyncAnimationRendererBase* createRenderer(KisImageSP image) override; + void initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, + KisImageSP image, int frame) override; private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONCACHERENDERDIALOG_H diff --git a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp index dadcea033a..c15d689842 100644 --- a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp +++ b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp @@ -1,174 +1,180 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAsyncAnimationFramesSaveDialog.h" #include #include #include #include "kis_properties_configuration.h" #include "KisMimeDatabase.h" #include #include #include struct KisAsyncAnimationFramesSaveDialog::Private { Private(KisImageSP _image, const KisTimeRange &_range, const QString &baseFilename, int _sequenceNumberingOffset, KisPropertiesConfigurationSP _exportConfiguration) : originalImage(_image), range(_range), sequenceNumberingOffset(_sequenceNumberingOffset), exportConfiguration(_exportConfiguration) { int baseLength = baseFilename.lastIndexOf("."); if (baseLength > -1) { filenamePrefix = baseFilename.left(baseLength); filenameSuffix = baseFilename.right(baseFilename.length() - baseLength); } else { filenamePrefix = baseFilename; } outputMimeType = KisMimeDatabase::mimeTypeForFile(baseFilename, false).toLatin1(); } KisImageSP originalImage; KisTimeRange range; QString filenamePrefix; QString filenameSuffix; QByteArray outputMimeType; int sequenceNumberingOffset; KisPropertiesConfigurationSP exportConfiguration; }; KisAsyncAnimationFramesSaveDialog::KisAsyncAnimationFramesSaveDialog(KisImageSP originalImage, const KisTimeRange &range, const QString &baseFilename, int sequenceNumberingOffset, KisPropertiesConfigurationSP exportConfiguration) : KisAsyncAnimationRenderDialogBase("Saving frames...", originalImage, 0), m_d(new Private(originalImage, range, baseFilename, sequenceNumberingOffset, exportConfiguration)) { } KisAsyncAnimationFramesSaveDialog::~KisAsyncAnimationFramesSaveDialog() { } KisAsyncAnimationRenderDialogBase::Result KisAsyncAnimationFramesSaveDialog::regenerateRange(KisViewManager *viewManager) { QFileInfo info(savedFilesMaskWildcard()); QDir dir(info.absolutePath()); if (!dir.exists()) { dir.mkpath(info.absolutePath()); } KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); QStringList filesList = dir.entryList({ info.fileName() }); if (!filesList.isEmpty()) { if (batchMode()) { return RenderFailed; } QStringList truncatedList = filesList; while (truncatedList.size() > 3) { truncatedList.takeLast(); } QString exampleFiles = truncatedList.join(", "); if (truncatedList.size() != filesList.size()) { exampleFiles += QString(", ..."); } QMessageBox::StandardButton result = QMessageBox::warning(0, i18n("Delete old frames?"), i18n("Frames with the same naming " "scheme exist in the destination " "directory. They are going to be " "deleted, continue?\n\n" "Directory: %1\n" "Files: %2", info.absolutePath(), exampleFiles), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (result == QMessageBox::Yes) { Q_FOREACH (const QString &file, filesList) { if (!dir.remove(file)) { QMessageBox::critical(0, i18n("Failed to delete"), i18n("Failed to delete an old frame file:\n\n" "%1\n\n" "Rendering cancelled.", dir.absoluteFilePath(file))); return RenderFailed; } } } else { return RenderCancelled; } } return KisAsyncAnimationRenderDialogBase::regenerateRange(viewManager); } QList KisAsyncAnimationFramesSaveDialog::calcDirtyFrames() const { // TODO: optimize! QList result; for (int i = m_d->range.start(); i <= m_d->range.end(); i++) { result.append(i); } return result; } KisAsyncAnimationRendererBase *KisAsyncAnimationFramesSaveDialog::createRenderer(KisImageSP image) { return new KisAsyncAnimationFramesSavingRenderer(image, m_d->filenamePrefix, m_d->filenameSuffix, m_d->outputMimeType, m_d->range, m_d->sequenceNumberingOffset, m_d->exportConfiguration); } +void KisAsyncAnimationFramesSaveDialog::initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame) +{ + Q_UNUSED(renderer); + Q_UNUSED(image); + Q_UNUSED(frame); +} QString KisAsyncAnimationFramesSaveDialog::savedFilesMask() const { return m_d->filenamePrefix + "%04d" + m_d->filenameSuffix; } QString KisAsyncAnimationFramesSaveDialog::savedFilesMaskWildcard() const { return m_d->filenamePrefix + "????" + m_d->filenameSuffix; } diff --git a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.h b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.h index dca29ab37c..02b65c6cd2 100644 --- a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.h +++ b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.h @@ -1,51 +1,53 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISASYNCANIMATIONFRAMESSAVEDIALOG_H #define KISASYNCANIMATIONFRAMESSAVEDIALOG_H #include "KisAsyncAnimationRenderDialogBase.h" #include "kis_types.h" class KRITAUI_EXPORT KisAsyncAnimationFramesSaveDialog : public KisAsyncAnimationRenderDialogBase { public: KisAsyncAnimationFramesSaveDialog(KisImageSP image, const KisTimeRange &range, const QString &baseFilename, int sequenceNumberingOffset, KisPropertiesConfigurationSP exportConfiguration); ~KisAsyncAnimationFramesSaveDialog(); Result regenerateRange(KisViewManager *viewManager); QString savedFilesMask() const; QString savedFilesMaskWildcard() const; protected: QList calcDirtyFrames() const override; KisAsyncAnimationRendererBase* createRenderer(KisImageSP image) override; + void initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, + KisImageSP image, int frame) override; private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONFRAMESSAVEDIALOG_H diff --git a/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp b/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp index d6db78bb17..1424441c1d 100644 --- a/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp +++ b/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.cpp @@ -1,307 +1,309 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAsyncAnimationRenderDialogBase.h" #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "KisAsyncAnimationRendererBase.h" #include "kis_time_range.h" #include "kis_image.h" #include "kis_image_config.h" #include "kis_memory_statistics_server.h" #include #include namespace { struct RendererPair { std::unique_ptr renderer; KisImageSP image; RendererPair() {} RendererPair(KisAsyncAnimationRendererBase *_renderer, KisImageSP _image) : renderer(_renderer), image(_image) { } RendererPair(RendererPair &&rhs) : renderer(std::move(rhs.renderer)), image(rhs.image) { } }; int calculateNumberMemoryAllowedClones(KisImageSP image) { KisMemoryStatisticsServer::Statistics stats = KisMemoryStatisticsServer::instance() ->fetchMemoryStatistics(image); const qint64 allowedMemory = 0.8 * stats.tilesHardLimit - stats.realMemorySize; const qint64 cloneSize = stats.projectionsSize; return allowedMemory > 0 ? allowedMemory / cloneSize : 0; } } struct KisAsyncAnimationRenderDialogBase::Private { Private(const QString &_actionTitle, KisImageSP _image, int _busyWait) : actionTitle(_actionTitle), image(_image), busyWait(_busyWait) { } QString actionTitle; KisImageSP image; int busyWait; bool isBatchMode = false; std::vector asyncRenderers; bool memoryLimitReached = false; QElapsedTimer processingTime; QScopedPointer progressDialog; QEventLoop waitLoop; QList stillDirtyFrames; QList framesInProgress; int dirtyFramesCount = 0; Result result = RenderComplete; int numDirtyFramesLeft() const { return stillDirtyFrames.size() + framesInProgress.size(); } }; KisAsyncAnimationRenderDialogBase::KisAsyncAnimationRenderDialogBase(const QString &actionTitle, KisImageSP image, int busyWait) : m_d(new Private(actionTitle, image, busyWait)) { } KisAsyncAnimationRenderDialogBase::~KisAsyncAnimationRenderDialogBase() { } KisAsyncAnimationRenderDialogBase::Result KisAsyncAnimationRenderDialogBase::regenerateRange(KisViewManager *viewManager) { m_d->stillDirtyFrames = calcDirtyFrames(); m_d->framesInProgress.clear(); m_d->result = RenderComplete; m_d->dirtyFramesCount = m_d->stillDirtyFrames.size(); if (!m_d->isBatchMode) { QWidget *parentWidget = viewManager ? viewManager->mainWindow() : 0; m_d->progressDialog.reset(new QProgressDialog(m_d->actionTitle, i18n("Cancel"), 0, 0, parentWidget)); m_d->progressDialog->setWindowModality(Qt::ApplicationModal); m_d->progressDialog->setMinimum(0); m_d->progressDialog->setMaximum(m_d->dirtyFramesCount); m_d->progressDialog->setMinimumDuration(m_d->busyWait); connect(m_d->progressDialog.data(), SIGNAL(canceled()), SLOT(slotCancelRegeneration())); } if (m_d->dirtyFramesCount <= 0) return m_d->result; m_d->processingTime.start(); KisImageConfig cfg; const int maxThreads = cfg.maxNumberOfThreads(); const int numAllowedWorker = 1 + calculateNumberMemoryAllowedClones(m_d->image); const int proposedNumWorkers = qMin(m_d->dirtyFramesCount, cfg.frameRenderingClones()); const int numWorkers = qMin(proposedNumWorkers, numAllowedWorker); const int numThreadsPerWorker = qMax(1, qCeil(qreal(maxThreads) / numWorkers)); m_d->memoryLimitReached = numWorkers < proposedNumWorkers; const int oldWorkingThreadsLimit = m_d->image->workingThreadsLimit(); ENTER_FUNCTION() << ppVar(numWorkers) << ppVar(numThreadsPerWorker); for (int i = 0; i < numWorkers; i++) { // reuse the image for one of the workers KisImageSP image = i == numWorkers - 1 ? m_d->image : m_d->image->clone(true); image->setWorkingThreadsLimit(numThreadsPerWorker); KisAsyncAnimationRendererBase *renderer = createRenderer(image); connect(renderer, SIGNAL(sigFrameCompleted(int)), SLOT(slotFrameCompleted(int))); connect(renderer, SIGNAL(sigFrameCancelled(int)), SLOT(slotFrameCancelled(int))); m_d->asyncRenderers.push_back(RendererPair(renderer, image)); } ENTER_FUNCTION() << "Copying done in" << m_d->processingTime.elapsed(); tryInitiateFrameRegeneration(); updateProgressLabel(); if (m_d->numDirtyFramesLeft() > 0) { m_d->waitLoop.exec(); } ENTER_FUNCTION() << "Full regeneration done in" << m_d->processingTime.elapsed(); for (auto &pair : m_d->asyncRenderers) { KIS_SAFE_ASSERT_RECOVER_NOOP(!pair.renderer->isActive()); if (viewManager) { viewManager->blockUntilOperationsFinishedForced(pair.image); } else { pair.image->barrierLock(true); pair.image->unlock(); } } m_d->asyncRenderers.clear(); if (viewManager) { viewManager->blockUntilOperationsFinishedForced(m_d->image); } else { m_d->image->barrierLock(true); m_d->image->unlock(); } m_d->image->setWorkingThreadsLimit(oldWorkingThreadsLimit); m_d->progressDialog.reset(); return m_d->result; } void KisAsyncAnimationRenderDialogBase::slotFrameCompleted(int frame) { Q_UNUSED(frame); m_d->framesInProgress.removeOne(frame); tryInitiateFrameRegeneration(); updateProgressLabel(); } void KisAsyncAnimationRenderDialogBase::slotFrameCancelled(int frame) { Q_UNUSED(frame); cancelProcessingImpl(false); } void KisAsyncAnimationRenderDialogBase::slotCancelRegeneration() { cancelProcessingImpl(true); } void KisAsyncAnimationRenderDialogBase::cancelProcessingImpl(bool isUserCancelled) { for (auto &pair : m_d->asyncRenderers) { if (pair.renderer->isActive()) { pair.renderer->cancelCurrentFrameRendering(); } KIS_SAFE_ASSERT_RECOVER_NOOP(!pair.renderer->isActive()); } m_d->stillDirtyFrames.clear(); m_d->framesInProgress.clear(); m_d->result = isUserCancelled ? RenderCancelled : RenderFailed; updateProgressLabel(); } void KisAsyncAnimationRenderDialogBase::tryInitiateFrameRegeneration() { bool hadWorkOnPreviousCycle = false; while (!m_d->stillDirtyFrames.isEmpty()) { for (auto &pair : m_d->asyncRenderers) { if (!pair.renderer->isActive()) { const int currentDirtyFrame = m_d->stillDirtyFrames.takeFirst(); + + initializeRendererForFrame(pair.renderer.get(), pair.image, currentDirtyFrame); pair.renderer->startFrameRegeneration(pair.image, currentDirtyFrame); hadWorkOnPreviousCycle = true; m_d->framesInProgress.append(currentDirtyFrame); break; } } if (!hadWorkOnPreviousCycle) break; hadWorkOnPreviousCycle = false; } } void KisAsyncAnimationRenderDialogBase::updateProgressLabel() { const int processedFramesCount = m_d->dirtyFramesCount - m_d->numDirtyFramesLeft(); const qint64 elapsedMSec = m_d->processingTime.elapsed(); const qint64 estimatedMSec = !processedFramesCount ? 0 : elapsedMSec * m_d->dirtyFramesCount / processedFramesCount; const QTime elapsedTime = QTime::fromMSecsSinceStartOfDay(elapsedMSec); const QTime estimatedTime = QTime::fromMSecsSinceStartOfDay(estimatedMSec); const QString timeFormat = estimatedTime.hour() > 0 ? "HH:mm:ss" : "mm:ss"; const QString elapsedTimeString = elapsedTime.toString(timeFormat); const QString estimatedTimeString = estimatedTime.toString(timeFormat); const QString memoryLimitMessage( i18n("\n\nMemory limit is reached!\nThe number of clones is limited to %1\n\n", m_d->asyncRenderers.size())); const QString progressLabel(i18n("%1\n\nElapsed: %2\nEstimated: %3\n\n%4", m_d->actionTitle, elapsedTimeString, estimatedTimeString, m_d->memoryLimitReached ? memoryLimitMessage : QString())); if (m_d->progressDialog) { m_d->progressDialog->setLabelText(progressLabel); m_d->progressDialog->setValue(processedFramesCount); } if (!m_d->numDirtyFramesLeft()) { m_d->waitLoop.quit(); } } void KisAsyncAnimationRenderDialogBase::setBatchMode(bool value) { m_d->isBatchMode = value; } bool KisAsyncAnimationRenderDialogBase::batchMode() const { return m_d->isBatchMode; } diff --git a/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.h b/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.h index 804cc7d787..029e5e97a6 100644 --- a/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.h +++ b/libs/ui/dialogs/KisAsyncAnimationRenderDialogBase.h @@ -1,137 +1,140 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISASYNCANIMATIONRENDERDIALOGBASE_H #define KISASYNCANIMATIONRENDERDIALOGBASE_H #include #include "kis_types.h" #include "kritaui_export.h" class KisTimeRange; class KisAsyncAnimationRendererBase; class KisViewManager; /** * @brief KisAsyncAnimationRenderDialogBase is a special class for rendering multiple * frames of the image and putting them somewhere (saving into a file or just * pushing into an openGL cache) * * The class handles a lot of boilerplate code for you and optimizes regeneration of * the frames using multithreading, according to the user's settings. The responsibilities * of the class are the following: * * Rendering itself: * - fetch the list of dirtly frames using calcDirtyFrames() * - create some clones of the image according to the user's settings * to facilitate multithreaded rendering and processing of the frames * - if the user doesn't have anough RAM, the clones will not be created * (the memory overhead is calculated using "projections" metric of the * statistics server). * - feed the images/threads with dirty frames until the all the frames * are done * * Progress reporting: * - if batchMode() is false, the user will see a progress dialog showing * the current progress with estimate about total processing timer * - the user can also cancel the regeneration by pressing Cancel button * * Usage Details: * - one should implement two methods to make the rendering work: * - calcDirtyFrames() * - createRenderer(KisImageSP image) * - these methids will be called on the start of the rendering */ class KRITAUI_EXPORT KisAsyncAnimationRenderDialogBase : public QObject { Q_OBJECT public: enum Result { RenderComplete, RenderCancelled, RenderFailed }; public: /** * @brief construct and initialize the dialog * @param actionTitle the first line of the status reports that the user sees in the dialog * @param image the image that will be as a source of frames. Make sure the image is *not* * locked by the time regenerateRange() is called * @param busyWait the dialog will not show for the specified time (in milliseconds) */ KisAsyncAnimationRenderDialogBase(const QString &actionTitle, KisImageSP image, int busyWait = 200); virtual ~KisAsyncAnimationRenderDialogBase(); /** * @brief start generation of frames and (if not in batch mode) show the dialog * * The link to view manager is used to barrier lock with visual feedback in the * end of the operation */ virtual Result regenerateRange(KisViewManager *viewManager); /** * @brief setting batch mode to true will prevent any dialogs or message boxes from * showing on screen. Please take it into account that using batch mode prevents * some potentially dangerous recovery execution paths (e.g. delete the existing * frames in the destination folder). In such case the rendering will be stopped with * RenderFailed result. */ void setBatchMode(bool value); /** * @see setBatchMode */ bool batchMode() const; private Q_SLOTS: void slotFrameCompleted(int frame); void slotFrameCancelled(int frame); void slotCancelRegeneration(); private: void tryInitiateFrameRegeneration(); void updateProgressLabel(); void cancelProcessingImpl(bool isUserCancelled); protected: /** * @brief returns a list of frames that should be regenerated by the dialog * * Called by the dialog in the very beginning of regenerateRange() */ virtual QList calcDirtyFrames() const = 0; /** * @brief create a renderer object linked to \p image * * Renderer are special objects that connect to the individual image signals, * react on them and fetch the final frames data * * @see KisAsyncAnimationRendererBase */ virtual KisAsyncAnimationRendererBase* createRenderer(KisImageSP image) = 0; + virtual void initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, + KisImageSP image, int frame) = 0; + private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONRENDERDIALOGBASE_H