1// Aseprite
2// Copyright (C) 2021-2022 Igara Studio S.A.
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/cmd/add_cel.h"
12#include "app/cmd/add_layer.h"
13#include "app/cmd/add_tileset.h"
14#include "app/cmd/background_from_layer.h"
15#include "app/cmd/copy_cel.h"
16#include "app/cmd/layer_from_background.h"
17#include "app/cmd/remove_layer.h"
18#include "app/commands/command.h"
19#include "app/commands/new_params.h"
20#include "app/context_access.h"
21#include "app/i18n/strings.h"
22#include "app/modules/gui.h"
23#include "app/tx.h"
24#include "app/util/cel_ops.h"
25#include "doc/grid.h"
26#include "doc/layer.h"
27#include "doc/layer_tilemap.h"
28#include "doc/tileset.h"
29
30#ifdef ENABLE_SCRIPTING
31#include "app/script/luacpp.h"
32#endif
33
34#include <map>
35
36namespace app {
37
38enum class ConvertLayerParam { None, Background, Layer, Tilemap };
39
40template<>
41void Param<ConvertLayerParam>::fromString(const std::string& value)
42{
43 if (value == "background")
44 setValue(ConvertLayerParam::Background);
45 else if (value == "layer")
46 setValue(ConvertLayerParam::Layer);
47 else if (value == "tilemap")
48 setValue(ConvertLayerParam::Tilemap);
49 else
50 setValue(ConvertLayerParam::None);
51}
52
53#ifdef ENABLE_SCRIPTING
54template<>
55void Param<ConvertLayerParam>::fromLua(lua_State* L, int index)
56{
57 fromString(lua_tostring(L, index));
58}
59#endif // ENABLE_SCRIPTING
60
61struct ConvertLayerParams : public NewParams {
62 Param<ConvertLayerParam> to { this, ConvertLayerParam::None, "to" };
63};
64
65class ConvertLayerCommand : public CommandWithNewParams<ConvertLayerParams> {
66public:
67 ConvertLayerCommand();
68
69private:
70 bool onEnabled(Context* context) override;
71 void onExecute(Context* context) override;
72 std::string onGetFriendlyName() const override;
73
74 void copyCels(Tx& tx,
75 Layer* srcLayer,
76 Layer* newLayer);
77};
78
79ConvertLayerCommand::ConvertLayerCommand()
80 : CommandWithNewParams<ConvertLayerParams>(CommandId::ConvertLayer(), CmdRecordableFlag)
81{
82}
83
84bool ConvertLayerCommand::onEnabled(Context* ctx)
85{
86 if (!ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
87 ContextFlags::HasActiveSprite |
88 ContextFlags::HasActiveLayer |
89 ContextFlags::ActiveLayerIsVisible |
90 ContextFlags::ActiveLayerIsEditable))
91 return false;
92
93 // TODO add support to convert reference layers into regular layers or tilemaps
94 if (ctx->checkFlags(ContextFlags::ActiveLayerIsReference))
95 return false;
96
97 switch (params().to()) {
98
99 case ConvertLayerParam::Background:
100 return
101 // Doesn't have a background layer
102 !ctx->checkFlags(ContextFlags::HasBackgroundLayer) &&
103 // Convert a regular layer or tilemap into background
104 ctx->checkFlags(ContextFlags::ActiveLayerIsImage) &&
105 // TODO add support for background tliemaps
106 !ctx->checkFlags(ContextFlags::ActiveLayerIsTilemap);
107
108 case ConvertLayerParam::Layer:
109 return
110 // Convert a background layer into a transparent layer
111 ctx->checkFlags(ContextFlags::ActiveLayerIsImage |
112 ContextFlags::ActiveLayerIsBackground) ||
113 // or a tilemap into a regular layer
114 ctx->checkFlags(ContextFlags::ActiveLayerIsTilemap);
115
116 case ConvertLayerParam::Tilemap:
117 return
118 ctx->checkFlags(ContextFlags::ActiveLayerIsImage) &&
119 !ctx->checkFlags(ContextFlags::ActiveLayerIsTilemap) &&
120 // TODO add support for background tliemaps
121 !ctx->checkFlags(ContextFlags::ActiveLayerIsBackground);
122
123 default:
124 return false;
125 }
126}
127
128void ConvertLayerCommand::onExecute(Context* ctx)
129{
130 ContextWriter writer(ctx);
131 Doc* document(writer.document());
132 {
133 Tx tx(ctx, friendlyName());
134 Site site = ctx->activeSite();
135 Sprite* sprite = site.sprite();
136 Layer* srcLayer = site.layer();
137
138 switch (params().to()) {
139
140 case ConvertLayerParam::Background:
141 // Layer -> Background
142 if (srcLayer->isTransparent()) {
143 ASSERT(srcLayer->isImage());
144 tx(new cmd::BackgroundFromLayer(static_cast<LayerImage*>(srcLayer)));
145 }
146 // Tilemap -> Background
147 else if (srcLayer->isTilemap()) {
148 auto newLayer = new LayerImage(sprite);
149 newLayer->configureAsBackground();
150 newLayer->setName(Strings::commands_NewFile_BackgroundLayer());
151 newLayer->setContinuous(srcLayer->isContinuous());
152 newLayer->setUserData(srcLayer->userData());
153 tx(new cmd::AddLayer(srcLayer->parent(), newLayer, srcLayer));
154
155 CelList srcCels;
156 srcLayer->getCels(srcCels);
157 for (Cel* srcCel : srcCels)
158 create_cel_copy(tx, srcCel, sprite, newLayer, srcCel->frame());
159
160 tx(new cmd::RemoveLayer(srcLayer));
161 }
162 break;
163
164 case ConvertLayerParam::Layer:
165 // Background -> Layer
166 if (srcLayer->isBackground()) {
167 tx(new cmd::LayerFromBackground(srcLayer));
168 }
169 // Tilemap -> Layer
170 else if (srcLayer->isTilemap()) {
171 auto newLayer = new LayerImage(sprite);
172 newLayer->setName(srcLayer->name());
173 newLayer->setContinuous(srcLayer->isContinuous());
174 newLayer->setBlendMode(static_cast<LayerImage*>(srcLayer)->blendMode());
175 newLayer->setOpacity(static_cast<LayerImage*>(srcLayer)->opacity());
176 newLayer->setUserData(srcLayer->userData());
177 tx(new cmd::AddLayer(srcLayer->parent(), newLayer, srcLayer));
178
179 copyCels(tx, srcLayer, newLayer);
180
181 tx(new cmd::RemoveLayer(srcLayer));
182 }
183 break;
184
185 case ConvertLayerParam::Tilemap:
186 // Background or Transparent Layer -> Tilemap
187 if (srcLayer->isImage() &&
188 (srcLayer->isBackground() ||
189 srcLayer->isTransparent())) {
190 Grid grid0 = site.grid();
191 grid0.origin(gfx::Point(0, 0));
192 auto tileset = new Tileset(sprite, grid0, 1);
193
194 auto addTileset = new cmd::AddTileset(sprite, tileset);
195 tx(addTileset);
196 tileset_index tsi = addTileset->tilesetIndex();
197
198 auto newLayer = new LayerTilemap(sprite, tsi);
199 newLayer->setName(srcLayer->name());
200 newLayer->setContinuous(srcLayer->isContinuous());
201 newLayer->setBlendMode(static_cast<LayerImage*>(srcLayer)->blendMode());
202 newLayer->setOpacity(static_cast<LayerImage*>(srcLayer)->opacity());
203 newLayer->setUserData(srcLayer->userData());
204 tx(new cmd::AddLayer(srcLayer->parent(), newLayer, srcLayer));
205
206 copyCels(tx, srcLayer, newLayer);
207
208 tx(new cmd::RemoveLayer(srcLayer));
209 }
210 break;
211 }
212
213 tx.commit();
214 }
215
216#ifdef ENABLE_UI
217 if (ctx->isUIAvailable())
218 update_screen_for_document(document);
219#endif
220}
221
222void ConvertLayerCommand::copyCels(Tx& tx,
223 Layer* srcLayer,
224 Layer* newLayer)
225{
226 std::map<doc::ObjectId, doc::Cel*> linkedCels;
227
228 CelList srcCels;
229 srcLayer->getCels(srcCels);
230 for (Cel* srcCel : srcCels) {
231 frame_t frame = srcCel->frame();
232
233 // Keep linked cels in the new layer
234 Cel* linkedSrcCel = srcCel->link();
235 if (linkedSrcCel) {
236 auto it = linkedCels.find(linkedSrcCel->id());
237 if (it != linkedCels.end()) {
238 tx(new cmd::CopyCel(
239 newLayer, linkedSrcCel->frame(),
240 newLayer, frame, true));
241 continue;
242 }
243 }
244
245 Cel* newCel = create_cel_copy(tx, srcCel, srcLayer->sprite(), newLayer, frame);
246 tx(new cmd::AddCel(newLayer, newCel));
247
248 linkedCels[srcCel->id()] = newCel;
249 }
250}
251
252std::string ConvertLayerCommand::onGetFriendlyName() const
253{
254 switch (params().to()) {
255 case ConvertLayerParam::Background: return Strings::commands_ConvertLayer_Background(); break;
256 case ConvertLayerParam::Layer: return Strings::commands_ConvertLayer_Layer(); break;
257 case ConvertLayerParam::Tilemap: return Strings::commands_ConvertLayer_Tilemap(); break;
258 default: return getBaseFriendlyName();
259 }
260}
261
262Command* CommandFactory::createConvertLayerCommand()
263{
264 return new ConvertLayerCommand;
265}
266
267} // namespace app
268