1// Aseprite
2// Copyright (C) 2019-2020 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/add_cel.h"
14#include "app/cmd/replace_image.h"
15#include "app/cmd/set_cel_position.h"
16#include "app/cmd/unlink_cel.h"
17#include "app/commands/command.h"
18#include "app/context_access.h"
19#include "app/doc.h"
20#include "app/doc_api.h"
21#include "app/modules/gui.h"
22#include "app/tx.h"
23#include "doc/blend_internals.h"
24#include "doc/cel.h"
25#include "doc/image.h"
26#include "doc/layer.h"
27#include "doc/primitives.h"
28#include "doc/sprite.h"
29#include "render/rasterize.h"
30#include "ui/ui.h"
31
32namespace app {
33
34class MergeDownLayerCommand : public Command {
35public:
36 MergeDownLayerCommand();
37
38protected:
39 bool onEnabled(Context* context) override;
40 void onExecute(Context* context) override;
41};
42
43MergeDownLayerCommand::MergeDownLayerCommand()
44 : Command(CommandId::MergeDownLayer(), CmdRecordableFlag)
45{
46}
47
48bool MergeDownLayerCommand::onEnabled(Context* context)
49{
50 if (!context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
51 ContextFlags::HasActiveSprite))
52 return false;
53
54 const ContextReader reader(context);
55 const Sprite* sprite(reader.sprite());
56 if (!sprite)
57 return false;
58
59 const Layer* src_layer = reader.layer();
60 if (!src_layer ||
61 !src_layer->isImage() ||
62 src_layer->isTilemap()) // TODO Add support to merge tilemaps (and groups!)
63 return false;
64
65 const Layer* dst_layer = src_layer->getPrevious();
66 if (!dst_layer ||
67 !dst_layer->isImage() ||
68 dst_layer->isTilemap()) // TODO Add support to merge tilemaps
69 return false;
70
71 return true;
72}
73
74void MergeDownLayerCommand::onExecute(Context* context)
75{
76 ContextWriter writer(context);
77 Doc* document(writer.document());
78 Sprite* sprite(writer.sprite());
79 LayerImage* src_layer = static_cast<LayerImage*>(writer.layer());
80 Layer* dst_layer = src_layer->getPrevious();
81
82 Tx tx(writer.context(), friendlyName(), ModifyDocument);
83
84 for (frame_t frpos = 0; frpos<sprite->totalFrames(); ++frpos) {
85 // Get frames
86 Cel* src_cel = src_layer->cel(frpos);
87 Cel* dst_cel = dst_layer->cel(frpos);
88
89 // Get images
90 Image* src_image;
91 if (src_cel != NULL)
92 src_image = src_cel->image();
93 else
94 src_image = NULL;
95
96 ImageRef dst_image;
97 if (dst_cel)
98 dst_image = dst_cel->imageRef();
99
100 // With source image?
101 if (src_image) {
102 int t;
103 int opacity;
104 opacity = MUL_UN8(src_cel->opacity(), src_layer->opacity(), t);
105
106 // No destination image
107 if (!dst_image) { // Only a transparent layer can have a null cel
108 // Copy this cel to the destination layer...
109
110 // Creating a copy of the image
111 dst_image.reset(
112 render::rasterize_with_cel_bounds(src_cel));
113
114 // Creating a copy of the cell
115 dst_cel = new Cel(frpos, dst_image);
116 dst_cel->setPosition(src_cel->x(), src_cel->y());
117 dst_cel->setOpacity(opacity);
118
119 tx(new cmd::AddCel(dst_layer, dst_cel));
120 }
121 // With destination
122 else {
123 gfx::Rect bounds;
124
125 // Merge down in the background layer
126 if (dst_layer->isBackground()) {
127 bounds = sprite->bounds();
128 }
129 // Merge down in a transparent layer
130 else {
131 bounds = src_cel->bounds().createUnion(dst_cel->bounds());
132 }
133
134 doc::color_t bgcolor = app_get_color_to_clear_layer(dst_layer);
135
136 ImageRef new_image(doc::crop_image(
137 dst_image.get(),
138 bounds.x-dst_cel->x(),
139 bounds.y-dst_cel->y(),
140 bounds.w, bounds.h, bgcolor));
141
142 // Draw src_cel on new_image
143 render::rasterize(
144 new_image.get(), src_cel,
145 -bounds.x, -bounds.y, false);
146
147 // First unlink the dst_cel
148 if (dst_cel->links())
149 tx(new cmd::UnlinkCel(dst_cel));
150
151 // Then modify the dst_cel
152 tx(new cmd::SetCelPosition(dst_cel,
153 bounds.x, bounds.y));
154
155 tx(new cmd::ReplaceImage(sprite,
156 dst_cel->imageRef(), new_image));
157 }
158 }
159 }
160
161 document->notifyLayerMergedDown(src_layer, dst_layer);
162 document->getApi(tx).removeLayer(src_layer); // src_layer is deleted inside removeLayer()
163
164 tx.commit();
165
166#ifdef ENABLE_UI
167 if (context->isUIAvailable())
168 update_screen_for_document(document);
169#endif
170}
171
172Command* CommandFactory::createMergeDownLayerCommand()
173{
174 return new MergeDownLayerCommand;
175}
176
177} // namespace app
178