1 // Aseprite
2 // Copyright (C) 2001-2018 David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10
11 #include "app/util/expand_cel_canvas.h"
12
13 #include "app/app.h"
14 #include "app/cmd/add_cel.h"
15 #include "app/cmd/clear_cel.h"
16 #include "app/cmd/copy_region.h"
17 #include "app/cmd/patch_cel.h"
18 #include "app/context.h"
19 #include "app/doc.h"
20 #include "app/site.h"
21 #include "app/transaction.h"
22 #include "app/util/range_utils.h"
23 #include "base/unique_ptr.h"
24 #include "doc/algorithm/shrink_bounds.h"
25 #include "doc/cel.h"
26 #include "doc/image.h"
27 #include "doc/layer.h"
28 #include "doc/primitives.h"
29 #include "doc/sprite.h"
30
31 namespace {
32
33 // We cannot have two ExpandCelCanvas instances at the same time
34 // (because we share ImageBuffers between them).
35 static app::ExpandCelCanvas* singleton = nullptr;
36
37 static doc::ImageBufferPtr src_buffer;
38 static doc::ImageBufferPtr dst_buffer;
39
destroy_buffers()40 static void destroy_buffers()
41 {
42 src_buffer.reset(NULL);
43 dst_buffer.reset(NULL);
44 }
45
create_buffers()46 static void create_buffers()
47 {
48 if (!src_buffer) {
49 app::App::instance()->Exit.connect(&destroy_buffers);
50
51 src_buffer.reset(new doc::ImageBuffer(1));
52 dst_buffer.reset(new doc::ImageBuffer(1));
53 }
54 }
55
56 }
57
58 namespace app {
59
ExpandCelCanvas(Site site,Layer * layer,TiledMode tiledMode,Transaction & transaction,Flags flags)60 ExpandCelCanvas::ExpandCelCanvas(
61 Site site, Layer* layer,
62 TiledMode tiledMode, Transaction& transaction, Flags flags)
63 : m_document(site.document())
64 , m_sprite(site.sprite())
65 , m_layer(layer)
66 , m_frame(site.frame())
67 , m_cel(NULL)
68 , m_celImage(NULL)
69 , m_celCreated(false)
70 , m_flags(flags)
71 , m_srcImage(NULL)
72 , m_dstImage(NULL)
73 , m_closed(false)
74 , m_committed(false)
75 , m_transaction(transaction)
76 , m_canCompareSrcVsDst((m_flags & NeedsSource) == NeedsSource)
77 {
78 ASSERT(!singleton);
79 singleton = this;
80
81 create_buffers();
82
83 if (m_layer && m_layer->isImage()) {
84 m_cel = m_layer->cel(site.frame());
85 if (m_cel)
86 m_celImage = m_cel->imageRef();
87 }
88
89 // Create a new cel
90 if (!m_cel) {
91 m_celCreated = true;
92 m_cel = new Cel(site.frame(), ImageRef(NULL));
93 }
94
95 m_origCelPos = m_cel->position();
96
97 // Region to draw
98 gfx::Rect celBounds(
99 m_cel->x(),
100 m_cel->y(),
101 m_celImage ? m_celImage->width(): m_sprite->width(),
102 m_celImage ? m_celImage->height(): m_sprite->height());
103
104 gfx::Rect spriteBounds(0, 0,
105 m_sprite->width(),
106 m_sprite->height());
107
108 if (tiledMode == TiledMode::NONE) { // Non-tiled
109 m_bounds = celBounds.createUnion(spriteBounds);
110 }
111 else { // Tiled
112 m_bounds = spriteBounds;
113 }
114
115 // We have to adjust the cel position to match the m_dstImage
116 // position (the new m_dstImage will be used in RenderEngine to
117 // draw this cel).
118 m_cel->setPosition(m_bounds.x, m_bounds.y);
119
120 if (m_celCreated) {
121 getDestCanvas();
122 m_cel->data()->setImage(m_dstImage);
123
124 if (m_layer && m_layer->isImage())
125 static_cast<LayerImage*>(m_layer)->addCel(m_cel);
126 }
127 }
128
~ExpandCelCanvas()129 ExpandCelCanvas::~ExpandCelCanvas()
130 {
131 ASSERT(singleton == this);
132 singleton = nullptr;
133
134 try {
135 if (!m_committed && !m_closed)
136 rollback();
137 }
138 catch (...) {
139 // Do nothing
140 }
141 }
142
commit()143 void ExpandCelCanvas::commit()
144 {
145 ASSERT(!m_closed);
146 ASSERT(!m_committed);
147
148 if (!m_layer) {
149 m_committed = true;
150 return;
151 }
152
153 // Was the cel created in the start of the tool-loop?
154 if (m_celCreated) {
155 ASSERT(m_cel);
156 ASSERT(!m_celImage);
157
158 // Validate the whole m_dstImage (invalid areas are cleared, as we
159 // don't have a m_celImage)
160 validateDestCanvas(gfx::Region(m_bounds));
161
162 // We can temporary remove the cel.
163 if (m_layer->isImage()) {
164 static_cast<LayerImage*>(m_layer)->removeCel(m_cel);
165
166 // Add a copy of m_dstImage in the sprite's image stock
167 gfx::Rect trimBounds = getTrimDstImageBounds();
168 if (!trimBounds.isEmpty()) {
169 ImageRef newImage(trimDstImage(trimBounds));
170 ASSERT(newImage);
171
172 m_cel->data()->setImage(newImage);
173 m_cel->setPosition(m_cel->position() + trimBounds.origin());
174
175 // And finally we add the cel again in the layer.
176 m_transaction.execute(new cmd::AddCel(m_layer, m_cel));
177 }
178 }
179 // We are selecting inside a layer group...
180 else {
181 // Just delete the created layer
182 delete m_cel;
183 m_cel = nullptr;
184 }
185 }
186 else if (m_celImage) {
187 // Restore cel position to its original position
188 m_cel->setPosition(m_origCelPos);
189
190 ASSERT(m_cel->image() == m_celImage.get());
191
192 gfx::Region* regionToPatch = &m_validDstRegion;
193 gfx::Region reduced;
194
195 if (m_canCompareSrcVsDst) {
196 ASSERT(gfx::Region().createSubtraction(m_validDstRegion, m_validSrcRegion).isEmpty());
197
198 for (gfx::Rect rc : m_validDstRegion) {
199 if (algorithm::shrink_bounds2(getSourceCanvas(),
200 getDestCanvas(), rc, rc)) {
201 reduced |= gfx::Region(rc);
202 }
203 }
204
205 regionToPatch = &reduced;
206 }
207
208 if (m_layer->isBackground()) {
209 m_transaction.execute(
210 new cmd::CopyRegion(
211 m_cel->image(),
212 m_dstImage.get(),
213 *regionToPatch,
214 m_bounds.origin()));
215 }
216 else {
217 m_transaction.execute(
218 new cmd::PatchCel(
219 m_cel,
220 m_dstImage.get(),
221 *regionToPatch,
222 m_bounds.origin()));
223 }
224 }
225 else {
226 ASSERT(false);
227 }
228
229 m_committed = true;
230 }
231
rollback()232 void ExpandCelCanvas::rollback()
233 {
234 ASSERT(!m_closed);
235 ASSERT(!m_committed);
236
237 // Here we destroy the temporary 'cel' created and restore all as it was before
238 m_cel->setPosition(m_origCelPos);
239
240 if (m_celCreated) {
241 if (m_layer && m_layer->isImage())
242 static_cast<LayerImage*>(m_layer)->removeCel(m_cel);
243
244 delete m_cel;
245 m_celImage.reset(NULL);
246 }
247
248 m_closed = true;
249 }
250
getSourceCanvas()251 Image* ExpandCelCanvas::getSourceCanvas()
252 {
253 ASSERT((m_flags & NeedsSource) == NeedsSource);
254
255 if (!m_srcImage) {
256 m_srcImage.reset(Image::create(m_sprite->pixelFormat(),
257 m_bounds.w, m_bounds.h, src_buffer));
258
259 m_srcImage->setMaskColor(m_sprite->transparentColor());
260 }
261 return m_srcImage.get();
262 }
263
getDestCanvas()264 Image* ExpandCelCanvas::getDestCanvas()
265 {
266 if (!m_dstImage) {
267 m_dstImage.reset(Image::create(m_sprite->pixelFormat(),
268 m_bounds.w, m_bounds.h, dst_buffer));
269
270 m_dstImage->setMaskColor(m_sprite->transparentColor());
271 }
272 return m_dstImage.get();
273 }
274
validateSourceCanvas(const gfx::Region & rgn)275 void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn)
276 {
277 getSourceCanvas();
278
279 gfx::Region rgnToValidate(rgn);
280 rgnToValidate.offset(-m_bounds.origin());
281 rgnToValidate.createSubtraction(rgnToValidate, m_validSrcRegion);
282 rgnToValidate.createIntersection(rgnToValidate, gfx::Region(m_srcImage->bounds()));
283
284 if (m_celImage) {
285 gfx::Region rgnToClear;
286 rgnToClear.createSubtraction(rgnToValidate,
287 gfx::Region(m_celImage->bounds()
288 .offset(m_origCelPos)
289 .offset(-m_bounds.origin())));
290 for (const auto& rc : rgnToClear)
291 fill_rect(m_srcImage.get(), rc, m_srcImage->maskColor());
292
293 for (const auto& rc : rgnToValidate)
294 m_srcImage->copy(m_celImage.get(),
295 gfx::Clip(rc.x, rc.y,
296 rc.x+m_bounds.x-m_origCelPos.x,
297 rc.y+m_bounds.y-m_origCelPos.y, rc.w, rc.h));
298 }
299 else {
300 for (const auto& rc : rgnToValidate)
301 fill_rect(m_srcImage.get(), rc, m_srcImage->maskColor());
302 }
303
304 m_validSrcRegion.createUnion(m_validSrcRegion, rgnToValidate);
305 }
306
validateDestCanvas(const gfx::Region & rgn)307 void ExpandCelCanvas::validateDestCanvas(const gfx::Region& rgn)
308 {
309 Image* src;
310 int src_x, src_y;
311 if ((m_flags & NeedsSource) == NeedsSource) {
312 validateSourceCanvas(rgn);
313 src = m_srcImage.get();
314 src_x = m_bounds.x;
315 src_y = m_bounds.y;
316 }
317 else {
318 src = m_celImage.get();
319 src_x = m_origCelPos.x;
320 src_y = m_origCelPos.y;
321 }
322
323 getDestCanvas();
324
325 gfx::Region rgnToValidate(rgn);
326 rgnToValidate.offset(-m_bounds.origin());
327 rgnToValidate.createSubtraction(rgnToValidate, m_validDstRegion);
328 rgnToValidate.createIntersection(rgnToValidate, gfx::Region(m_dstImage->bounds()));
329
330 if (src) {
331 gfx::Region rgnToClear;
332 rgnToClear.createSubtraction(rgnToValidate,
333 gfx::Region(src->bounds()
334 .offset(src_x, src_y)
335 .offset(-m_bounds.origin())));
336 for (const auto& rc : rgnToClear)
337 fill_rect(m_dstImage.get(), rc, m_dstImage->maskColor());
338
339 for (const auto& rc : rgnToValidate)
340 m_dstImage->copy(src,
341 gfx::Clip(rc.x, rc.y,
342 rc.x+m_bounds.x-src_x,
343 rc.y+m_bounds.y-src_y, rc.w, rc.h));
344 }
345 else {
346 for (const auto& rc : rgnToValidate)
347 fill_rect(m_dstImage.get(), rc, m_dstImage->maskColor());
348 }
349
350 m_validDstRegion.createUnion(m_validDstRegion, rgnToValidate);
351 }
352
invalidateDestCanvas()353 void ExpandCelCanvas::invalidateDestCanvas()
354 {
355 m_validDstRegion.clear();
356 }
357
invalidateDestCanvas(const gfx::Region & rgn)358 void ExpandCelCanvas::invalidateDestCanvas(const gfx::Region& rgn)
359 {
360 gfx::Region rgnToInvalidate(rgn);
361 rgnToInvalidate.offset(-m_bounds.origin());
362 m_validDstRegion.createSubtraction(m_validDstRegion, rgnToInvalidate);
363 }
364
copyValidDestToSourceCanvas(const gfx::Region & rgn)365 void ExpandCelCanvas::copyValidDestToSourceCanvas(const gfx::Region& rgn)
366 {
367 gfx::Region rgn2(rgn);
368 rgn2.offset(-m_bounds.origin());
369 rgn2.createIntersection(rgn2, m_validSrcRegion);
370 rgn2.createIntersection(rgn2, m_validDstRegion);
371 for (const auto& rc : rgn2)
372 m_srcImage->copy(m_dstImage.get(),
373 gfx::Clip(rc.x, rc.y, rc.x, rc.y, rc.w, rc.h));
374
375 // We cannot compare src vs dst in this case (e.g. on tools like
376 // spray and jumble that updated the source image form the modified
377 // destination).
378 m_canCompareSrcVsDst = false;
379 }
380
getTrimDstImageBounds() const381 gfx::Rect ExpandCelCanvas::getTrimDstImageBounds() const
382 {
383 if (m_layer->isBackground())
384 return m_dstImage->bounds();
385 else {
386 gfx::Rect bounds;
387 algorithm::shrink_bounds(m_dstImage.get(), bounds,
388 m_dstImage->maskColor());
389 return bounds;
390 }
391 }
392
trimDstImage(const gfx::Rect & bounds) const393 ImageRef ExpandCelCanvas::trimDstImage(const gfx::Rect& bounds) const
394 {
395 return ImageRef(
396 crop_image(m_dstImage.get(),
397 bounds.x, bounds.y,
398 bounds.w, bounds.h,
399 m_dstImage->maskColor()));
400 }
401
402 } // namespace app
403