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 | |
36 | namespace app { |
37 | |
38 | enum class ConvertLayerParam { None, Background, Layer, Tilemap }; |
39 | |
40 | template<> |
41 | void 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 |
54 | template<> |
55 | void Param<ConvertLayerParam>::fromLua(lua_State* L, int index) |
56 | { |
57 | fromString(lua_tostring(L, index)); |
58 | } |
59 | #endif // ENABLE_SCRIPTING |
60 | |
61 | struct ConvertLayerParams : public NewParams { |
62 | Param<ConvertLayerParam> to { this, ConvertLayerParam::None, "to" }; |
63 | }; |
64 | |
65 | class ConvertLayerCommand : public CommandWithNewParams<ConvertLayerParams> { |
66 | public: |
67 | ConvertLayerCommand(); |
68 | |
69 | private: |
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 | |
79 | ConvertLayerCommand::ConvertLayerCommand() |
80 | : CommandWithNewParams<ConvertLayerParams>(CommandId::ConvertLayer(), CmdRecordableFlag) |
81 | { |
82 | } |
83 | |
84 | bool 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 | |
128 | void 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 | |
222 | void 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 | |
252 | std::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 | |
262 | Command* CommandFactory::createConvertLayerCommand() |
263 | { |
264 | return new ConvertLayerCommand; |
265 | } |
266 | |
267 | } // namespace app |
268 | |