File indexing completed on 2024-05-19 04:26:32

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_regenerate_frame_stroke_strategy.h"
0008 
0009 #include <KisRegion.h>
0010 #include "kis_image_interfaces.h"
0011 #include "kis_image_animation_interface.h"
0012 #include "kis_node.h"
0013 #include "kis_image.h"
0014 #include "krita_utils.h"
0015 
0016 #include "kis_full_refresh_walker.h"
0017 #include "kis_async_merger.h"
0018 #include "kis_projection_updates_filter.h"
0019 #include <KisLockFrameGenerationLock.h>
0020 
0021 
0022 struct KisRegenerateFrameStrokeStrategy::Private
0023 {
0024     Type type;
0025     int frameId;
0026     int previousFrameId;
0027     KisRegion dirtyRegion;
0028     KisImageAnimationInterface *interface;
0029     QStack<KisProjectionUpdatesFilterSP> prevUpdatesFilters;
0030     std::optional<KisLockFrameGenerationLock> frameGenerationLock;
0031 
0032     class Data : public KisStrokeJobData {
0033     public:
0034         Data(KisNodeSP _root, const QRect &_rect, const QRect &_cropRect)
0035             : KisStrokeJobData(CONCURRENT),
0036               root(_root), rect(_rect), cropRect(_cropRect)
0037             {}
0038 
0039         KisStrokeJobData* createLodClone(int levelOfDetail) override {
0040             Q_UNUSED(levelOfDetail);
0041             return new KisStrokeJobData(CONCURRENT);
0042         }
0043 
0044         KisNodeSP root;
0045         QRect rect;
0046         QRect cropRect;
0047     };
0048 
0049     void saveAndResetUpdatesFilter() {
0050         KisImageSP image = interface->image().toStrongRef();
0051         if (!image) {
0052             return;
0053         }
0054 
0055         while (KisProjectionUpdatesFilterCookie cookie = image->currentProjectionUpdatesFilter()) {
0056             prevUpdatesFilters.push(image->removeProjectionUpdatesFilter(cookie));
0057         }
0058     }
0059 
0060     void restoreUpdatesFilter() {
0061         KisImageSP image = interface->image().toStrongRef();
0062         if (!image) {
0063             return;
0064         }
0065 
0066         while (!prevUpdatesFilters.isEmpty()) {
0067             image->addProjectionUpdatesFilter(prevUpdatesFilters.pop());
0068         }
0069     }
0070 };
0071 
0072 KisRegenerateFrameStrokeStrategy::KisRegenerateFrameStrokeStrategy(int frameId,
0073                                                                    const KisRegion &dirtyRegion,
0074                                                                    bool isCancellable,
0075                                                                    KisImageAnimationInterface *interface,
0076                                                                    KisLockFrameGenerationLock &&frameGenerationLock)
0077     : KisSimpleStrokeStrategy(QLatin1String("regenerate_external_frame_stroke")),
0078       m_d(new Private)
0079 {
0080     m_d->type = EXTERNAL_FRAME;
0081 
0082     m_d->frameId = frameId;
0083     m_d->dirtyRegion = dirtyRegion;
0084     m_d->interface = interface;
0085     m_d->frameGenerationLock = std::move(frameGenerationLock);
0086 
0087     enableJob(JOB_INIT);
0088     enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
0089     enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER);
0090 
0091     enableJob(JOB_DOSTROKE);
0092 
0093     enableJob(JOB_SUSPEND);
0094     enableJob(JOB_RESUME);
0095 
0096     setRequestsOtherStrokesToEnd(false);
0097     setClearsRedoOnStart(false);
0098     setCanForgetAboutMe(isCancellable);
0099 }
0100 
0101 KisRegenerateFrameStrokeStrategy::KisRegenerateFrameStrokeStrategy(KisImageAnimationInterface *interface)
0102     : KisSimpleStrokeStrategy(QLatin1String("regenerate_current_frame_stroke"), kundo2_i18n("Render Animation")),
0103       m_d(new Private)
0104 {
0105     m_d->type = CURRENT_FRAME;
0106 
0107     m_d->frameId = 0;
0108     m_d->dirtyRegion = KisRegion();
0109     m_d->interface = interface;
0110 
0111     enableJob(JOB_INIT);
0112     enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
0113     enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER);
0114 
0115     enableJob(JOB_SUSPEND);
0116     enableJob(JOB_RESUME);
0117 
0118     // switching frames is a distinct user action, so it should
0119     // cancel the playback or any action easily
0120     setRequestsOtherStrokesToEnd(true);
0121     setClearsRedoOnStart(false);
0122 }
0123 
0124 KisRegenerateFrameStrokeStrategy::~KisRegenerateFrameStrokeStrategy()
0125 {
0126 }
0127 
0128 void KisRegenerateFrameStrokeStrategy::initStrokeCallback()
0129 {
0130     KisImageSP image = m_d->interface->image().toStrongRef();
0131     if (!image) {
0132         return;
0133     }
0134     if (m_d->type == EXTERNAL_FRAME) {
0135         m_d->saveAndResetUpdatesFilter();
0136         image->disableUIUpdates();
0137         m_d->interface->saveAndResetCurrentTime(m_d->frameId, &m_d->previousFrameId);
0138     } else if (m_d->type == CURRENT_FRAME) {
0139         m_d->interface->blockFrameInvalidation(true);
0140         m_d->interface->updatesFacade()->refreshGraphAsync(KisNodeSP());
0141     }
0142 }
0143 
0144 void KisRegenerateFrameStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
0145 {
0146     Private::Data *d = dynamic_cast<Private::Data*>(data);
0147     KIS_ASSERT(d);
0148     KIS_ASSERT(!m_d->dirtyRegion.isEmpty());
0149     KIS_ASSERT(m_d->type == EXTERNAL_FRAME);
0150 
0151     const bool skipNonRenderableNodes = m_d->type == EXTERNAL_FRAME;
0152     KisBaseRectsWalkerSP walker = new KisFullRefreshWalker(d->cropRect,
0153                                                            skipNonRenderableNodes ? KisFullRefreshWalker::SkipNonRenderableNodes : KisFullRefreshWalker::None);
0154     walker->collectRects(d->root, d->rect);
0155 
0156     KisAsyncMerger merger;
0157     merger.startMerge(*walker);
0158 }
0159 
0160 void KisRegenerateFrameStrokeStrategy::finishStrokeCallback()
0161 {
0162     KisImageSP image = m_d->interface->image().toStrongRef();
0163     if (!image) {
0164         return;
0165     }
0166 
0167 
0168     if (m_d->type == EXTERNAL_FRAME) {
0169         m_d->interface->notifyFrameReady();
0170         m_d->interface->restoreCurrentTime(&m_d->previousFrameId);
0171         image->enableUIUpdates();
0172         m_d->restoreUpdatesFilter();
0173     } else if (m_d->type == CURRENT_FRAME) {
0174         m_d->interface->notifyFrameRegenerated();
0175         m_d->interface->blockFrameInvalidation(false);
0176     }
0177 }
0178 
0179 void KisRegenerateFrameStrokeStrategy::cancelStrokeCallback()
0180 {
0181     KisImageSP image = m_d->interface->image().toStrongRef();
0182     if (!image) {
0183         return;
0184     }
0185     if (m_d->type == EXTERNAL_FRAME) {
0186         m_d->interface->notifyFrameCancelled();
0187         m_d->interface->restoreCurrentTime(&m_d->previousFrameId);
0188         image->enableUIUpdates();
0189         m_d->restoreUpdatesFilter();
0190     } else if (m_d->type == CURRENT_FRAME) {
0191         m_d->interface->blockFrameInvalidation(false);
0192     }
0193 }
0194 
0195 KisStrokeStrategy* KisRegenerateFrameStrokeStrategy::createLodClone(int levelOfDetail)
0196 {
0197     Q_UNUSED(levelOfDetail);
0198 
0199     /**
0200      * We need to regenerate animation frames on LodN level only if
0201      * we are processing current frame. Return dummy stroke otherwise
0202      */
0203     return m_d->type == CURRENT_FRAME ?
0204         new KisRegenerateFrameStrokeStrategy(m_d->interface) :
0205         new KisSimpleStrokeStrategy(QLatin1String("dumb-lodn-KisRegenerateFrameStrokeStrategy"));
0206 }
0207 
0208 void KisRegenerateFrameStrokeStrategy::suspendStrokeCallback()
0209 {
0210     KisImageSP image = m_d->interface->image().toStrongRef();
0211     if (!image) {
0212         return;
0213     }
0214     if (m_d->type == EXTERNAL_FRAME) {
0215         m_d->interface->restoreCurrentTime(&m_d->previousFrameId);
0216         image->enableUIUpdates();
0217         m_d->restoreUpdatesFilter();
0218     } else if (m_d->type == CURRENT_FRAME) {
0219         m_d->interface->blockFrameInvalidation(false);
0220     }
0221 }
0222 
0223 void KisRegenerateFrameStrokeStrategy::resumeStrokeCallback()
0224 {
0225     KisImageSP image = m_d->interface->image().toStrongRef();
0226     if (!image) {
0227         return;
0228     }
0229     if (m_d->type == EXTERNAL_FRAME) {
0230         m_d->saveAndResetUpdatesFilter();
0231         image->disableUIUpdates();
0232         m_d->interface->saveAndResetCurrentTime(m_d->frameId, &m_d->previousFrameId);
0233     } else if (m_d->type == CURRENT_FRAME) {
0234         m_d->interface->blockFrameInvalidation(true);
0235     }
0236 }
0237 
0238 QList<KisStrokeJobData*> KisRegenerateFrameStrokeStrategy::createJobsData(KisImageWSP _image)
0239 {
0240     using KritaUtils::splitRectIntoPatches;
0241     using KritaUtils::optimalPatchSize;
0242     KisImageSP image = _image;
0243 
0244     const QRect cropRect = image->bounds();
0245     QVector<QRect> rects = splitRectIntoPatches(image->bounds(), optimalPatchSize());
0246     QList<KisStrokeJobData*> jobsData;
0247 
0248     Q_FOREACH (const QRect &rc, rects) {
0249         jobsData << new Private::Data(image->root(), rc, cropRect);
0250     }
0251 
0252     return jobsData;
0253 }