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/app.h"
12 #include "app/cmd/clear_mask.h"
13 #include "app/cmd/deselect_mask.h"
14 #include "app/cmd/trim_cel.h"
15 #include "app/console.h"
16 #include "app/context_access.h"
17 #include "app/doc.h"
18 #include "app/doc_api.h"
19 #include "app/doc_range.h"
20 #include "app/doc_range_ops.h"
21 #include "app/modules/editors.h"
22 #include "app/modules/gfx.h"
23 #include "app/modules/gui.h"
24 #include "app/pref/preferences.h"
25 #include "app/transaction.h"
26 #include "app/ui/color_bar.h"
27 #include "app/ui/editor/editor.h"
28 #include "app/ui/skin/skin_theme.h"
29 #include "app/ui/timeline/timeline.h"
30 #include "app/ui_context.h"
31 #include "app/util/clipboard.h"
32 #include "app/util/clipboard_native.h"
33 #include "app/util/new_image_from_mask.h"
34 #include "base/shared_ptr.h"
35 #include "clip/clip.h"
36 #include "doc/doc.h"
37 #include "render/ordered_dither.h"
38 #include "render/quantization.h"
39
40 #include <stdexcept>
41
42 namespace app {
43
44 namespace {
45
46 class ClipboardRange : public DocsObserver {
47 public:
ClipboardRange()48 ClipboardRange() : m_doc(nullptr) {
49 }
50
~ClipboardRange()51 ~ClipboardRange() {
52 ASSERT(!m_doc);
53 }
54
observeUIContext()55 void observeUIContext() {
56 UIContext::instance()->documents().add_observer(this);
57 }
58
unobserveUIContext()59 void unobserveUIContext() {
60 UIContext::instance()->documents().remove_observer(this);
61 }
62
valid()63 bool valid() {
64 return (m_doc != nullptr);
65 }
66
invalidate()67 void invalidate() {
68 m_doc = nullptr;
69 }
70
setRange(Doc * doc,const DocRange & range)71 void setRange(Doc* doc, const DocRange& range) {
72 m_doc = doc;
73 m_range = range;
74 }
75
document() const76 Doc* document() const { return m_doc; }
range() const77 DocRange range() const { return m_range; }
78
79 // DocsObserver impl
onRemoveDocument(Doc * doc)80 void onRemoveDocument(Doc* doc) override {
81 if (doc == m_doc)
82 invalidate();
83 }
84
85 private:
86 Doc* m_doc;
87 DocRange m_range;
88 };
89
90 }
91
92 namespace clipboard {
93
94 using namespace doc;
95
96 static base::SharedPtr<Palette> clipboard_palette;
97 static PalettePicks clipboard_picks;
98 static ImageRef clipboard_image;
99 static base::SharedPtr<Mask> clipboard_mask;
100 static ClipboardRange clipboard_range;
101
102 static ClipboardManager* g_instance = nullptr;
103
use_native_clipboard()104 static bool use_native_clipboard()
105 {
106 return Preferences::instance().experimental.useNativeClipboard();
107 }
108
instance()109 ClipboardManager* ClipboardManager::instance()
110 {
111 return g_instance;
112 }
113
ClipboardManager()114 ClipboardManager::ClipboardManager()
115 {
116 ASSERT(!g_instance);
117 g_instance = this;
118
119 register_native_clipboard_formats();
120
121 clipboard_range.observeUIContext();
122 }
123
~ClipboardManager()124 ClipboardManager::~ClipboardManager()
125 {
126 clipboard_range.invalidate();
127 clipboard_range.unobserveUIContext();
128
129 // Clean the whole clipboard
130 clipboard_palette.reset();
131 clipboard_image.reset();
132 clipboard_mask.reset();
133
134 ASSERT(g_instance == this);
135 g_instance = nullptr;
136 }
137
setClipboardText(const std::string & text)138 void ClipboardManager::setClipboardText(const std::string& text)
139 {
140 if (use_native_clipboard()) {
141 clip::set_text(text);
142 }
143 else {
144 m_text = text;
145 }
146 }
147
getClipboardText(std::string & text)148 bool ClipboardManager::getClipboardText(std::string& text)
149 {
150 if (use_native_clipboard()) {
151 return clip::get_text(text);
152 }
153 else {
154 text = m_text;
155 return true;
156 }
157 }
158
set_clipboard_image(Image * image,Mask * mask,Palette * palette,bool set_system_clipboard,bool image_source_is_transparent)159 static void set_clipboard_image(Image* image,
160 Mask* mask,
161 Palette* palette,
162 bool set_system_clipboard,
163 bool image_source_is_transparent)
164 {
165 clipboard_palette.reset(palette);
166 clipboard_picks.clear();
167 clipboard_image.reset(image);
168 clipboard_mask.reset(mask);
169
170 // Copy image to the native clipboard
171 if (set_system_clipboard) {
172 color_t oldMask;
173 if (image) {
174 oldMask = image->maskColor();
175 if (!image_source_is_transparent)
176 image->setMaskColor(-1);
177 }
178
179 if (use_native_clipboard())
180 set_native_clipboard_bitmap(image, mask, palette);
181
182 if (image && !image_source_is_transparent)
183 image->setMaskColor(oldMask);
184 }
185
186 clipboard_range.invalidate();
187 }
188
copy_from_document(const Site & site,bool merged=false)189 static bool copy_from_document(const Site& site, bool merged = false)
190 {
191 const Doc* document = static_cast<const Doc*>(site.document());
192 ASSERT(document);
193
194 const Mask* mask = document->mask();
195 Image* image = new_image_from_mask(site, mask, merged);
196 if (!image)
197 return false;
198
199 const Palette* pal = document->sprite()->palette(site.frame());
200 set_clipboard_image(
201 image,
202 (mask ? new Mask(*mask): nullptr),
203 (pal ? new Palette(*pal): nullptr),
204 true,
205 site.layer() && !site.layer()->isBackground());
206
207 return true;
208 }
209
get_current_format()210 ClipboardFormat get_current_format()
211 {
212 // Check if the native clipboard has an image
213 if (use_native_clipboard() &&
214 has_native_clipboard_bitmap())
215 return ClipboardImage;
216 else if (clipboard_image)
217 return ClipboardImage;
218 else if (clipboard_range.valid())
219 return ClipboardDocRange;
220 else if (clipboard_palette && clipboard_picks.picks())
221 return ClipboardPaletteEntries;
222 else
223 return ClipboardNone;
224 }
225
get_document_range_info(Doc ** document,DocRange * range)226 void get_document_range_info(Doc** document, DocRange* range)
227 {
228 if (clipboard_range.valid()) {
229 *document = clipboard_range.document();
230 *range = clipboard_range.range();
231 }
232 else {
233 *document = NULL;
234 }
235 }
236
clear_content()237 void clear_content()
238 {
239 set_clipboard_image(nullptr, nullptr, nullptr, true, false);
240 }
241
cut(ContextWriter & writer)242 void cut(ContextWriter& writer)
243 {
244 ASSERT(writer.document() != NULL);
245 ASSERT(writer.sprite() != NULL);
246 ASSERT(writer.layer() != NULL);
247
248 if (!copy_from_document(*writer.site())) {
249 Console console;
250 console.printf("Can't copying an image portion from the current layer\n");
251 }
252 else {
253 {
254 Transaction transaction(writer.context(), "Cut");
255 transaction.execute(new cmd::ClearMask(writer.cel()));
256
257 ASSERT(writer.cel());
258 if (writer.cel() &&
259 writer.cel()->layer()->isTransparent())
260 transaction.execute(new cmd::TrimCel(writer.cel()));
261
262 transaction.execute(new cmd::DeselectMask(writer.document()));
263 transaction.commit();
264 }
265 writer.document()->generateMaskBoundaries();
266 update_screen_for_document(writer.document());
267 }
268 }
269
copy(const ContextReader & reader)270 void copy(const ContextReader& reader)
271 {
272 ASSERT(reader.document() != NULL);
273
274 if (!copy_from_document(*reader.site())) {
275 Console console;
276 console.printf("Can't copying an image portion from the current layer\n");
277 return;
278 }
279 }
280
copy_merged(const ContextReader & reader)281 void copy_merged(const ContextReader& reader)
282 {
283 ASSERT(reader.document() != NULL);
284
285 copy_from_document(*reader.site(), true);
286 }
287
copy_range(const ContextReader & reader,const DocRange & range)288 void copy_range(const ContextReader& reader, const DocRange& range)
289 {
290 ASSERT(reader.document() != NULL);
291
292 ContextWriter writer(reader);
293
294 clear_content();
295 clipboard_range.setRange(writer.document(), range);
296
297 // TODO Replace this with a signal, because here the timeline
298 // depends on the clipboard and the clipboard on the timeline.
299 App::instance()->timeline()->activateClipboardRange();
300 }
301
copy_image(const Image * image,const Mask * mask,const Palette * pal)302 void copy_image(const Image* image, const Mask* mask, const Palette* pal)
303 {
304 set_clipboard_image(
305 Image::createCopy(image),
306 (mask ? new Mask(*mask): nullptr),
307 (pal ? new Palette(*pal): nullptr),
308 true, false);
309 }
310
copy_palette(const Palette * palette,const doc::PalettePicks & picks)311 void copy_palette(const Palette* palette, const doc::PalettePicks& picks)
312 {
313 if (!picks.picks())
314 return; // Do nothing case
315
316 set_clipboard_image(nullptr,
317 nullptr,
318 new Palette(*palette),
319 true, false);
320 clipboard_picks = picks;
321 }
322
paste()323 void paste()
324 {
325 Editor* editor = current_editor;
326 if (editor == NULL)
327 return;
328
329 Doc* dstDoc = editor->document();
330 Sprite* dstSpr = dstDoc->sprite();
331
332 switch (get_current_format()) {
333
334 case clipboard::ClipboardImage: {
335 // Get the image from the native clipboard.
336 if (use_native_clipboard()) {
337 Image* native_image = nullptr;
338 Mask* native_mask = nullptr;
339 Palette* native_palette = nullptr;
340 get_native_clipboard_bitmap(&native_image, &native_mask, &native_palette);
341 if (native_image)
342 set_clipboard_image(native_image, native_mask, native_palette,
343 false, false);
344 }
345
346 if (!clipboard_image)
347 return;
348
349 Palette* dst_palette = dstSpr->palette(editor->frame());
350
351 // Source image (clipboard or a converted copy to the destination 'imgtype')
352 ImageRef src_image;
353 if (clipboard_image->pixelFormat() == dstSpr->pixelFormat() &&
354 // Indexed images can be copied directly only if both images
355 // have the same palette.
356 (clipboard_image->pixelFormat() != IMAGE_INDEXED ||
357 clipboard_palette->countDiff(dst_palette, NULL, NULL) == 0)) {
358 src_image = clipboard_image;
359 }
360 else {
361 RgbMap* dst_rgbmap = dstSpr->rgbMap(editor->frame());
362
363 src_image.reset(
364 render::convert_pixel_format(
365 clipboard_image.get(), NULL, dstSpr->pixelFormat(),
366 render::DitheringAlgorithm::None,
367 render::DitheringMatrix(),
368 dst_rgbmap, clipboard_palette.get(),
369 false,
370 0));
371 }
372
373 // Change to MovingPixelsState
374 editor->pasteImage(src_image.get(),
375 clipboard_mask.get());
376 break;
377 }
378
379 case clipboard::ClipboardDocRange: {
380 DocRange srcRange = clipboard_range.range();
381 Doc* srcDoc = clipboard_range.document();
382 Sprite* srcSpr = srcDoc->sprite();
383
384 switch (srcRange.type()) {
385
386 case DocRange::kCels: {
387 Layer* dstLayer = editor->layer();
388 frame_t dstFrameFirst = editor->frame();
389
390 DocRange dstRange;
391 dstRange.startRange(dstLayer, dstFrameFirst, DocRange::kCels);
392 for (layer_t i=1; i<srcRange.layers(); ++i) {
393 dstLayer = dstLayer->getPreviousBrowsable();
394 if (dstLayer == nullptr)
395 break;
396 }
397 dstRange.endRange(dstLayer, dstFrameFirst+srcRange.frames()-1);
398
399 // We can use a document range op (copy_range) to copy/paste
400 // cels in the same document.
401 if (srcDoc == dstDoc) {
402 // This is the app::copy_range (not clipboard::copy_range()).
403 if (srcRange.layers() == dstRange.layers())
404 app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore);
405 editor->invalidate();
406 return;
407 }
408
409 Transaction transaction(UIContext::instance(), "Paste Cels");
410 DocApi api = dstDoc->getApi(transaction);
411
412 // Add extra frames if needed
413 while (dstFrameFirst+srcRange.frames() > dstSpr->totalFrames())
414 api.addFrame(dstSpr, dstSpr->totalFrames());
415
416 auto srcLayers = srcRange.selectedLayers().toLayerList();
417 auto dstLayers = dstRange.selectedLayers().toLayerList();
418
419 auto srcIt = srcLayers.begin();
420 auto dstIt = dstLayers.begin();
421 auto srcEnd = srcLayers.end();
422 auto dstEnd = dstLayers.end();
423
424 for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) {
425 auto srcLayer = *srcIt;
426 auto dstLayer = *dstIt;
427
428 if (!srcLayer->isImage() ||
429 !dstLayer->isImage())
430 continue;
431
432 // Maps a linked Cel in the original sprite with its
433 // corresponding copy in the new sprite. In this way
434 // we can.
435 std::map<Cel*, Cel*> relatedCels;
436
437 frame_t dstFrame = dstFrameFirst;
438 for (frame_t srcFrame : srcRange.selectedFrames()) {
439 Cel* srcCel = srcLayer->cel(srcFrame);
440 Cel* srcLink = nullptr;
441
442 if (srcCel && srcCel->image()) {
443 bool createCopy = true;
444
445 if (dstLayer->isContinuous() &&
446 srcCel->links()) {
447 srcLink = srcCel->link();
448 if (!srcLink)
449 srcLink = srcCel;
450
451 if (srcLink) {
452 Cel* dstRelated = relatedCels[srcLink];
453 if (dstRelated) {
454 createCopy = false;
455
456 // Create a link from dstRelated
457 api.copyCel(
458 static_cast<LayerImage*>(dstLayer), dstRelated->frame(),
459 static_cast<LayerImage*>(dstLayer), dstFrame);
460 }
461 }
462 }
463
464 if (createCopy) {
465 api.copyCel(
466 static_cast<LayerImage*>(srcLayer), srcFrame,
467 static_cast<LayerImage*>(dstLayer), dstFrame);
468
469 if (srcLink)
470 relatedCels[srcLink] = dstLayer->cel(dstFrame);
471 }
472 }
473 else {
474 Cel* dstCel = dstLayer->cel(dstFrame);
475 if (dstCel)
476 api.clearCel(dstCel);
477 }
478
479 ++dstFrame;
480 }
481 }
482
483 transaction.commit();
484 editor->invalidate();
485 break;
486 }
487
488 case DocRange::kFrames: {
489 frame_t dstFrame = editor->frame();
490
491 // We use a DocRange operation to copy frames inside
492 // the same sprite.
493 if (srcSpr == dstSpr) {
494 DocRange dstRange;
495 dstRange.startRange(nullptr, dstFrame, DocRange::kFrames);
496 dstRange.endRange(nullptr, dstFrame);
497 app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore);
498 break;
499 }
500
501 Transaction transaction(UIContext::instance(), "Paste Frames");
502 DocApi api = dstDoc->getApi(transaction);
503
504 auto srcLayers = srcSpr->allBrowsableLayers();
505 auto dstLayers = dstSpr->allBrowsableLayers();
506
507 for (frame_t srcFrame : srcRange.selectedFrames()) {
508 api.addEmptyFrame(dstSpr, dstFrame);
509 api.setFrameDuration(dstSpr, dstFrame, srcSpr->frameDuration(srcFrame));
510
511 auto srcIt = srcLayers.begin();
512 auto dstIt = dstLayers.begin();
513 auto srcEnd = srcLayers.end();
514 auto dstEnd = dstLayers.end();
515
516 for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) {
517 auto srcLayer = *srcIt;
518 auto dstLayer = *dstIt;
519
520 if (!srcLayer->isImage() ||
521 !dstLayer->isImage())
522 continue;
523
524 Cel* cel = static_cast<LayerImage*>(srcLayer)->cel(srcFrame);
525 if (cel && cel->image()) {
526 api.copyCel(
527 static_cast<LayerImage*>(srcLayer), srcFrame,
528 static_cast<LayerImage*>(dstLayer), dstFrame);
529 }
530 }
531
532 ++dstFrame;
533 }
534
535 transaction.commit();
536 editor->invalidate();
537 break;
538 }
539
540 case DocRange::kLayers: {
541 if (srcDoc->colorMode() != dstDoc->colorMode())
542 throw std::runtime_error("You cannot copy layers of document with different color modes");
543
544 Transaction transaction(UIContext::instance(), "Paste Layers");
545 DocApi api = dstDoc->getApi(transaction);
546
547 // Remove children if their parent is selected so we only
548 // copy the parent.
549 SelectedLayers srcLayersSet = srcRange.selectedLayers();
550 srcLayersSet.removeChildrenIfParentIsSelected();
551 LayerList srcLayers = srcLayersSet.toLayerList();
552
553 // Expand frames of dstDoc if it's needed.
554 frame_t maxFrame = 0;
555 for (Layer* srcLayer : srcLayers) {
556 if (!srcLayer->isImage())
557 continue;
558
559 Cel* lastCel = static_cast<LayerImage*>(srcLayer)->getLastCel();
560 if (lastCel && maxFrame < lastCel->frame())
561 maxFrame = lastCel->frame();
562 }
563 while (dstSpr->totalFrames() < maxFrame+1)
564 api.addEmptyFrame(dstSpr, dstSpr->totalFrames());
565
566 for (Layer* srcLayer : srcLayers) {
567 Layer* afterThis;
568 if (srcLayer->isBackground() && !dstDoc->sprite()->backgroundLayer())
569 afterThis = nullptr;
570 else
571 afterThis = dstSpr->root()->lastLayer();
572
573 Layer* newLayer = nullptr;
574 if (srcLayer->isImage())
575 newLayer = new LayerImage(dstSpr);
576 else if (srcLayer->isGroup())
577 newLayer = new LayerGroup(dstSpr);
578 else
579 continue;
580
581 api.addLayer(dstSpr->root(), newLayer, afterThis);
582
583 srcDoc->copyLayerContent(srcLayer, dstDoc, newLayer);
584 }
585
586 transaction.commit();
587 editor->invalidate();
588 break;
589 }
590 }
591 break;
592 }
593
594 }
595 }
596
get_image_size(gfx::Size & size)597 bool get_image_size(gfx::Size& size)
598 {
599 if (use_native_clipboard() &&
600 get_native_clipboard_bitmap_size(&size))
601 return true;
602
603 if (clipboard_image) {
604 size.w = clipboard_image->width();
605 size.h = clipboard_image->height();
606 return true;
607 }
608
609 return false;
610 }
611
get_palette()612 Palette* get_palette()
613 {
614 if (clipboard::get_current_format() == ClipboardPaletteEntries) {
615 ASSERT(clipboard_palette);
616 return clipboard_palette.get();
617 }
618 else
619 return nullptr;
620 }
621
get_palette_picks()622 const PalettePicks& get_palette_picks()
623 {
624 return clipboard_picks;
625 }
626
627 } // namespace clipboard
628
629 } // namespace app
630