1// Aseprite
2// Copyright (C) 2019-2021 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/doc_api.h"
13
14#include "app/cmd/add_cel.h"
15#include "app/cmd/add_frame.h"
16#include "app/cmd/add_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/move_cel.h"
23#include "app/cmd/move_layer.h"
24#include "app/cmd/remove_cel.h"
25#include "app/cmd/remove_frame.h"
26#include "app/cmd/remove_layer.h"
27#include "app/cmd/remove_tag.h"
28#include "app/cmd/replace_image.h"
29#include "app/cmd/set_cel_bounds.h"
30#include "app/cmd/set_cel_frame.h"
31#include "app/cmd/set_cel_opacity.h"
32#include "app/cmd/set_cel_position.h"
33#include "app/cmd/set_frame_duration.h"
34#include "app/cmd/set_mask.h"
35#include "app/cmd/set_mask_position.h"
36#include "app/cmd/set_palette.h"
37#include "app/cmd/set_slice_key.h"
38#include "app/cmd/set_sprite_size.h"
39#include "app/cmd/set_tag_range.h"
40#include "app/cmd/set_total_frames.h"
41#include "app/cmd/set_transparent_color.h"
42#include "app/color_target.h"
43#include "app/color_utils.h"
44#include "app/context.h"
45#include "app/doc.h"
46#include "app/doc_undo.h"
47#include "app/pref/preferences.h"
48#include "app/snap_to_grid.h"
49#include "app/transaction.h"
50#include "app/util/autocrop.h"
51#include "doc/algorithm/flip_image.h"
52#include "doc/algorithm/shrink_bounds.h"
53#include "doc/cel.h"
54#include "doc/mask.h"
55#include "doc/palette.h"
56#include "doc/slice.h"
57#include "doc/tag.h"
58#include "doc/tags.h"
59#include "render/render.h"
60
61#include <algorithm>
62#include <iterator>
63#include <set>
64#include <vector>
65
66#include "gfx/rect_io.h"
67#include "gfx/point_io.h"
68
69#define TRACE_DOCAPI(...)
70
71namespace app {
72
73DocApi::HandleLinkedCels::HandleLinkedCels(
74 DocApi& api,
75 doc::LayerImage* srcLayer, const doc::frame_t srcFrame,
76 doc::LayerImage* dstLayer, const doc::frame_t dstFrame)
77 : m_api(api)
78 , m_srcDataId(doc::NullId)
79 , m_dstLayer(dstLayer)
80 , m_dstFrame(dstFrame)
81 , m_created(false)
82{
83 if (Cel* srcCel = srcLayer->cel(srcFrame)) {
84 auto it = m_api.m_linkedCels.find(srcCel->data()->id());
85 if (it != m_api.m_linkedCels.end()) {
86 Cel* dstRelated = it->second;
87 if (dstRelated && dstRelated->layer() == dstLayer) {
88 // Create a link
89 m_api.m_transaction.execute(
90 new cmd::CopyCel(
91 dstRelated->layer(), dstRelated->frame(),
92 dstLayer, dstFrame, true));
93 m_created = true;
94 return;
95 }
96 }
97 m_srcDataId = srcCel->data()->id();
98 }
99}
100
101DocApi::HandleLinkedCels::~HandleLinkedCels()
102{
103 if (m_srcDataId != doc::NullId) {
104 if (Cel* dstCel = m_dstLayer->cel(m_dstFrame))
105 m_api.m_linkedCels[m_srcDataId] = dstCel;
106 }
107}
108
109DocApi::DocApi(Doc* document, Transaction& transaction)
110 : m_document(document)
111 , m_transaction(transaction)
112{
113}
114
115void DocApi::setSpriteSize(Sprite* sprite, int w, int h)
116{
117 m_transaction.execute(new cmd::SetSpriteSize(sprite, w, h));
118}
119
120void DocApi::setSpriteTransparentColor(Sprite* sprite, color_t maskColor)
121{
122 m_transaction.execute(new cmd::SetTransparentColor(sprite, maskColor));
123}
124
125void DocApi::cropSprite(Sprite* sprite,
126 const gfx::Rect& bounds,
127 const bool trimOutside)
128{
129 ASSERT(m_document == static_cast<Doc*>(sprite->document()));
130
131 setSpriteSize(sprite, bounds.w, bounds.h);
132
133 LayerList layers = sprite->allLayers();
134 for (Layer* layer : layers) {
135 if (!layer->isImage())
136 continue;
137
138 cropImageLayer(static_cast<LayerImage*>(layer), bounds, trimOutside);
139 }
140
141 // Update mask position
142 if (!m_document->mask()->isEmpty())
143 setMaskPosition(m_document->mask()->bounds().x-bounds.x,
144 m_document->mask()->bounds().y-bounds.y);
145
146 // Update slice positions
147 if (bounds.origin() != gfx::Point(0, 0)) {
148 for (auto& slice : m_document->sprite()->slices()) {
149 Slice::List::List keys;
150 std::copy(slice->begin(), slice->end(),
151 std::back_inserter(keys));
152
153 for (auto& k : keys) {
154 const SliceKey& key = *k.value();
155 if (key.isEmpty())
156 continue;
157
158 gfx::Rect newSliceBounds(key.bounds());
159 newSliceBounds.offset(-bounds.origin());
160
161 SliceKey newKey = key;
162
163 // If the slice is outside and the user doesn't want the out
164 // of canvas content, we delete the slice.
165 if (trimOutside) {
166 newSliceBounds &= gfx::Rect(bounds.size());
167 if (newSliceBounds.isEmpty())
168 newKey = SliceKey(); // An empty key (so we remove this key)
169 }
170
171 if (!newKey.isEmpty())
172 newKey.setBounds(newSliceBounds);
173
174 // As SliceKey::center() and pivot() properties are relative
175 // to the bounds(), we don't need to adjust them.
176
177 m_transaction.execute(
178 new cmd::SetSliceKey(slice, k.frame(), newKey));
179 }
180 }
181 }
182}
183
184void DocApi::cropImageLayer(LayerImage* layer,
185 const gfx::Rect& bounds,
186 const bool trimOutside)
187{
188 std::set<ObjectId> visited;
189 CelList cels, clearCels;
190 layer->getCels(cels);
191 for (Cel* cel : cels) {
192 if (visited.find(cel->data()->id()) != visited.end())
193 continue;
194 visited.insert(cel->data()->id());
195
196 if (!cropCel(layer, cel, bounds, trimOutside)) {
197 // Delete this cel and its links
198 clearCels.push_back(cel);
199 }
200 }
201
202 for (Cel* cel : clearCels)
203 clearCelAndAllLinks(cel);
204}
205
206// Returns false if the cel (and its links) must be deleted after this
207bool DocApi::cropCel(LayerImage* layer,
208 Cel* cel,
209 const gfx::Rect& bounds,
210 const bool trimOutside)
211{
212 if (layer->isBackground()) {
213 Image* image = cel->image();
214 if (image && !cel->link()) {
215 ASSERT(cel->x() == 0);
216 ASSERT(cel->y() == 0);
217
218 ImageRef newImage(
219 crop_image(image,
220 bounds.x, bounds.y,
221 bounds.w, bounds.h,
222 m_document->bgColor(layer)));
223
224 replaceImage(cel->sprite(),
225 cel->imageRef(),
226 newImage);
227 }
228 return true;
229 }
230
231 if (layer->isReference()) {
232 // Update the ref cel's bounds
233 gfx::RectF newBounds = cel->boundsF();
234 newBounds.x -= bounds.x;
235 newBounds.y -= bounds.y;
236 m_transaction.execute(new cmd::SetCelBoundsF(cel, newBounds));
237 return true;
238 }
239
240 gfx::Point newCelPos(cel->position() - bounds.origin());
241
242 // This is the complex case: we want to crop a transparent cel and
243 // remove the content that is outside the sprite canvas. This might
244 // generate one or two of the following Cmd:
245 // 1. Clear the cel ("return false" will generate a "cmd::ClearCel"
246 // then) if the cel bounds will be totally outside in the new
247 // canvas size
248 // 2. Replace the cel image if the cel must be cut in
249 // some edge because it's not totally contained
250 // 3. Just set the cel position (the most common case)
251 // if the cel image will be completely inside the new
252 // canvas
253 if (trimOutside) {
254 Image* image = cel->image();
255 if (image && !cel->link()) {
256 gfx::Rect newCelBounds = (bounds & cel->bounds());
257
258 if (newCelBounds.isEmpty())
259 return false;
260
261 newCelBounds.offset(-bounds.origin());
262
263 gfx::Point paintPos(newCelBounds.x - newCelPos.x,
264 newCelBounds.y - newCelPos.y);
265
266 const color_t bg = image->pixelFormat() == IMAGE_TILEMAP ?
267 notile :
268 m_document->bgColor(layer);
269 newCelPos = newCelBounds.origin();
270
271 doc::Grid grid;
272 if (layer->isTilemap()) {
273 const Tileset* tileset = static_cast<LayerTilemap*>(layer)->tileset();
274 grid = tileset->grid();
275 grid.origin(cel->position());
276
277 newCelBounds.setOrigin(bounds.origin() + newCelPos);
278 newCelBounds = grid.canvasToTile(newCelBounds);
279 paintPos = newCelBounds.origin();
280 newCelPos = grid.tileToCanvas(paintPos) - bounds.origin();
281 }
282
283 // crop the image
284 ImageRef newImage(
285 crop_image(image,
286 paintPos.x, paintPos.y,
287 newCelBounds.w, newCelBounds.h,
288 bg));
289
290 // Try to shrink the image ignoring transparent borders
291 gfx::Rect frameBounds;
292 if (doc::algorithm::shrink_bounds(newImage.get(),
293 newImage->maskColor(),
294 layer, frameBounds)) {
295 // In this case the new cel image can be even smaller
296 if (frameBounds != newImage->bounds()) {
297 newImage = ImageRef(
298 crop_image(newImage.get(),
299 frameBounds.x, frameBounds.y,
300 frameBounds.w, frameBounds.h,
301 bg));
302 if (layer->isTilemap())
303 newCelPos += grid.tileToCanvas(frameBounds.origin()) - grid.origin();
304 else
305 newCelPos += frameBounds.origin();
306 }
307 }
308 else {
309 // Delete this cel and its links
310 return false;
311 }
312
313 // If it's the same image, we can re-use the cel image and just
314 // move the cel position.
315 if (!is_same_image(cel->image(), newImage.get())) {
316 replaceImage(cel->sprite(),
317 cel->imageRef(),
318 newImage);
319 }
320 }
321 }
322
323 // Update the cel's position
324 setCelPosition(
325 cel->sprite(), cel,
326 newCelPos.x,
327 newCelPos.y);
328 return true;
329}
330
331void DocApi::trimSprite(Sprite* sprite, const bool byGrid)
332{
333 gfx::Rect bounds = get_trimmed_bounds(sprite, byGrid);
334 if (!bounds.isEmpty())
335 cropSprite(sprite, bounds);
336}
337
338void DocApi::addFrame(Sprite* sprite, frame_t newFrame)
339{
340 copyFrame(sprite, newFrame-1, newFrame,
341 kDropBeforeFrame,
342 kDefaultTagsAdjustment);
343}
344
345void DocApi::addEmptyFrame(Sprite* sprite, frame_t newFrame)
346{
347 m_transaction.execute(new cmd::AddFrame(sprite, newFrame));
348 adjustTags(sprite, newFrame, +1,
349 kDropBeforeFrame,
350 kDefaultTagsAdjustment);
351}
352
353void DocApi::addEmptyFramesTo(Sprite* sprite, frame_t newFrame)
354{
355 while (sprite->totalFrames() <= newFrame)
356 addEmptyFrame(sprite, sprite->totalFrames());
357}
358
359void DocApi::copyFrame(Sprite* sprite,
360 frame_t fromFrame,
361 const frame_t newFrame0,
362 const DropFramePlace dropFramePlace,
363 const TagsHandling tagsHandling)
364{
365 ASSERT(sprite);
366
367 frame_t newFrame =
368 (dropFramePlace == kDropBeforeFrame ? newFrame0:
369 newFrame0+1);
370
371 m_transaction.execute(
372 new cmd::CopyFrame(
373 sprite, fromFrame, newFrame));
374
375 if (fromFrame >= newFrame)
376 ++fromFrame;
377
378 for (Layer* layer : sprite->allLayers()) {
379 if (layer->isImage()) {
380 copyCel(
381 static_cast<LayerImage*>(layer), fromFrame,
382 static_cast<LayerImage*>(layer), newFrame);
383 }
384 }
385
386 adjustTags(sprite, newFrame0, +1,
387 dropFramePlace,
388 tagsHandling);
389}
390
391void DocApi::removeFrame(Sprite* sprite, frame_t frame)
392{
393 ASSERT(frame >= 0);
394 m_transaction.execute(new cmd::RemoveFrame(sprite, frame));
395 adjustTags(sprite, frame, -1,
396 kDropBeforeFrame,
397 kDefaultTagsAdjustment);
398}
399
400void DocApi::setTotalFrames(Sprite* sprite, frame_t frames)
401{
402 ASSERT(frames >= 1);
403 m_transaction.execute(new cmd::SetTotalFrames(sprite, frames));
404}
405
406void DocApi::setFrameDuration(Sprite* sprite, frame_t frame, int msecs)
407{
408 m_transaction.execute(new cmd::SetFrameDuration(sprite, frame, msecs));
409}
410
411void DocApi::setFrameRangeDuration(Sprite* sprite, frame_t from, frame_t to, int msecs)
412{
413 ASSERT(from >= frame_t(0));
414 ASSERT(from < to);
415 ASSERT(to <= sprite->lastFrame());
416
417 for (frame_t fr=from; fr<=to; ++fr)
418 m_transaction.execute(new cmd::SetFrameDuration(sprite, fr, msecs));
419}
420
421void DocApi::moveFrame(Sprite* sprite,
422 const frame_t frame,
423 frame_t targetFrame,
424 const DropFramePlace dropFramePlace,
425 const TagsHandling tagsHandling)
426{
427 const frame_t beforeFrame =
428 (dropFramePlace == kDropBeforeFrame ? targetFrame: targetFrame+1);
429
430 if (frame >= 0 && frame <= sprite->lastFrame() &&
431 beforeFrame >= 0 && beforeFrame <= sprite->lastFrame()+1 &&
432 ((frame != beforeFrame) ||
433 (!sprite->tags().empty() &&
434 tagsHandling != kDontAdjustTags))) {
435 // Change the frame-lengths.
436 int frlen_aux = sprite->frameDuration(frame);
437
438 // Moving the frame to the future.
439 if (frame < beforeFrame) {
440 for (frame_t c=frame; c<beforeFrame-1; ++c)
441 setFrameDuration(sprite, c, sprite->frameDuration(c+1));
442 setFrameDuration(sprite, beforeFrame-1, frlen_aux);
443 }
444 // Moving the frame to the past.
445 else if (beforeFrame < frame) {
446 for (frame_t c=frame; c>beforeFrame; --c)
447 setFrameDuration(sprite, c, sprite->frameDuration(c-1));
448 setFrameDuration(sprite, beforeFrame, frlen_aux);
449 }
450
451 if (tagsHandling != kDontAdjustTags) {
452 adjustTags(sprite, frame, -1, dropFramePlace, tagsHandling);
453 if (targetFrame >= frame)
454 --targetFrame;
455 adjustTags(sprite, targetFrame, +1, dropFramePlace, tagsHandling);
456 }
457
458 // Change cel positions.
459 if (frame != beforeFrame)
460 moveFrameLayer(sprite->root(), frame, beforeFrame);
461 }
462}
463
464void DocApi::moveFrameLayer(Layer* layer, frame_t frame, frame_t beforeFrame)
465{
466 ASSERT(layer);
467
468 switch (layer->type()) {
469
470 case ObjectType::LayerImage:
471 case ObjectType::LayerTilemap: {
472 LayerImage* imglayer = static_cast<LayerImage*>(layer);
473
474 CelList cels;
475 imglayer->getCels(cels);
476
477 CelIterator it = cels.begin();
478 CelIterator end = cels.end();
479
480 for (; it != end; ++it) {
481 Cel* cel = *it;
482 frame_t celFrame = cel->frame();
483 frame_t newFrame = celFrame;
484
485 // moving the frame to the future
486 if (frame < beforeFrame) {
487 if (celFrame == frame) {
488 newFrame = beforeFrame-1;
489 }
490 else if (celFrame > frame &&
491 celFrame < beforeFrame) {
492 --newFrame;
493 }
494 }
495 // moving the frame to the past
496 else if (beforeFrame < frame) {
497 if (celFrame == frame) {
498 newFrame = beforeFrame;
499 }
500 else if (celFrame >= beforeFrame &&
501 celFrame < frame) {
502 ++newFrame;
503 }
504 }
505
506 if (celFrame != newFrame)
507 setCelFramePosition(cel, newFrame);
508 }
509 break;
510 }
511
512 case ObjectType::LayerGroup: {
513 for (Layer* child : static_cast<LayerGroup*>(layer)->layers())
514 moveFrameLayer(child, frame, beforeFrame);
515 break;
516 }
517
518 }
519}
520
521void DocApi::addCel(LayerImage* layer, Cel* cel)
522{
523 ASSERT(layer);
524 ASSERT(cel);
525
526 m_transaction.execute(new cmd::AddCel(layer, cel));
527}
528
529void DocApi::setCelFramePosition(Cel* cel, frame_t frame)
530{
531 ASSERT(cel);
532 ASSERT(frame >= 0);
533
534 m_transaction.execute(new cmd::SetCelFrame(cel, frame));
535}
536
537void DocApi::setCelPosition(Sprite* sprite, Cel* cel, int x, int y)
538{
539 ASSERT(cel);
540
541 if (cel->x() != x || cel->y() != y)
542 m_transaction.execute(new cmd::SetCelPosition(cel, x, y));
543}
544
545void DocApi::setCelOpacity(Sprite* sprite, Cel* cel, int newOpacity)
546{
547 ASSERT(cel);
548 ASSERT(sprite->supportAlpha());
549
550 m_transaction.execute(new cmd::SetCelOpacity(cel, newOpacity));
551}
552
553void DocApi::clearCel(Layer* layer, frame_t frame)
554{
555 ASSERT(layer->isImage());
556 if (Cel* cel = layer->cel(frame))
557 clearCel(cel);
558}
559
560void DocApi::clearCel(Cel* cel)
561{
562 ASSERT(cel);
563 m_transaction.execute(new cmd::ClearCel(cel));
564}
565
566void DocApi::clearCelAndAllLinks(Cel* cel)
567{
568 ASSERT(cel);
569
570 ObjectId dataId = cel->data()->id();
571
572 CelList cels;
573 cel->layer()->getCels(cels);
574 for (Cel* cel2 : cels) {
575 if (cel2->data()->id() == dataId)
576 clearCel(cel2);
577 }
578}
579
580void DocApi::moveCel(
581 LayerImage* srcLayer, frame_t srcFrame,
582 LayerImage* dstLayer, frame_t dstFrame)
583{
584 ASSERT(srcLayer != dstLayer || srcFrame != dstFrame);
585 if (srcLayer == dstLayer && srcFrame == dstFrame)
586 return; // Nothing to be done
587
588 HandleLinkedCels handleLinkedCels(
589 *this, srcLayer, srcFrame, dstLayer, dstFrame);
590 if (handleLinkedCels.linkWasCreated()) {
591 if (Cel* srcCel = srcLayer->cel(srcFrame))
592 clearCel(srcCel);
593 return;
594 }
595
596 m_transaction.execute(new cmd::MoveCel(
597 srcLayer, srcFrame,
598 dstLayer, dstFrame, dstLayer->isContinuous()));
599}
600
601void DocApi::copyCel(
602 LayerImage* srcLayer, frame_t srcFrame,
603 LayerImage* dstLayer, frame_t dstFrame,
604 const bool* forceContinuous)
605{
606 ASSERT(srcLayer != dstLayer || srcFrame != dstFrame);
607 if (srcLayer == dstLayer && srcFrame == dstFrame)
608 return; // Nothing to be done
609
610 HandleLinkedCels handleLinkedCels(
611 *this, srcLayer, srcFrame, dstLayer, dstFrame);
612 if (handleLinkedCels.linkWasCreated())
613 return;
614
615 m_transaction.execute(
616 new cmd::CopyCel(
617 srcLayer, srcFrame,
618 dstLayer, dstFrame,
619 (forceContinuous ? *forceContinuous:
620 dstLayer->isContinuous())));
621}
622
623void DocApi::swapCel(
624 LayerImage* layer, frame_t frame1, frame_t frame2)
625{
626 ASSERT(frame1 != frame2);
627
628 Sprite* sprite = layer->sprite();
629 ASSERT(sprite != NULL);
630 ASSERT(frame1 >= 0 && frame1 < sprite->totalFrames());
631 ASSERT(frame2 >= 0 && frame2 < sprite->totalFrames());
632 (void)sprite; // To avoid unused variable warning on Release mode
633
634 Cel* cel1 = layer->cel(frame1);
635 Cel* cel2 = layer->cel(frame2);
636
637 if (cel1) setCelFramePosition(cel1, frame2);
638 if (cel2) setCelFramePosition(cel2, frame1);
639}
640
641LayerImage* DocApi::newLayer(LayerGroup* parent, const std::string& name)
642{
643 LayerImage* newLayer = new LayerImage(parent->sprite());
644 newLayer->setName(name);
645
646 addLayer(parent, newLayer, parent->lastLayer());
647 return newLayer;
648}
649
650LayerGroup* DocApi::newGroup(LayerGroup* parent, const std::string& name)
651{
652 LayerGroup* newLayerGroup = new LayerGroup(parent->sprite());
653 newLayerGroup->setName(name);
654
655 addLayer(parent, newLayerGroup, parent->lastLayer());
656 return newLayerGroup;
657}
658
659void DocApi::addLayer(LayerGroup* parent, Layer* newLayer, Layer* afterThis)
660{
661 m_transaction.execute(new cmd::AddLayer(parent, newLayer, afterThis));
662}
663
664void DocApi::removeLayer(Layer* layer)
665{
666 ASSERT(layer);
667
668 m_transaction.execute(new cmd::RemoveLayer(layer));
669}
670
671void DocApi::restackLayerAfter(Layer* layer, LayerGroup* parent, Layer* afterThis)
672{
673 ASSERT(parent);
674
675 if (layer == afterThis)
676 return;
677
678 m_transaction.execute(new cmd::MoveLayer(layer, parent, afterThis));
679}
680
681void DocApi::restackLayerBefore(Layer* layer, LayerGroup* parent, Layer* beforeThis)
682{
683 ASSERT(parent);
684
685 if (layer == beforeThis)
686 return;
687
688 Layer* afterThis;
689 if (beforeThis)
690 afterThis = beforeThis->getPrevious();
691 else
692 afterThis = parent->lastLayer();
693
694 restackLayerAfter(layer, parent, afterThis);
695}
696
697Layer* DocApi::duplicateLayerAfter(Layer* sourceLayer, LayerGroup* parent, Layer* afterLayer)
698{
699 ASSERT(parent);
700 std::unique_ptr<Layer> newLayerPtr;
701
702 if (sourceLayer->isTilemap()) {
703 newLayerPtr.reset(new LayerTilemap(sourceLayer->sprite(),
704 static_cast<LayerTilemap*>(sourceLayer)->tilesetIndex()));
705 }
706 else if (sourceLayer->isImage())
707 newLayerPtr.reset(new LayerImage(sourceLayer->sprite()));
708 else if (sourceLayer->isGroup())
709 newLayerPtr.reset(new LayerGroup(sourceLayer->sprite()));
710 else
711 throw std::runtime_error("Invalid layer type");
712
713 m_document->copyLayerContent(sourceLayer, m_document, newLayerPtr.get());
714
715 newLayerPtr->setName(newLayerPtr->name() + " Copy");
716
717 addLayer(parent, newLayerPtr.get(), afterLayer);
718
719 // Release the pointer as it is owned by the sprite now.
720 return newLayerPtr.release();
721}
722
723Layer* DocApi::duplicateLayerBefore(Layer* sourceLayer, LayerGroup* parent, Layer* beforeLayer)
724{
725 ASSERT(parent);
726 Layer* afterThis = (beforeLayer ? beforeLayer->getPreviousBrowsable(): nullptr);
727 Layer* newLayer = duplicateLayerAfter(sourceLayer, parent, afterThis);
728 if (newLayer)
729 restackLayerBefore(newLayer, parent, beforeLayer);
730 return newLayer;
731}
732
733Cel* DocApi::addCel(LayerImage* layer, frame_t frameNumber, const ImageRef& image)
734{
735 ASSERT(layer->cel(frameNumber) == NULL);
736
737 std::unique_ptr<Cel> cel(new Cel(frameNumber, image));
738
739 addCel(layer, cel.get());
740 return cel.release();
741}
742
743void DocApi::replaceImage(Sprite* sprite, const ImageRef& oldImage, const ImageRef& newImage)
744{
745 ASSERT(oldImage);
746 ASSERT(newImage);
747 ASSERT(oldImage->maskColor() == newImage->maskColor());
748
749 m_transaction.execute(new cmd::ReplaceImage(
750 sprite, oldImage, newImage));
751}
752
753void DocApi::flipImage(Image* image, const gfx::Rect& bounds,
754 doc::algorithm::FlipType flipType)
755{
756 m_transaction.execute(new cmd::FlipImage(image, bounds, flipType));
757}
758
759void DocApi::copyToCurrentMask(Mask* mask)
760{
761 ASSERT(m_document->mask());
762 ASSERT(mask);
763
764 m_transaction.execute(new cmd::SetMask(m_document, mask));
765}
766
767void DocApi::setMaskPosition(int x, int y)
768{
769 ASSERT(m_document->mask());
770
771 m_transaction.execute(new cmd::SetMaskPosition(m_document, gfx::Point(x, y)));
772}
773
774void DocApi::setPalette(Sprite* sprite, frame_t frame, const Palette* newPalette)
775{
776 Palette* currentSpritePalette = sprite->palette(frame); // Sprite current pal
777 int from, to;
778
779 // Check differences between current sprite palette and current system palette
780 from = to = -1;
781 currentSpritePalette->countDiff(newPalette, &from, &to);
782
783 if (from >= 0 && to >= from) {
784 m_transaction.execute(new cmd::SetPalette(
785 sprite, frame, newPalette));
786 }
787}
788
789void DocApi::adjustTags(Sprite* sprite,
790 const frame_t frame,
791 const frame_t delta,
792 const DropFramePlace dropFramePlace,
793 const TagsHandling tagsHandling)
794{
795 TRACE_DOCAPI(
796 "\n adjustTags %s frame %d delta=%d tags=%s:\n",
797 (dropFramePlace == kDropBeforeFrame ? "before": "after"),
798 frame, delta,
799 (tagsHandling == kDefaultTagsAdjustment ? "default":
800 tagsHandling == kFitInsideTags ? "fit-inside":
801 "fit-outside"));
802 ASSERT(tagsHandling != kDontAdjustTags);
803
804 // As FrameTag::setFrameRange() changes m_frameTags, we need to use
805 // a copy of this collection
806 std::vector<Tag*> tags(sprite->tags().begin(), sprite->tags().end());
807
808 for (Tag* tag : tags) {
809 frame_t from = tag->fromFrame();
810 frame_t to = tag->toFrame();
811
812 TRACE_DOCAPI(" - [from to]=[%d %d] ->", from, to);
813
814 // When delta = +1, frame = beforeFrame
815 if (delta == +1) {
816 switch (tagsHandling) {
817 case kDefaultTagsAdjustment:
818 if (frame <= from) { ++from; }
819 if (frame <= to+1) { ++to; }
820 break;
821 case kFitInsideTags:
822 if (frame < from) { ++from; }
823 if (frame <= to) { ++to; }
824 break;
825 case kFitOutsideTags:
826 if ((frame < from) ||
827 (frame == from &&
828 dropFramePlace == kDropBeforeFrame)) {
829 ++from;
830 }
831 if ((frame < to) ||
832 (frame == to &&
833 dropFramePlace == kDropBeforeFrame)) {
834 ++to;
835 }
836 break;
837 }
838 }
839 else if (delta == -1) {
840 if (frame < from) { --from; }
841 if (frame <= to) { --to; }
842 }
843
844 TRACE_DOCAPI(" [%d %d]\n", from, to);
845
846 if (from != tag->fromFrame() ||
847 to != tag->toFrame()) {
848 if (from > to)
849 m_transaction.execute(new cmd::RemoveTag(sprite, tag));
850 else
851 m_transaction.execute(new cmd::SetTagRange(tag, from, to));
852 }
853 }
854}
855
856} // namespace app
857