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/app.h"
13#include "app/cmd/clear_mask.h"
14#include "app/cmd/deselect_mask.h"
15#include "app/cmd/set_mask.h"
16#include "app/cmd/trim_cel.h"
17#include "app/console.h"
18#include "app/context_access.h"
19#include "app/doc.h"
20#include "app/doc_api.h"
21#include "app/doc_range.h"
22#include "app/doc_range_ops.h"
23#include "app/modules/editors.h"
24#include "app/modules/gfx.h"
25#include "app/modules/gui.h"
26#include "app/pref/preferences.h"
27#include "app/tx.h"
28#include "app/ui/color_bar.h"
29#include "app/ui/editor/editor.h"
30#include "app/ui/skin/skin_theme.h"
31#include "app/ui/timeline/timeline.h"
32#include "app/ui_context.h"
33#include "app/util/cel_ops.h"
34#include "app/util/clipboard.h"
35#include "app/util/new_image_from_mask.h"
36#include "app/util/range_utils.h"
37#include "clip/clip.h"
38#include "doc/doc.h"
39#include "render/dithering.h"
40#include "render/ordered_dither.h"
41#include "render/quantization.h"
42
43#include <memory>
44#include <stdexcept>
45
46namespace app {
47
48using namespace doc;
49
50namespace {
51
52 class ClipboardRange : public DocsObserver {
53 public:
54 ClipboardRange() : m_doc(nullptr) {
55 }
56
57 ~ClipboardRange() {
58 ASSERT(!m_doc);
59 }
60
61 void observeUIContext() {
62 UIContext::instance()->documents().add_observer(this);
63 }
64
65 void unobserveUIContext() {
66 UIContext::instance()->documents().remove_observer(this);
67 }
68
69 bool valid() const {
70 return (m_doc != nullptr);
71 }
72
73 void invalidate() {
74 m_doc = nullptr;
75 }
76
77 void setRange(Doc* doc, const DocRange& range) {
78 m_doc = doc;
79 m_range = range;
80 }
81
82 Doc* document() const { return m_doc; }
83 DocRange range() const { return m_range; }
84
85 // DocsObserver impl
86 void onRemoveDocument(Doc* doc) override {
87 if (doc == m_doc)
88 invalidate();
89 }
90
91 private:
92 Doc* m_doc;
93 DocRange m_range;
94 };
95
96}
97
98// Data in the clipboard
99struct Clipboard::Data {
100 // Text used when the native clipboard is disabled
101 std::string text;
102
103 // RGB/Grayscale/Indexed image
104 ImageRef image;
105
106 // The palette of the image (or tileset) if it's indexed
107 std::shared_ptr<Palette> palette;
108
109 // In case we copy a tilemap information
110 ImageRef tilemap;
111
112 // Tileset for the tilemap or a set of tiles if we are copying tiles
113 // in the color bar
114 std::shared_ptr<Tileset> tileset;
115
116 // Selected entries copied from the palette or the tileset
117 PalettePicks picks;
118
119 // Original selection used to copy the image
120 std::shared_ptr<Mask> mask;
121
122 // Selected set of layers/layers/cels
123 ClipboardRange range;
124
125 Data() {
126 range.observeUIContext();
127 }
128
129 ~Data() {
130 clear();
131 range.unobserveUIContext();
132 }
133
134 void clear() {
135 text.clear();
136 image.reset();
137 palette.reset();
138 tilemap.reset();
139 tileset.reset();
140 picks.clear();
141 mask.reset();
142 range.invalidate();
143 }
144
145 ClipboardFormat format() const {
146 if (image)
147 return ClipboardFormat::Image;
148 else if (tilemap)
149 return ClipboardFormat::Tilemap;
150 else if (range.valid())
151 return ClipboardFormat::DocRange;
152 else if (palette && picks.picks())
153 return ClipboardFormat::PaletteEntries;
154 else if (tileset && picks.picks())
155 return ClipboardFormat::Tileset;
156 else
157 return ClipboardFormat::None;
158 }
159};
160
161static bool use_native_clipboard()
162{
163 return Preferences::instance().experimental.useNativeClipboard();
164}
165
166static Clipboard* g_instance = nullptr;
167
168Clipboard* Clipboard::instance()
169{
170 return g_instance;
171}
172
173Clipboard::Clipboard()
174 : m_data(new Data)
175{
176 ASSERT(!g_instance);
177 g_instance = this;
178
179 registerNativeFormats();
180}
181
182Clipboard::~Clipboard()
183{
184 ASSERT(g_instance == this);
185 g_instance = nullptr;
186}
187
188void Clipboard::setClipboardText(const std::string& text)
189{
190 if (use_native_clipboard()) {
191 clip::set_text(text);
192 }
193 else {
194 m_data->text = text;
195 }
196}
197
198bool Clipboard::getClipboardText(std::string& text)
199{
200 if (use_native_clipboard()) {
201 return clip::get_text(text);
202 }
203 else {
204 text = m_data->text;
205 return true;
206 }
207}
208
209void Clipboard::setData(Image* image,
210 Mask* mask,
211 Palette* palette,
212 Tileset* tileset,
213 bool set_native_clipboard,
214 bool image_source_is_transparent)
215{
216 const bool isTilemap = (image && image->isTilemap());
217
218 m_data->clear();
219 m_data->palette.reset(palette);
220 m_data->tileset.reset(tileset);
221 m_data->mask.reset(mask);
222 if (isTilemap)
223 m_data->tilemap.reset(image);
224 else
225 m_data->image.reset(image);
226
227 if (set_native_clipboard) {
228 // Copy tilemap to the native clipboard
229 if (isTilemap) {
230 ASSERT(tileset);
231 setNativeBitmap(image, mask, palette, tileset);
232 }
233 // Copy non-tilemap images to the native clipboard
234 else {
235 color_t oldMask = 0;
236 if (image) {
237 oldMask = image->maskColor();
238 if (!image_source_is_transparent)
239 image->setMaskColor(-1);
240 }
241
242 if (use_native_clipboard())
243 setNativeBitmap(image, mask, palette);
244
245 if (image && !image_source_is_transparent)
246 image->setMaskColor(oldMask);
247 }
248 }
249}
250
251bool Clipboard::copyFromDocument(const Site& site, bool merged)
252{
253 ASSERT(site.document());
254 const Doc* doc = static_cast<const Doc*>(site.document());
255 const Mask* mask = doc->mask();
256 const Palette* pal = doc->sprite()->palette(site.frame());
257
258 if (!merged &&
259 site.layer() &&
260 site.layer()->isTilemap() &&
261 site.tilemapMode() == TilemapMode::Tiles) {
262 const Tileset* ts = static_cast<LayerTilemap*>(site.layer())->tileset();
263
264 Image* image = new_tilemap_from_mask(site, mask);
265 if (!image)
266 return false;
267
268 setData(
269 image,
270 (mask ? new Mask(*mask): nullptr),
271 (pal ? new Palette(*pal): nullptr),
272 Tileset::MakeCopyCopyingImages(ts),
273 true, // set native clipboard
274 site.layer() && !site.layer()->isBackground());
275
276 return true;
277 }
278
279 Image* image = new_image_from_mask(site, mask,
280 Preferences::instance().experimental.newBlend(),
281 merged);
282 if (!image)
283 return false;
284
285 setData(
286 image,
287 (mask ? new Mask(*mask): nullptr),
288 (pal ? new Palette(*pal): nullptr),
289 nullptr,
290 true, // set native clipboard
291 site.layer() && !site.layer()->isBackground());
292
293 return true;
294}
295
296ClipboardFormat Clipboard::format() const
297{
298 // Check if the native clipboard has an image
299 if (use_native_clipboard() && hasNativeBitmap()) {
300 return ClipboardFormat::Image;
301 }
302 else {
303 return m_data->format();
304 }
305}
306
307void Clipboard::getDocumentRangeInfo(Doc** document, DocRange* range)
308{
309 if (m_data->range.valid()) {
310 *document = m_data->range.document();
311 *range = m_data->range.range();
312 }
313 else {
314 *document = NULL;
315 }
316}
317
318void Clipboard::clearMaskFromCels(Tx& tx,
319 Doc* doc,
320 const Site& site,
321 const CelList& cels,
322 const bool deselectMask)
323{
324 for (Cel* cel : cels) {
325 ObjectId celId = cel->id();
326
327 clear_mask_from_cel(
328 tx, cel,
329 site.tilemapMode(),
330 site.tilesetMode());
331
332 // Get cel again just in case the cmd::ClearMask() called cmd::ClearCel()
333 cel = doc::get<Cel>(celId);
334 if (site.shouldTrimCel(cel))
335 tx(new cmd::TrimCel(cel));
336 }
337
338 if (deselectMask)
339 tx(new cmd::DeselectMask(doc));
340}
341
342void Clipboard::clearContent()
343{
344 if (use_native_clipboard())
345 clearNativeContent();
346 m_data->clear();
347}
348
349void Clipboard::cut(ContextWriter& writer)
350{
351 ASSERT(writer.document() != NULL);
352 ASSERT(writer.sprite() != NULL);
353 ASSERT(writer.layer() != NULL);
354
355 if (!copyFromDocument(*writer.site())) {
356 Console console;
357 console.printf("Can't copying an image portion from the current layer\n");
358 }
359 else {
360 // TODO This code is similar to DocView::onClear()
361 {
362 Tx tx(writer.context(), "Cut");
363 Site site = writer.context()->activeSite();
364 CelList cels;
365 if (site.range().enabled()) {
366 cels = get_unique_cels_to_edit_pixels(site.sprite(), site.range());
367 }
368 else if (site.cel()) {
369 cels.push_back(site.cel());
370 }
371 clearMaskFromCels(tx,
372 writer.document(),
373 site,
374 cels,
375 true); // Deselect mask
376 tx.commit();
377 }
378 writer.document()->generateMaskBoundaries();
379 update_screen_for_document(writer.document());
380 }
381}
382
383void Clipboard::copy(const ContextReader& reader)
384{
385 ASSERT(reader.document() != NULL);
386
387 if (!copyFromDocument(*reader.site())) {
388 Console console;
389 console.printf("Can't copying an image portion from the current layer\n");
390 return;
391 }
392}
393
394void Clipboard::copyMerged(const ContextReader& reader)
395{
396 ASSERT(reader.document() != NULL);
397
398 copyFromDocument(*reader.site(), true);
399}
400
401void Clipboard::copyRange(const ContextReader& reader, const DocRange& range)
402{
403 ASSERT(reader.document() != NULL);
404
405 ContextWriter writer(reader);
406
407 clearContent();
408 m_data->range.setRange(writer.document(), range);
409
410 // TODO Replace this with a signal, because here the timeline
411 // depends on the clipboard and the clipboard on the timeline.
412 App::instance()->timeline()->activateClipboardRange();
413}
414
415void Clipboard::copyImage(const Image* image,
416 const Mask* mask,
417 const Palette* pal)
418{
419 ASSERT(image->pixelFormat() != IMAGE_TILEMAP);
420 setData(
421 Image::createCopy(image),
422 (mask ? new Mask(*mask): nullptr),
423 (pal ? new Palette(*pal): nullptr),
424 nullptr,
425 true, false);
426}
427
428void Clipboard::copyTilemap(const Image* image,
429 const Mask* mask,
430 const Palette* pal,
431 const Tileset* tileset)
432{
433 ASSERT(image->pixelFormat() == IMAGE_TILEMAP);
434 setData(
435 Image::createCopy(image),
436 (mask ? new Mask(*mask): nullptr),
437 (pal ? new Palette(*pal): nullptr),
438 Tileset::MakeCopyCopyingImages(tileset),
439 true, false);
440}
441
442void Clipboard::copyPalette(const Palette* palette,
443 const PalettePicks& picks)
444{
445 if (!picks.picks())
446 return; // Do nothing case
447
448 setData(nullptr,
449 nullptr,
450 new Palette(*palette),
451 nullptr,
452 true, // set native clipboard
453 false);
454 m_data->picks = picks;
455}
456
457void Clipboard::paste(Context* ctx,
458 const bool interactive)
459{
460 Site site = ctx->activeSite();
461 Doc* dstDoc = site.document();
462 if (!dstDoc)
463 return;
464
465 Sprite* dstSpr = site.sprite();
466 if (!dstSpr)
467 return;
468
469 switch (format()) {
470
471 case ClipboardFormat::Image: {
472 // Get the image from the native clipboard.
473 if (!getImage(nullptr))
474 return;
475
476 ASSERT(m_data->image);
477
478 Palette* dst_palette = dstSpr->palette(site.frame());
479
480 // Source image (clipboard or a converted copy to the destination 'imgtype')
481 ImageRef src_image;
482 if (// Copy image of the same pixel format
483 (m_data->image->pixelFormat() == dstSpr->pixelFormat() &&
484 // Indexed images can be copied directly only if both images
485 // have the same palette.
486 (m_data->image->pixelFormat() != IMAGE_INDEXED ||
487 m_data->palette->countDiff(dst_palette, NULL, NULL) == 0))) {
488 src_image = m_data->image;
489 }
490 else {
491 RgbMap* dst_rgbmap = dstSpr->rgbMap(site.frame());
492
493 src_image.reset(
494 render::convert_pixel_format(
495 m_data->image.get(), NULL, dstSpr->pixelFormat(),
496 render::Dithering(),
497 dst_rgbmap, m_data->palette.get(),
498 false,
499 0));
500 }
501
502 if (current_editor && interactive) {
503 // TODO we don't support pasting in multiple cels at the
504 // moment, so we clear the range here (same as in
505 // PasteTextCommand::onExecute())
506 App::instance()->timeline()->clearAndInvalidateRange();
507
508 // Change to MovingPixelsState
509 current_editor->pasteImage(src_image.get(),
510 m_data->mask.get());
511 }
512 else {
513 // Non-interactive version (just copy the image to the cel)
514 Layer* dstLayer = site.layer();
515 ASSERT(dstLayer);
516 if (!dstLayer || !dstLayer->isImage())
517 return;
518
519 Tx tx(ctx, "Paste Image");
520 DocApi api = dstDoc->getApi(tx);
521 Cel* dstCel = api.addCel(
522 static_cast<LayerImage*>(dstLayer), site.frame(),
523 ImageRef(Image::createCopy(src_image.get())));
524
525 // Adjust bounds
526 if (dstCel) {
527 if (m_data->mask) {
528 if (dstLayer->isReference()) {
529 dstCel->setBounds(dstSpr->bounds());
530
531 Mask emptyMask;
532 tx(new cmd::SetMask(dstDoc, &emptyMask));
533 }
534 else {
535 dstCel->setBounds(m_data->mask->bounds());
536 tx(new cmd::SetMask(dstDoc, m_data->mask.get()));
537 }
538 }
539 }
540
541 tx.commit();
542 }
543 break;
544 }
545
546 case ClipboardFormat::Tilemap: {
547 if (current_editor && interactive) {
548 // TODO match both tilesets?
549 // TODO add post-command parameters (issue #2324)
550
551 // Change to MovingTilemapState
552 current_editor->pasteImage(m_data->tilemap.get(),
553 m_data->mask.get());
554 }
555 else {
556 // TODO non-interactive version (for scripts)
557 }
558 break;
559 }
560
561 case ClipboardFormat::DocRange: {
562 DocRange srcRange = m_data->range.range();
563 Doc* srcDoc = m_data->range.document();
564 Sprite* srcSpr = srcDoc->sprite();
565
566 switch (srcRange.type()) {
567
568 case DocRange::kCels: {
569 Layer* dstLayer = site.layer();
570 ASSERT(dstLayer);
571 if (!dstLayer)
572 return;
573
574 frame_t dstFrameFirst = site.frame();
575
576 DocRange dstRange;
577 dstRange.startRange(dstLayer, dstFrameFirst, DocRange::kCels);
578 for (layer_t i=1; i<srcRange.layers(); ++i) {
579 dstLayer = dstLayer->getPreviousBrowsable();
580 if (dstLayer == nullptr)
581 break;
582 }
583 dstRange.endRange(dstLayer, dstFrameFirst+srcRange.frames()-1);
584
585 // We can use a document range op (copy_range) to copy/paste
586 // cels in the same document.
587 if (srcDoc == dstDoc) {
588 // This is the app::copy_range (not clipboard::copy_range()).
589 if (srcRange.layers() == dstRange.layers())
590 app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore);
591 if (current_editor)
592 current_editor->invalidate(); // TODO check if this is necessary
593 return;
594 }
595
596 Tx tx(ctx, "Paste Cels");
597 DocApi api = dstDoc->getApi(tx);
598
599 // Add extra frames if needed
600 while (dstFrameFirst+srcRange.frames() > dstSpr->totalFrames())
601 api.addFrame(dstSpr, dstSpr->totalFrames());
602
603 auto srcLayers = srcRange.selectedLayers().toBrowsableLayerList();
604 auto dstLayers = dstRange.selectedLayers().toBrowsableLayerList();
605
606 auto srcIt = srcLayers.begin();
607 auto dstIt = dstLayers.begin();
608 auto srcEnd = srcLayers.end();
609 auto dstEnd = dstLayers.end();
610
611 for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) {
612 auto srcLayer = *srcIt;
613 auto dstLayer = *dstIt;
614
615 if (!srcLayer->isImage() ||
616 !dstLayer->isImage())
617 continue;
618
619 frame_t dstFrame = dstFrameFirst;
620 for (frame_t srcFrame : srcRange.selectedFrames()) {
621 Cel* srcCel = srcLayer->cel(srcFrame);
622
623 if (srcCel && srcCel->image()) {
624 api.copyCel(
625 static_cast<LayerImage*>(srcLayer), srcFrame,
626 static_cast<LayerImage*>(dstLayer), dstFrame);
627 }
628 else {
629 if (Cel* dstCel = dstLayer->cel(dstFrame))
630 api.clearCel(dstCel);
631 }
632
633 ++dstFrame;
634 }
635 }
636
637 tx.commit();
638 if (current_editor)
639 current_editor->invalidate(); // TODO check if this is necessary
640 break;
641 }
642
643 case DocRange::kFrames: {
644 frame_t dstFrame = site.frame();
645
646 // We use a DocRange operation to copy frames inside
647 // the same sprite.
648 if (srcSpr == dstSpr) {
649 DocRange dstRange;
650 dstRange.startRange(nullptr, dstFrame, DocRange::kFrames);
651 dstRange.endRange(nullptr, dstFrame);
652 app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore);
653 break;
654 }
655
656 Tx tx(ctx, "Paste Frames");
657 DocApi api = dstDoc->getApi(tx);
658
659 auto srcLayers = srcSpr->allBrowsableLayers();
660 auto dstLayers = dstSpr->allBrowsableLayers();
661
662 for (frame_t srcFrame : srcRange.selectedFrames()) {
663 api.addEmptyFrame(dstSpr, dstFrame);
664 api.setFrameDuration(dstSpr, dstFrame, srcSpr->frameDuration(srcFrame));
665
666 auto srcIt = srcLayers.begin();
667 auto dstIt = dstLayers.begin();
668 auto srcEnd = srcLayers.end();
669 auto dstEnd = dstLayers.end();
670
671 for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) {
672 auto srcLayer = *srcIt;
673 auto dstLayer = *dstIt;
674
675 if (!srcLayer->isImage() ||
676 !dstLayer->isImage())
677 continue;
678
679 Cel* cel = static_cast<LayerImage*>(srcLayer)->cel(srcFrame);
680 if (cel && cel->image()) {
681 api.copyCel(
682 static_cast<LayerImage*>(srcLayer), srcFrame,
683 static_cast<LayerImage*>(dstLayer), dstFrame);
684 }
685 }
686
687 ++dstFrame;
688 }
689
690 tx.commit();
691 if (current_editor)
692 current_editor->invalidate(); // TODO check if this is necessary
693 break;
694 }
695
696 case DocRange::kLayers: {
697 if (srcDoc->colorMode() != dstDoc->colorMode())
698 throw std::runtime_error("You cannot copy layers of document with different color modes");
699
700 Tx tx(ctx, "Paste Layers");
701 DocApi api = dstDoc->getApi(tx);
702
703 // Remove children if their parent is selected so we only
704 // copy the parent.
705 SelectedLayers srcLayersSet = srcRange.selectedLayers();
706 srcLayersSet.removeChildrenIfParentIsSelected();
707 LayerList srcLayers = srcLayersSet.toBrowsableLayerList();
708
709 // Expand frames of dstDoc if it's needed.
710 frame_t maxFrame = 0;
711 for (Layer* srcLayer : srcLayers) {
712 if (!srcLayer->isImage())
713 continue;
714
715 Cel* lastCel = static_cast<LayerImage*>(srcLayer)->getLastCel();
716 if (lastCel && maxFrame < lastCel->frame())
717 maxFrame = lastCel->frame();
718 }
719 while (dstSpr->totalFrames() < maxFrame+1)
720 api.addEmptyFrame(dstSpr, dstSpr->totalFrames());
721
722 for (Layer* srcLayer : srcLayers) {
723 Layer* afterThis;
724 if (srcLayer->isBackground() && !dstDoc->sprite()->backgroundLayer())
725 afterThis = nullptr;
726 else
727 afterThis = dstSpr->root()->lastLayer();
728
729 Layer* newLayer = nullptr;
730 if (srcLayer->isImage())
731 newLayer = new LayerImage(dstSpr);
732 else if (srcLayer->isGroup())
733 newLayer = new LayerGroup(dstSpr);
734 else
735 continue;
736
737 api.addLayer(dstSpr->root(), newLayer, afterThis);
738
739 srcDoc->copyLayerContent(srcLayer, dstDoc, newLayer);
740 }
741
742 tx.commit();
743 if (current_editor)
744 current_editor->invalidate(); // TODO check if this is necessary
745 break;
746 }
747 }
748 break;
749 }
750
751 }
752}
753
754ImageRef Clipboard::getImage(Palette* palette)
755{
756 // Get the image from the native clipboard.
757 if (use_native_clipboard()) {
758 Image* native_image = nullptr;
759 Mask* native_mask = nullptr;
760 Palette* native_palette = nullptr;
761 Tileset* native_tileset = nullptr;
762 getNativeBitmap(&native_image,
763 &native_mask,
764 &native_palette,
765 &native_tileset);
766 if (native_image) {
767 setData(native_image,
768 native_mask,
769 native_palette,
770 native_tileset,
771 false, false);
772 }
773 }
774 if (m_data->palette && palette)
775 m_data->palette->copyColorsTo(palette);
776 return m_data->image;
777}
778
779bool Clipboard::getImageSize(gfx::Size& size)
780{
781 if (use_native_clipboard() && getNativeBitmapSize(&size))
782 return true;
783
784 if (m_data->image) {
785 size.w = m_data->image->width();
786 size.h = m_data->image->height();
787 return true;
788 }
789
790 return false;
791}
792
793Palette* Clipboard::getPalette()
794{
795 if (format() == ClipboardFormat::PaletteEntries) {
796 ASSERT(m_data->palette);
797 return m_data->palette.get();
798 }
799 else
800 return nullptr;
801}
802
803const PalettePicks& Clipboard::getPalettePicks()
804{
805 return m_data->picks;
806}
807
808} // namespace app
809