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