1 /*
2  *  Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_regenerate_frame_stroke_strategy.h"
20 
21 #include <KisRegion.h>
22 #include "kis_image_interfaces.h"
23 #include "kis_image_animation_interface.h"
24 #include "kis_node.h"
25 #include "kis_image.h"
26 #include "krita_utils.h"
27 
28 #include "kis_full_refresh_walker.h"
29 #include "kis_async_merger.h"
30 #include "kis_projection_updates_filter.h"
31 
32 
33 struct KisRegenerateFrameStrokeStrategy::Private
34 {
35     Type type;
36     int frameId;
37     int previousFrameId;
38     KisRegion dirtyRegion;
39     KisImageAnimationInterface *interface;
40     QStack<KisProjectionUpdatesFilterSP> prevUpdatesFilters;
41 
42     class Data : public KisStrokeJobData {
43     public:
Data(KisNodeSP _root,const QRect & _rect,const QRect & _cropRect)44         Data(KisNodeSP _root, const QRect &_rect, const QRect &_cropRect)
45             : KisStrokeJobData(CONCURRENT),
46               root(_root), rect(_rect), cropRect(_cropRect)
47             {}
48 
createLodClone(int levelOfDetail)49         KisStrokeJobData* createLodClone(int levelOfDetail) override {
50             Q_UNUSED(levelOfDetail);
51             return new KisStrokeJobData(CONCURRENT);
52         }
53 
54         KisNodeSP root;
55         QRect rect;
56         QRect cropRect;
57     };
58 
saveAndResetUpdatesFilterKisRegenerateFrameStrokeStrategy::Private59     void saveAndResetUpdatesFilter() {
60         KisImageSP image = interface->image().toStrongRef();
61         if (!image) {
62             return;
63         }
64 
65         while (KisProjectionUpdatesFilterCookie cookie = image->currentProjectionUpdatesFilter()) {
66             prevUpdatesFilters.push(image->removeProjectionUpdatesFilter(cookie));
67         }
68     }
69 
restoreUpdatesFilterKisRegenerateFrameStrokeStrategy::Private70     void restoreUpdatesFilter() {
71         KisImageSP image = interface->image().toStrongRef();
72         if (!image) {
73             return;
74         }
75 
76         while (!prevUpdatesFilters.isEmpty()) {
77             image->addProjectionUpdatesFilter(prevUpdatesFilters.pop());
78         }
79     }
80 };
81 
KisRegenerateFrameStrokeStrategy(int frameId,const KisRegion & dirtyRegion,KisImageAnimationInterface * interface)82 KisRegenerateFrameStrokeStrategy::KisRegenerateFrameStrokeStrategy(int frameId,
83                                                                    const KisRegion &dirtyRegion,
84                                                                    KisImageAnimationInterface *interface)
85     : KisSimpleStrokeStrategy(QLatin1String("regenerate_external_frame_stroke")),
86       m_d(new Private)
87 {
88     m_d->type = EXTERNAL_FRAME;
89 
90     m_d->frameId = frameId;
91     m_d->dirtyRegion = dirtyRegion;
92     m_d->interface = interface;
93 
94     enableJob(JOB_INIT);
95     enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
96     enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER);
97 
98     enableJob(JOB_DOSTROKE);
99 
100     enableJob(JOB_SUSPEND);
101     enableJob(JOB_RESUME);
102 
103     setRequestsOtherStrokesToEnd(false);
104     setClearsRedoOnStart(false);
105     setCanForgetAboutMe(true);
106 }
107 
KisRegenerateFrameStrokeStrategy(KisImageAnimationInterface * interface)108 KisRegenerateFrameStrokeStrategy::KisRegenerateFrameStrokeStrategy(KisImageAnimationInterface *interface)
109     : KisSimpleStrokeStrategy(QLatin1String("regenerate_current_frame_stroke"), kundo2_i18n("Render Animation")),
110       m_d(new Private)
111 {
112     m_d->type = CURRENT_FRAME;
113 
114     m_d->frameId = 0;
115     m_d->dirtyRegion = KisRegion();
116     m_d->interface = interface;
117 
118     enableJob(JOB_INIT);
119     enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
120     enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER);
121 
122     enableJob(JOB_SUSPEND);
123     enableJob(JOB_RESUME);
124 
125     // switching frames is a distinct user action, so it should
126     // cancel the playback or any action easily
127     setRequestsOtherStrokesToEnd(true);
128     setClearsRedoOnStart(false);
129 }
130 
~KisRegenerateFrameStrokeStrategy()131 KisRegenerateFrameStrokeStrategy::~KisRegenerateFrameStrokeStrategy()
132 {
133 }
134 
initStrokeCallback()135 void KisRegenerateFrameStrokeStrategy::initStrokeCallback()
136 {
137     KisImageSP image = m_d->interface->image().toStrongRef();
138     if (!image) {
139         return;
140     }
141     if (m_d->type == EXTERNAL_FRAME) {
142         m_d->saveAndResetUpdatesFilter();
143         image->disableUIUpdates();
144         m_d->interface->saveAndResetCurrentTime(m_d->frameId, &m_d->previousFrameId);
145     } else if (m_d->type == CURRENT_FRAME) {
146         m_d->interface->blockFrameInvalidation(true);
147         m_d->interface->updatesFacade()->refreshGraphAsync(KisNodeSP());
148     }
149 }
150 
doStrokeCallback(KisStrokeJobData * data)151 void KisRegenerateFrameStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
152 {
153     Private::Data *d = dynamic_cast<Private::Data*>(data);
154     KIS_ASSERT(d);
155     KIS_ASSERT(!m_d->dirtyRegion.isEmpty());
156     KIS_ASSERT(m_d->type == EXTERNAL_FRAME);
157 
158     KisBaseRectsWalkerSP walker = new KisFullRefreshWalker(d->cropRect);
159     walker->collectRects(d->root, d->rect);
160 
161     KisAsyncMerger merger;
162     merger.startMerge(*walker);
163 }
164 
finishStrokeCallback()165 void KisRegenerateFrameStrokeStrategy::finishStrokeCallback()
166 {
167     KisImageSP image = m_d->interface->image().toStrongRef();
168     if (!image) {
169         return;
170     }
171     if (m_d->type == EXTERNAL_FRAME) {
172         m_d->interface->notifyFrameReady();
173         m_d->interface->restoreCurrentTime(&m_d->previousFrameId);
174         image->enableUIUpdates();
175         m_d->restoreUpdatesFilter();
176     } else if (m_d->type == CURRENT_FRAME) {
177         m_d->interface->blockFrameInvalidation(false);
178     }
179 }
180 
cancelStrokeCallback()181 void KisRegenerateFrameStrokeStrategy::cancelStrokeCallback()
182 {
183     KisImageSP image = m_d->interface->image().toStrongRef();
184     if (!image) {
185         return;
186     }
187     if (m_d->type == EXTERNAL_FRAME) {
188         m_d->interface->notifyFrameCancelled();
189         m_d->interface->restoreCurrentTime(&m_d->previousFrameId);
190         image->enableUIUpdates();
191         m_d->restoreUpdatesFilter();
192     } else if (m_d->type == CURRENT_FRAME) {
193         m_d->interface->blockFrameInvalidation(false);
194     }
195 }
196 
createLodClone(int levelOfDetail)197 KisStrokeStrategy* KisRegenerateFrameStrokeStrategy::createLodClone(int levelOfDetail)
198 {
199     Q_UNUSED(levelOfDetail);
200 
201     /**
202      * We need to regenerate animation frames on LodN level only if
203      * we are processing current frame. Return dummy stroke otherwise
204      */
205     return m_d->type == CURRENT_FRAME ?
206         new KisRegenerateFrameStrokeStrategy(m_d->interface) :
207         new KisSimpleStrokeStrategy(QLatin1String("dumb-lodn-KisRegenerateFrameStrokeStrategy"));
208 }
209 
suspendStrokeCallback()210 void KisRegenerateFrameStrokeStrategy::suspendStrokeCallback()
211 {
212     KisImageSP image = m_d->interface->image().toStrongRef();
213     if (!image) {
214         return;
215     }
216     if (m_d->type == EXTERNAL_FRAME) {
217         m_d->interface->restoreCurrentTime(&m_d->previousFrameId);
218         image->enableUIUpdates();
219         m_d->restoreUpdatesFilter();
220     } else if (m_d->type == CURRENT_FRAME) {
221         m_d->interface->blockFrameInvalidation(false);
222     }
223 }
224 
resumeStrokeCallback()225 void KisRegenerateFrameStrokeStrategy::resumeStrokeCallback()
226 {
227     KisImageSP image = m_d->interface->image().toStrongRef();
228     if (!image) {
229         return;
230     }
231     if (m_d->type == EXTERNAL_FRAME) {
232         m_d->saveAndResetUpdatesFilter();
233         image->disableUIUpdates();
234         m_d->interface->saveAndResetCurrentTime(m_d->frameId, &m_d->previousFrameId);
235     } else if (m_d->type == CURRENT_FRAME) {
236         m_d->interface->blockFrameInvalidation(true);
237     }
238 }
239 
createJobsData(KisImageWSP _image)240 QList<KisStrokeJobData*> KisRegenerateFrameStrokeStrategy::createJobsData(KisImageWSP _image)
241 {
242     using KritaUtils::splitRectIntoPatches;
243     using KritaUtils::optimalPatchSize;
244     KisImageSP image = _image;
245 
246     const QRect cropRect = image->bounds();
247     QVector<QRect> rects = splitRectIntoPatches(image->bounds(), optimalPatchSize());
248     QList<KisStrokeJobData*> jobsData;
249 
250     Q_FOREACH (const QRect &rc, rects) {
251         jobsData << new Private::Data(image->root(), rc, cropRect);
252     }
253 
254     return jobsData;
255 }
256