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 | |
71 | namespace app { |
72 | |
73 | DocApi::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 | |
101 | DocApi::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 | |
109 | DocApi::DocApi(Doc* document, Transaction& transaction) |
110 | : m_document(document) |
111 | , m_transaction(transaction) |
112 | { |
113 | } |
114 | |
115 | void DocApi::setSpriteSize(Sprite* sprite, int w, int h) |
116 | { |
117 | m_transaction.execute(new cmd::SetSpriteSize(sprite, w, h)); |
118 | } |
119 | |
120 | void DocApi::setSpriteTransparentColor(Sprite* sprite, color_t maskColor) |
121 | { |
122 | m_transaction.execute(new cmd::SetTransparentColor(sprite, maskColor)); |
123 | } |
124 | |
125 | void 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 | |
184 | void 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 |
207 | bool 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 | |
331 | void 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 | |
338 | void DocApi::addFrame(Sprite* sprite, frame_t newFrame) |
339 | { |
340 | copyFrame(sprite, newFrame-1, newFrame, |
341 | kDropBeforeFrame, |
342 | kDefaultTagsAdjustment); |
343 | } |
344 | |
345 | void 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 | |
353 | void DocApi::addEmptyFramesTo(Sprite* sprite, frame_t newFrame) |
354 | { |
355 | while (sprite->totalFrames() <= newFrame) |
356 | addEmptyFrame(sprite, sprite->totalFrames()); |
357 | } |
358 | |
359 | void 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 | |
391 | void 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 | |
400 | void DocApi::setTotalFrames(Sprite* sprite, frame_t frames) |
401 | { |
402 | ASSERT(frames >= 1); |
403 | m_transaction.execute(new cmd::SetTotalFrames(sprite, frames)); |
404 | } |
405 | |
406 | void DocApi::setFrameDuration(Sprite* sprite, frame_t frame, int msecs) |
407 | { |
408 | m_transaction.execute(new cmd::SetFrameDuration(sprite, frame, msecs)); |
409 | } |
410 | |
411 | void 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 | |
421 | void 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 | |
464 | void 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 | |
521 | void 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 | |
529 | void 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 | |
537 | void 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 | |
545 | void 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 | |
553 | void DocApi::clearCel(Layer* layer, frame_t frame) |
554 | { |
555 | ASSERT(layer->isImage()); |
556 | if (Cel* cel = layer->cel(frame)) |
557 | clearCel(cel); |
558 | } |
559 | |
560 | void DocApi::clearCel(Cel* cel) |
561 | { |
562 | ASSERT(cel); |
563 | m_transaction.execute(new cmd::ClearCel(cel)); |
564 | } |
565 | |
566 | void 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 | |
580 | void 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 | |
601 | void 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 | |
623 | void 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 | |
641 | LayerImage* 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 | |
650 | LayerGroup* 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 | |
659 | void DocApi::addLayer(LayerGroup* parent, Layer* newLayer, Layer* afterThis) |
660 | { |
661 | m_transaction.execute(new cmd::AddLayer(parent, newLayer, afterThis)); |
662 | } |
663 | |
664 | void DocApi::removeLayer(Layer* layer) |
665 | { |
666 | ASSERT(layer); |
667 | |
668 | m_transaction.execute(new cmd::RemoveLayer(layer)); |
669 | } |
670 | |
671 | void 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 | |
681 | void 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 | |
697 | Layer* 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 | |
723 | Layer* 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 | |
733 | Cel* DocApi::addCel(LayerImage* layer, frame_t , 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 | |
743 | void 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 | |
753 | void 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 | |
759 | void 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 | |
767 | void 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 | |
774 | void 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 | |
789 | void 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 | |