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/doc_api.h"
12 
13 #include "app/cmd/add_cel.h"
14 #include "app/cmd/add_frame.h"
15 #include "app/cmd/add_layer.h"
16 #include "app/cmd/background_from_layer.h"
17 #include "app/cmd/clear_cel.h"
18 #include "app/cmd/clear_image.h"
19 #include "app/cmd/copy_cel.h"
20 #include "app/cmd/copy_frame.h"
21 #include "app/cmd/flip_image.h"
22 #include "app/cmd/layer_from_background.h"
23 #include "app/cmd/move_cel.h"
24 #include "app/cmd/move_layer.h"
25 #include "app/cmd/remove_cel.h"
26 #include "app/cmd/remove_frame.h"
27 #include "app/cmd/remove_frame_tag.h"
28 #include "app/cmd/remove_layer.h"
29 #include "app/cmd/replace_image.h"
30 #include "app/cmd/set_cel_bounds.h"
31 #include "app/cmd/set_cel_frame.h"
32 #include "app/cmd/set_cel_opacity.h"
33 #include "app/cmd/set_cel_position.h"
34 #include "app/cmd/set_frame_duration.h"
35 #include "app/cmd/set_frame_tag_range.h"
36 #include "app/cmd/set_mask.h"
37 #include "app/cmd/set_mask_position.h"
38 #include "app/cmd/set_palette.h"
39 #include "app/cmd/set_slice_key.h"
40 #include "app/cmd/set_sprite_size.h"
41 #include "app/cmd/set_total_frames.h"
42 #include "app/cmd/set_transparent_color.h"
43 #include "app/color_target.h"
44 #include "app/color_utils.h"
45 #include "app/context.h"
46 #include "app/doc.h"
47 #include "app/doc_undo.h"
48 #include "app/transaction.h"
49 #include "base/unique_ptr.h"
50 #include "doc/algorithm/flip_image.h"
51 #include "doc/algorithm/shrink_bounds.h"
52 #include "doc/cel.h"
53 #include "doc/frame_tag.h"
54 #include "doc/frame_tags.h"
55 #include "doc/mask.h"
56 #include "doc/palette.h"
57 #include "doc/slice.h"
58 #include "render/render.h"
59 
60 #include <set>
61 
62 #define TRACE_DOCAPI(...)
63 
64 namespace app {
65 
DocApi(Doc * document,Transaction & transaction)66 DocApi::DocApi(Doc* document, Transaction& transaction)
67   : m_document(document)
68   , m_transaction(transaction)
69 {
70 }
71 
setSpriteSize(Sprite * sprite,int w,int h)72 void DocApi::setSpriteSize(Sprite* sprite, int w, int h)
73 {
74   m_transaction.execute(new cmd::SetSpriteSize(sprite, w, h));
75 }
76 
setSpriteTransparentColor(Sprite * sprite,color_t maskColor)77 void DocApi::setSpriteTransparentColor(Sprite* sprite, color_t maskColor)
78 {
79   m_transaction.execute(new cmd::SetTransparentColor(sprite, maskColor));
80 }
81 
cropSprite(Sprite * sprite,const gfx::Rect & bounds)82 void DocApi::cropSprite(Sprite* sprite, const gfx::Rect& bounds)
83 {
84   setSpriteSize(sprite, bounds.w, bounds.h);
85 
86   Doc* doc = static_cast<Doc*>(sprite->document());
87   LayerList layers = sprite->allLayers();
88   for (Layer* layer : layers) {
89     if (!layer->isImage())
90       continue;
91 
92     std::set<ObjectId> visited;
93     CelIterator it = ((LayerImage*)layer)->getCelBegin();
94     CelIterator end = ((LayerImage*)layer)->getCelEnd();
95     for (; it != end; ++it) {
96       Cel* cel = *it;
97       if (visited.find(cel->data()->id()) != visited.end())
98         continue;
99       visited.insert(cel->data()->id());
100 
101       if (layer->isBackground()) {
102         Image* image = cel->image();
103         if (image && !cel->link()) {
104           ASSERT(cel->x() == 0);
105           ASSERT(cel->y() == 0);
106 
107           // Create the new image through a crop
108           ImageRef new_image(
109             crop_image(image,
110               bounds.x, bounds.y,
111               bounds.w, bounds.h,
112               doc->bgColor(layer)));
113 
114           // Replace the image in the stock that is pointed by the cel
115           replaceImage(sprite, cel->imageRef(), new_image);
116         }
117       }
118       else if (layer->isReference()) {
119         // Update the ref cel's bounds
120         gfx::RectF newBounds = cel->boundsF();
121         newBounds.x -= bounds.x;
122         newBounds.y -= bounds.y;
123         m_transaction.execute(new cmd::SetCelBoundsF(cel, newBounds));
124       }
125       else {
126         // Update the cel's position
127         setCelPosition(sprite, cel,
128           cel->x()-bounds.x, cel->y()-bounds.y);
129       }
130     }
131   }
132 
133   // Update mask position
134   if (!m_document->mask()->isEmpty())
135     setMaskPosition(m_document->mask()->bounds().x-bounds.x,
136                     m_document->mask()->bounds().y-bounds.y);
137 
138   // Update slice positions
139   if (bounds.origin() != gfx::Point(0, 0)) {
140     for (auto& slice : m_document->sprite()->slices()) {
141       for (auto& k : *slice) {
142         const SliceKey& key = *k.value();
143         if (key.isEmpty())
144           continue;
145 
146         SliceKey newKey = key;
147         newKey.setBounds(
148           gfx::Rect(newKey.bounds()).offset(-bounds.origin()));
149 
150         // As SliceKey::center() and pivot() properties are relative
151         // to the bounds(), we don't need to adjust them.
152 
153         m_transaction.execute(
154           new cmd::SetSliceKey(slice, k.frame(), newKey));
155       }
156     }
157   }
158 }
159 
trimSprite(Sprite * sprite)160 void DocApi::trimSprite(Sprite* sprite)
161 {
162   gfx::Rect bounds;
163 
164   base::UniquePtr<Image> image_wrap(Image::create(sprite->pixelFormat(),
165                                                   sprite->width(),
166                                                   sprite->height()));
167   Image* image = image_wrap.get();
168   render::Render render;
169 
170   for (frame_t frame(0); frame<sprite->totalFrames(); ++frame) {
171     render.renderSprite(image, sprite, frame);
172 
173     // TODO configurable (what color pixel to use as "refpixel",
174     // here we are using the top-left pixel by default)
175     gfx::Rect frameBounds;
176     if (doc::algorithm::shrink_bounds(image, frameBounds, get_pixel(image, 0, 0)))
177       bounds = bounds.createUnion(frameBounds);
178   }
179 
180   if (!bounds.isEmpty())
181     cropSprite(sprite, bounds);
182 }
183 
addFrame(Sprite * sprite,frame_t newFrame)184 void DocApi::addFrame(Sprite* sprite, frame_t newFrame)
185 {
186   copyFrame(sprite, newFrame-1, newFrame,
187             kDropBeforeFrame,
188             kDefaultTagsAdjustment);
189 }
190 
addEmptyFrame(Sprite * sprite,frame_t newFrame)191 void DocApi::addEmptyFrame(Sprite* sprite, frame_t newFrame)
192 {
193   m_transaction.execute(new cmd::AddFrame(sprite, newFrame));
194   adjustFrameTags(sprite, newFrame, +1,
195                   kDropBeforeFrame,
196                   kDefaultTagsAdjustment);
197 }
198 
addEmptyFramesTo(Sprite * sprite,frame_t newFrame)199 void DocApi::addEmptyFramesTo(Sprite* sprite, frame_t newFrame)
200 {
201   while (sprite->totalFrames() <= newFrame)
202     addEmptyFrame(sprite, sprite->totalFrames());
203 }
204 
copyFrame(Sprite * sprite,const frame_t fromFrame,const frame_t newFrame,const DropFramePlace dropFramePlace,const TagsHandling tagsHandling)205 void DocApi::copyFrame(Sprite* sprite,
206                             const frame_t fromFrame,
207                             const frame_t newFrame,
208                             const DropFramePlace dropFramePlace,
209                             const TagsHandling tagsHandling)
210 {
211   ASSERT(sprite);
212   m_transaction.execute(
213     new cmd::CopyFrame(
214       sprite, fromFrame,
215       (dropFramePlace == kDropBeforeFrame ? newFrame:
216                                             newFrame+1)));
217 
218   adjustFrameTags(sprite, newFrame, +1,
219                   dropFramePlace,
220                   tagsHandling);
221 }
222 
removeFrame(Sprite * sprite,frame_t frame)223 void DocApi::removeFrame(Sprite* sprite, frame_t frame)
224 {
225   ASSERT(frame >= 0);
226   m_transaction.execute(new cmd::RemoveFrame(sprite, frame));
227   adjustFrameTags(sprite, frame, -1,
228                   kDropBeforeFrame,
229                   kDefaultTagsAdjustment);
230 }
231 
setTotalFrames(Sprite * sprite,frame_t frames)232 void DocApi::setTotalFrames(Sprite* sprite, frame_t frames)
233 {
234   ASSERT(frames >= 1);
235   m_transaction.execute(new cmd::SetTotalFrames(sprite, frames));
236 }
237 
setFrameDuration(Sprite * sprite,frame_t frame,int msecs)238 void DocApi::setFrameDuration(Sprite* sprite, frame_t frame, int msecs)
239 {
240   m_transaction.execute(new cmd::SetFrameDuration(sprite, frame, msecs));
241 }
242 
setFrameRangeDuration(Sprite * sprite,frame_t from,frame_t to,int msecs)243 void DocApi::setFrameRangeDuration(Sprite* sprite, frame_t from, frame_t to, int msecs)
244 {
245   ASSERT(from >= frame_t(0));
246   ASSERT(from < to);
247   ASSERT(to <= sprite->lastFrame());
248 
249   for (frame_t fr=from; fr<=to; ++fr)
250     m_transaction.execute(new cmd::SetFrameDuration(sprite, fr, msecs));
251 }
252 
moveFrame(Sprite * sprite,const frame_t frame,frame_t targetFrame,const DropFramePlace dropFramePlace,const TagsHandling tagsHandling)253 void DocApi::moveFrame(Sprite* sprite,
254                             const frame_t frame,
255                             frame_t targetFrame,
256                             const DropFramePlace dropFramePlace,
257                             const TagsHandling tagsHandling)
258 {
259   const frame_t beforeFrame =
260     (dropFramePlace == kDropBeforeFrame ? targetFrame: targetFrame+1);
261 
262   if (frame       >= 0 && frame       <= sprite->lastFrame()   &&
263       beforeFrame >= 0 && beforeFrame <= sprite->lastFrame()+1 &&
264       ((frame != beforeFrame) ||
265        (!sprite->frameTags().empty() &&
266         tagsHandling != kDontAdjustTags))) {
267     // Change the frame-lengths.
268     int frlen_aux = sprite->frameDuration(frame);
269 
270     // Moving the frame to the future.
271     if (frame < beforeFrame) {
272       for (frame_t c=frame; c<beforeFrame-1; ++c)
273         setFrameDuration(sprite, c, sprite->frameDuration(c+1));
274       setFrameDuration(sprite, beforeFrame-1, frlen_aux);
275     }
276     // Moving the frame to the past.
277     else if (beforeFrame < frame) {
278       for (frame_t c=frame; c>beforeFrame; --c)
279         setFrameDuration(sprite, c, sprite->frameDuration(c-1));
280       setFrameDuration(sprite, beforeFrame, frlen_aux);
281     }
282 
283     if (tagsHandling != kDontAdjustTags) {
284       adjustFrameTags(sprite, frame, -1, dropFramePlace, tagsHandling);
285       if (targetFrame >= frame)
286         --targetFrame;
287       adjustFrameTags(sprite, targetFrame, +1, dropFramePlace, tagsHandling);
288     }
289 
290     // Change cel positions.
291     if (frame != beforeFrame)
292       moveFrameLayer(sprite->root(), frame, beforeFrame);
293   }
294 }
295 
moveFrameLayer(Layer * layer,frame_t frame,frame_t beforeFrame)296 void DocApi::moveFrameLayer(Layer* layer, frame_t frame, frame_t beforeFrame)
297 {
298   ASSERT(layer);
299 
300   switch (layer->type()) {
301 
302     case ObjectType::LayerImage: {
303       LayerImage* imglayer = static_cast<LayerImage*>(layer);
304 
305       CelList cels;
306       imglayer->getCels(cels);
307 
308       CelIterator it = cels.begin();
309       CelIterator end = cels.end();
310 
311       for (; it != end; ++it) {
312         Cel* cel = *it;
313         frame_t celFrame = cel->frame();
314         frame_t newFrame = celFrame;
315 
316         // moving the frame to the future
317         if (frame < beforeFrame) {
318           if (celFrame == frame) {
319             newFrame = beforeFrame-1;
320           }
321           else if (celFrame > frame &&
322                    celFrame < beforeFrame) {
323             --newFrame;
324           }
325         }
326         // moving the frame to the past
327         else if (beforeFrame < frame) {
328           if (celFrame == frame) {
329             newFrame = beforeFrame;
330           }
331           else if (celFrame >= beforeFrame &&
332                    celFrame < frame) {
333             ++newFrame;
334           }
335         }
336 
337         if (celFrame != newFrame)
338           setCelFramePosition(cel, newFrame);
339       }
340       break;
341     }
342 
343     case ObjectType::LayerGroup: {
344       for (Layer* child : static_cast<LayerGroup*>(layer)->layers())
345         moveFrameLayer(child, frame, beforeFrame);
346       break;
347     }
348 
349   }
350 }
351 
addCel(LayerImage * layer,Cel * cel)352 void DocApi::addCel(LayerImage* layer, Cel* cel)
353 {
354   ASSERT(layer);
355   ASSERT(cel);
356 
357   m_transaction.execute(new cmd::AddCel(layer, cel));
358 }
359 
setCelFramePosition(Cel * cel,frame_t frame)360 void DocApi::setCelFramePosition(Cel* cel, frame_t frame)
361 {
362   ASSERT(cel);
363   ASSERT(frame >= 0);
364 
365   m_transaction.execute(new cmd::SetCelFrame(cel, frame));
366 }
367 
setCelPosition(Sprite * sprite,Cel * cel,int x,int y)368 void DocApi::setCelPosition(Sprite* sprite, Cel* cel, int x, int y)
369 {
370   ASSERT(cel);
371 
372   m_transaction.execute(new cmd::SetCelPosition(cel, x, y));
373 }
374 
setCelOpacity(Sprite * sprite,Cel * cel,int newOpacity)375 void DocApi::setCelOpacity(Sprite* sprite, Cel* cel, int newOpacity)
376 {
377   ASSERT(cel);
378   ASSERT(sprite->supportAlpha());
379 
380   m_transaction.execute(new cmd::SetCelOpacity(cel, newOpacity));
381 }
382 
clearCel(LayerImage * layer,frame_t frame)383 void DocApi::clearCel(LayerImage* layer, frame_t frame)
384 {
385   if (Cel* cel = layer->cel(frame))
386     clearCel(cel);
387 }
388 
clearCel(Cel * cel)389 void DocApi::clearCel(Cel* cel)
390 {
391   ASSERT(cel);
392   m_transaction.execute(new cmd::ClearCel(cel));
393 }
394 
moveCel(LayerImage * srcLayer,frame_t srcFrame,LayerImage * dstLayer,frame_t dstFrame)395 void DocApi::moveCel(
396   LayerImage* srcLayer, frame_t srcFrame,
397   LayerImage* dstLayer, frame_t dstFrame)
398 {
399   ASSERT(srcLayer != dstLayer || srcFrame != dstFrame);
400   m_transaction.execute(new cmd::MoveCel(
401       srcLayer, srcFrame,
402       dstLayer, dstFrame, dstLayer->isContinuous()));
403 }
404 
copyCel(LayerImage * srcLayer,frame_t srcFrame,LayerImage * dstLayer,frame_t dstFrame)405 void DocApi::copyCel(
406   LayerImage* srcLayer, frame_t srcFrame,
407   LayerImage* dstLayer, frame_t dstFrame)
408 {
409   copyCel(
410     srcLayer, srcFrame,
411     dstLayer, dstFrame, dstLayer->isContinuous());
412 }
413 
copyCel(LayerImage * srcLayer,frame_t srcFrame,LayerImage * dstLayer,frame_t dstFrame,bool continuous)414 void DocApi::copyCel(
415   LayerImage* srcLayer, frame_t srcFrame,
416   LayerImage* dstLayer, frame_t dstFrame, bool continuous)
417 {
418   ASSERT(srcLayer != dstLayer || srcFrame != dstFrame);
419 
420   if (srcLayer == dstLayer && srcFrame == dstFrame)
421     return;                     // Nothing to be done
422 
423   m_transaction.execute(
424     new cmd::CopyCel(
425       srcLayer, srcFrame,
426       dstLayer, dstFrame, continuous));
427 }
428 
swapCel(LayerImage * layer,frame_t frame1,frame_t frame2)429 void DocApi::swapCel(
430   LayerImage* layer, frame_t frame1, frame_t frame2)
431 {
432   ASSERT(frame1 != frame2);
433 
434   Sprite* sprite = layer->sprite();
435   ASSERT(sprite != NULL);
436   ASSERT(frame1 >= 0 && frame1 < sprite->totalFrames());
437   ASSERT(frame2 >= 0 && frame2 < sprite->totalFrames());
438   (void)sprite;              // To avoid unused variable warning on Release mode
439 
440   Cel* cel1 = layer->cel(frame1);
441   Cel* cel2 = layer->cel(frame2);
442 
443   if (cel1) setCelFramePosition(cel1, frame2);
444   if (cel2) setCelFramePosition(cel2, frame1);
445 }
446 
newLayer(LayerGroup * parent,const std::string & name)447 LayerImage* DocApi::newLayer(LayerGroup* parent, const std::string& name)
448 {
449   LayerImage* layer = new LayerImage(parent->sprite());
450   layer->setName(name);
451 
452   addLayer(parent, layer, parent->lastLayer());
453   return layer;
454 }
455 
newGroup(LayerGroup * parent,const std::string & name)456 LayerGroup* DocApi::newGroup(LayerGroup* parent, const std::string& name)
457 {
458   LayerGroup* layer = new LayerGroup(parent->sprite());
459   layer->setName(name);
460 
461   addLayer(parent, layer, parent->lastLayer());
462   return layer;
463 }
464 
addLayer(LayerGroup * parent,Layer * newLayer,Layer * afterThis)465 void DocApi::addLayer(LayerGroup* parent, Layer* newLayer, Layer* afterThis)
466 {
467   m_transaction.execute(new cmd::AddLayer(parent, newLayer, afterThis));
468 }
469 
removeLayer(Layer * layer)470 void DocApi::removeLayer(Layer* layer)
471 {
472   ASSERT(layer);
473 
474   m_transaction.execute(new cmd::RemoveLayer(layer));
475 }
476 
restackLayerAfter(Layer * layer,LayerGroup * parent,Layer * afterThis)477 void DocApi::restackLayerAfter(Layer* layer, LayerGroup* parent, Layer* afterThis)
478 {
479   ASSERT(parent);
480 
481   if (layer == afterThis)
482     return;
483 
484   m_transaction.execute(new cmd::MoveLayer(layer, parent, afterThis));
485 }
486 
restackLayerBefore(Layer * layer,LayerGroup * parent,Layer * beforeThis)487 void DocApi::restackLayerBefore(Layer* layer, LayerGroup* parent, Layer* beforeThis)
488 {
489   ASSERT(parent);
490 
491   if (layer == beforeThis)
492     return;
493 
494   Layer* afterThis;
495   if (beforeThis)
496     afterThis = beforeThis->getPrevious();
497   else
498     afterThis = parent->lastLayer();
499 
500   restackLayerAfter(layer, parent, afterThis);
501 }
502 
backgroundFromLayer(Layer * layer)503 void DocApi::backgroundFromLayer(Layer* layer)
504 {
505   m_transaction.execute(new cmd::BackgroundFromLayer(layer));
506 }
507 
layerFromBackground(Layer * layer)508 void DocApi::layerFromBackground(Layer* layer)
509 {
510   m_transaction.execute(new cmd::LayerFromBackground(layer));
511 }
512 
duplicateLayerAfter(Layer * sourceLayer,LayerGroup * parent,Layer * afterLayer)513 Layer* DocApi::duplicateLayerAfter(Layer* sourceLayer, LayerGroup* parent, Layer* afterLayer)
514 {
515   ASSERT(parent);
516   base::UniquePtr<Layer> newLayerPtr;
517 
518   if (sourceLayer->isImage())
519     newLayerPtr.reset(new LayerImage(sourceLayer->sprite()));
520   else if (sourceLayer->isGroup())
521     newLayerPtr.reset(new LayerGroup(sourceLayer->sprite()));
522   else
523     throw std::runtime_error("Invalid layer type");
524 
525   m_document->copyLayerContent(sourceLayer, m_document, newLayerPtr);
526 
527   newLayerPtr->setName(newLayerPtr->name() + " Copy");
528 
529   addLayer(parent, newLayerPtr, afterLayer);
530 
531   // Release the pointer as it is owned by the sprite now.
532   return newLayerPtr.release();
533 }
534 
duplicateLayerBefore(Layer * sourceLayer,LayerGroup * parent,Layer * beforeLayer)535 Layer* DocApi::duplicateLayerBefore(Layer* sourceLayer, LayerGroup* parent, Layer* beforeLayer)
536 {
537   ASSERT(parent);
538   Layer* afterThis = (beforeLayer ? beforeLayer->getPreviousBrowsable(): nullptr);
539   Layer* newLayer = duplicateLayerAfter(sourceLayer, parent, afterThis);
540   if (newLayer)
541     restackLayerBefore(newLayer, parent, beforeLayer);
542   return newLayer;
543 }
544 
addCel(LayerImage * layer,frame_t frameNumber,const ImageRef & image)545 Cel* DocApi::addCel(LayerImage* layer, frame_t frameNumber, const ImageRef& image)
546 {
547   ASSERT(layer->cel(frameNumber) == NULL);
548 
549   base::UniquePtr<Cel> cel(new Cel(frameNumber, image));
550 
551   addCel(layer, cel);
552   return cel.release();
553 }
554 
replaceImage(Sprite * sprite,const ImageRef & oldImage,const ImageRef & newImage)555 void DocApi::replaceImage(Sprite* sprite, const ImageRef& oldImage, const ImageRef& newImage)
556 {
557   ASSERT(oldImage);
558   ASSERT(newImage);
559   ASSERT(oldImage->maskColor() == newImage->maskColor());
560 
561   m_transaction.execute(new cmd::ReplaceImage(
562       sprite, oldImage, newImage));
563 }
564 
flipImage(Image * image,const gfx::Rect & bounds,doc::algorithm::FlipType flipType)565 void DocApi::flipImage(Image* image, const gfx::Rect& bounds,
566   doc::algorithm::FlipType flipType)
567 {
568   m_transaction.execute(new cmd::FlipImage(image, bounds, flipType));
569 }
570 
copyToCurrentMask(Mask * mask)571 void DocApi::copyToCurrentMask(Mask* mask)
572 {
573   ASSERT(m_document->mask());
574   ASSERT(mask);
575 
576   m_transaction.execute(new cmd::SetMask(m_document, mask));
577 }
578 
setMaskPosition(int x,int y)579 void DocApi::setMaskPosition(int x, int y)
580 {
581   ASSERT(m_document->mask());
582 
583   m_transaction.execute(new cmd::SetMaskPosition(m_document, gfx::Point(x, y)));
584 }
585 
setPalette(Sprite * sprite,frame_t frame,const Palette * newPalette)586 void DocApi::setPalette(Sprite* sprite, frame_t frame, const Palette* newPalette)
587 {
588   Palette* currentSpritePalette = sprite->palette(frame); // Sprite current pal
589   int from, to;
590 
591   // Check differences between current sprite palette and current system palette
592   from = to = -1;
593   currentSpritePalette->countDiff(newPalette, &from, &to);
594 
595   if (from >= 0 && to >= from) {
596     m_transaction.execute(new cmd::SetPalette(
597         sprite, frame, newPalette));
598   }
599 }
600 
adjustFrameTags(Sprite * sprite,const frame_t frame,const frame_t delta,const DropFramePlace dropFramePlace,const TagsHandling tagsHandling)601 void DocApi::adjustFrameTags(Sprite* sprite,
602                                   const frame_t frame,
603                                   const frame_t delta,
604                                   const DropFramePlace dropFramePlace,
605                                   const TagsHandling tagsHandling)
606 {
607   TRACE_DOCAPI(
608     "\n  adjustFrameTags %s frame %d delta=%d tags=%s:\n",
609     (dropFramePlace == kDropBeforeFrame ? "before": "after"),
610     frame, delta,
611     (tagsHandling == kDefaultTagsAdjustment ? "default":
612      tagsHandling == kFitInsideTags ? "fit-inside":
613                                       "fit-outside"));
614   ASSERT(tagsHandling != kDontAdjustTags);
615 
616   // As FrameTag::setFrameRange() changes m_frameTags, we need to use
617   // a copy of this collection
618   std::vector<FrameTag*> tags(sprite->frameTags().begin(), sprite->frameTags().end());
619 
620   for (FrameTag* tag : tags) {
621     frame_t from = tag->fromFrame();
622     frame_t to = tag->toFrame();
623 
624     TRACE_DOCAPI(" - [from to]=[%d %d] ->", from, to);
625 
626     // When delta = +1, frame = beforeFrame
627     if (delta == +1) {
628       switch (tagsHandling) {
629         case kDefaultTagsAdjustment:
630           if (frame <= from) { ++from; }
631           if (frame <= to+1) { ++to; }
632           break;
633         case kFitInsideTags:
634           if (frame < from) { ++from; }
635           if (frame <= to) { ++to; }
636           break;
637         case kFitOutsideTags:
638           if ((frame < from) ||
639               (frame == from &&
640                dropFramePlace == kDropBeforeFrame)) {
641             ++from;
642           }
643           if ((frame < to) ||
644               (frame == to &&
645                dropFramePlace == kDropBeforeFrame)) {
646             ++to;
647           }
648           break;
649       }
650     }
651     else if (delta == -1) {
652       if (frame < from) { --from; }
653       if (frame <= to) { --to; }
654     }
655 
656     TRACE_DOCAPI(" [%d %d]\n", from, to);
657 
658     if (from != tag->fromFrame() ||
659         to != tag->toFrame()) {
660       if (from > to)
661         m_transaction.execute(new cmd::RemoveFrameTag(sprite, tag));
662       else
663         m_transaction.execute(new cmd::SetFrameTagRange(tag, from, to));
664     }
665   }
666 }
667 
668 } // namespace app
669