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