1// Aseprite
2// Copyright (C) 2019-2022 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_tileset.h"
14#include "app/cmd/clear_mask.h"
15#include "app/cmd/move_layer.h"
16#include "app/cmd/trim_cel.h"
17#include "app/commands/command.h"
18#include "app/commands/commands.h"
19#include "app/commands/new_params.h"
20#include "app/commands/params.h"
21#include "app/context_access.h"
22#include "app/doc_api.h"
23#include "app/find_widget.h"
24#include "app/i18n/strings.h"
25#include "app/load_widget.h"
26#include "app/modules/gui.h"
27#include "app/pref/preferences.h"
28#include "app/restore_visible_layers.h"
29#include "app/tx.h"
30#include "app/ui/main_window.h"
31#include "app/ui/status_bar.h"
32#include "app/ui/tileset_selector.h"
33#include "app/ui_context.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 "doc/layer.h"
38#include "doc/layer_tilemap.h"
39#include "doc/primitives.h"
40#include "doc/sprite.h"
41#include "fmt/format.h"
42#include "render/dithering.h"
43#include "render/ordered_dither.h"
44#include "render/quantization.h"
45#include "render/render.h"
46#include "ui/ui.h"
47
48#include "new_layer.xml.h"
49
50#include <algorithm>
51#include <cstring>
52#include <string>
53#include <cstdlib>
54
55namespace app {
56
57using namespace ui;
58
59struct NewLayerParams : public NewParams {
60 Param<std::string> name { this, std::string(), "name" };
61 Param<bool> group { this, false, "group" };
62 Param<bool> reference { this, false, "reference" };
63 Param<bool> tilemap { this, false, "tilemap" };
64 Param<bool> ask { this, false, "ask" };
65 Param<bool> fromFile { this, false, { "fromFile", "from-file" } };
66 Param<bool> fromClipboard { this, false, "fromClipboard" };
67 Param<bool> viaCut { this, false, "viaCut" };
68 Param<bool> viaCopy { this, false, "viaCopy" };
69 Param<bool> top { this, false, "top" };
70 Param<bool> before { this, false, "before" };
71};
72
73class NewLayerCommand : public CommandWithNewParams<NewLayerParams> {
74public:
75 enum class Type { Layer, Group, ReferenceLayer, TilemapLayer };
76 enum class Place { AfterActiveLayer, BeforeActiveLayer, Top };
77
78 NewLayerCommand();
79
80protected:
81 void onLoadParams(const Params& params) override;
82 bool onEnabled(Context* context) override;
83 void onExecute(Context* context) override;
84 std::string onGetFriendlyName() const override;
85
86private:
87 void adjustRefCelBounds(Cel* cel, gfx::RectF bounds);
88 std::string getUniqueLayerName(const Sprite* sprite) const;
89 std::string getUniqueTilesetName(const Sprite* sprite) const;
90 int getMaxLayerNum(const Layer* layer) const;
91 std::string layerPrefix() const;
92
93 Type m_type;
94 Place m_place;
95};
96
97NewLayerCommand::NewLayerCommand()
98 : CommandWithNewParams(CommandId::NewLayer(), CmdRecordableFlag)
99{
100}
101
102void NewLayerCommand::onLoadParams(const Params& commandParams)
103{
104 CommandWithNewParams<NewLayerParams>::onLoadParams(commandParams);
105
106 m_type = Type::Layer;
107 if (params().group())
108 m_type = Type::Group;
109 else if (params().reference())
110 m_type = Type::ReferenceLayer;
111 else if (params().tilemap())
112 m_type = Type::TilemapLayer;
113 else
114 m_type = Type::Layer;
115
116 m_place = Place::AfterActiveLayer;
117 if (params().top())
118 m_place = Place::Top;
119 else if (params().before())
120 m_place = Place::BeforeActiveLayer;
121}
122
123bool NewLayerCommand::onEnabled(Context* ctx)
124{
125 if (!ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
126 ContextFlags::HasActiveSprite))
127 return false;
128
129#ifdef ENABLE_UI
130 if (params().fromClipboard() &&
131 ctx->clipboard()->format() != ClipboardFormat::Image)
132 return false;
133#endif
134
135 if ((params().viaCut() ||
136 params().viaCopy()) &&
137 !ctx->checkFlags(ContextFlags::HasVisibleMask))
138 return false;
139
140 return true;
141}
142
143namespace {
144class Scoped { // TODO move this to base library
145public:
146 Scoped(const std::function<void()>& func) : m_func(func) { }
147 ~Scoped() { m_func(); }
148private:
149 std::function<void()> m_func;
150};
151}
152
153void NewLayerCommand::onExecute(Context* context)
154{
155 ContextReader reader(context);
156 Site site = context->activeSite();
157 Doc* document(reader.document());
158 Sprite* sprite(reader.sprite());
159 std::string name;
160
161#if ENABLE_UI
162 // Show the tooltip feedback only if we are not inside a transaction
163 // (e.g. we can be already in a transaction if we are running in a
164 // Lua script app.transaction()).
165 const bool showTooltip = (document->transaction() == nullptr);
166#endif
167
168 Doc* pasteDoc = nullptr;
169 Scoped destroyPasteDoc(
170 [&pasteDoc, context]{
171 if (pasteDoc) {
172 DocDestroyer destroyer(context, pasteDoc, 100);
173 destroyer.destroyDocument();
174 }
175 });
176
177 // Default name
178 if (params().name.isSet())
179 name = params().name();
180 else
181 name = getUniqueLayerName(sprite);
182
183 // Select a file to copy its content
184 if (params().fromFile()) {
185 Doc* oldActiveDocument = context->activeDocument();
186 Command* openFile = Commands::instance()->byId(CommandId::OpenFile());
187 Params params;
188 params.set("filename", "");
189 context->executeCommand(openFile, params);
190
191 // The user have selected another document.
192 if (oldActiveDocument != context->activeDocument()) {
193 pasteDoc = context->activeDocument();
194 static_cast<UIContext*>(context)
195 ->setActiveDocument(oldActiveDocument);
196 }
197 // If the user didn't selected a new document, it means that the
198 // file selector dialog was canceled.
199 else
200 return;
201 }
202
203 // Information about the tileset to be used for new tilemaps
204 TilesetSelector::Info tilesetInfo;
205 tilesetInfo.newTileset = true;
206 tilesetInfo.grid = context->activeSite().grid();
207 tilesetInfo.baseIndex = 1;
208
209#ifdef ENABLE_UI
210 // If params specify to ask the user about the name...
211 if (params().ask() && context->isUIAvailable()) {
212 auto& pref = Preferences::instance();
213 tilesetInfo.baseIndex = pref.tileset.baseIndex();
214
215 // We open the window to ask the name
216 app::gen::NewLayer window;
217 TilesetSelector* tilesetSelector = nullptr;
218 window.name()->setText(name.c_str());
219 window.name()->setMinSize(gfx::Size(128, 0));
220
221 // Tileset selector for new tilemaps
222 const bool isTilemap = (m_type == Type::TilemapLayer);
223 window.tilesetLabel()->setVisible(isTilemap);
224 window.tilesetOptions()->setVisible(isTilemap);
225 if (isTilemap) {
226 tilesetSelector = new TilesetSelector(sprite, tilesetInfo);
227 window.tilesetOptions()->addChild(tilesetSelector);
228 }
229
230 window.openWindowInForeground();
231 if (window.closer() != window.ok())
232 return;
233
234 pref.tileset.baseIndex(tilesetSelector->getInfo().baseIndex);
235
236 name = window.name()->text();
237 if (tilesetSelector)
238 tilesetInfo = tilesetSelector->getInfo();
239 }
240#endif
241
242 ContextWriter writer(reader);
243 LayerGroup* parent = sprite->root();
244 Layer* activeLayer = writer.layer();
245 SelectedLayers selLayers = site.selectedLayers();
246 if (activeLayer) {
247 if (activeLayer->isGroup() &&
248 activeLayer->isExpanded() &&
249 m_type != Type::Group) {
250 parent = static_cast<LayerGroup*>(activeLayer);
251 activeLayer = nullptr;
252 }
253 else {
254 parent = activeLayer->parent();
255 }
256 }
257
258 Layer* layer = nullptr;
259 {
260 Tx tx(
261 writer.context(),
262 fmt::format(Strings::commands_NewLayer(), layerPrefix()));
263 DocApi api = document->getApi(tx);
264 bool afterBackground = false;
265
266 switch (m_type) {
267 case Type::Layer:
268 layer = api.newLayer(parent, name);
269 if (m_place == Place::BeforeActiveLayer)
270 api.restackLayerBefore(layer, parent, activeLayer);
271 break;
272 case Type::Group:
273 layer = api.newGroup(parent, name);
274 break;
275 case Type::ReferenceLayer:
276 layer = api.newLayer(parent, name);
277 if (layer)
278 layer->setReference(true);
279 afterBackground = true;
280 break;
281 case Type::TilemapLayer: {
282 tileset_index tsi;
283 if (tilesetInfo.newTileset) {
284 auto tileset = new Tileset(sprite, tilesetInfo.grid, 1);
285 tileset->setBaseIndex(tilesetInfo.baseIndex);
286 tileset->setName(tilesetInfo.name);
287
288 auto addTileset = new cmd::AddTileset(sprite, tileset);
289 tx(addTileset);
290
291 tsi = addTileset->tilesetIndex();
292 }
293 else {
294 tsi = tilesetInfo.tsi;
295 }
296
297 layer = new LayerTilemap(sprite, tsi);
298 layer->setName(name);
299 api.addLayer(parent, layer, parent->lastLayer());
300 break;
301 }
302 }
303
304 ASSERT(layer);
305 if (!layer)
306 return;
307
308 ASSERT(layer->parent());
309
310 // Put new layer as an overlay of the background or in the first
311 // layer in case the sprite is transparent.
312 if (afterBackground) {
313 Layer* first = sprite->root()->firstLayer();
314 if (first) {
315 if (first->isBackground())
316 api.restackLayerAfter(layer, sprite->root(), first);
317 else
318 api.restackLayerBefore(layer, sprite->root(), first);
319 }
320 }
321 // Move the layer above the active one.
322 else if (activeLayer && m_place == Place::AfterActiveLayer) {
323 api.restackLayerAfter(layer,
324 activeLayer->parent(),
325 activeLayer);
326 }
327
328 // Put all selected layers inside the group
329 if (m_type == Type::Group && site.inTimeline()) {
330 LayerGroup* commonParent = nullptr;
331 layer_t sameParents = 0;
332 for (Layer* l : selLayers) {
333 if (!commonParent ||
334 commonParent == l->parent()) {
335 commonParent = l->parent();
336 ++sameParents;
337 }
338 }
339
340 if (sameParents == selLayers.size()) {
341 for (Layer* newChild : selLayers.toBrowsableLayerList()) {
342 tx(
343 new cmd::MoveLayer(newChild, layer,
344 static_cast<LayerGroup*>(layer)->lastLayer()));
345 }
346 }
347 }
348
349 // Paste sprite content
350 if (pasteDoc && layer->isImage()) {
351 Sprite* pasteSpr = pasteDoc->sprite();
352 render::Render render;
353 render.setNewBlend(true);
354 render.setBgOptions(render::BgOptions::MakeNone());
355
356 // Add more frames at the end
357 if (writer.frame()+pasteSpr->lastFrame() > sprite->lastFrame())
358 api.addEmptyFramesTo(sprite, writer.frame()+pasteSpr->lastFrame());
359
360 // Paste the given sprite as flatten
361 for (frame_t fr=0; fr<=pasteSpr->lastFrame(); ++fr) {
362 ImageRef pasteImage(
363 Image::create(
364 pasteSpr->pixelFormat(),
365 pasteSpr->width(),
366 pasteSpr->height()));
367 clear_image(pasteImage.get(),
368 pasteSpr->transparentColor());
369 render.renderSprite(pasteImage.get(), pasteSpr, fr);
370
371 frame_t dstFrame = writer.frame()+fr;
372
373 if (sprite->pixelFormat() != pasteSpr->pixelFormat() ||
374 sprite->pixelFormat() == IMAGE_INDEXED) {
375 ImageRef pasteImageConv(
376 render::convert_pixel_format(
377 pasteImage.get(),
378 nullptr,
379 sprite->pixelFormat(),
380 render::Dithering(),
381 sprite->rgbMap(dstFrame),
382 pasteSpr->palette(fr),
383 (pasteSpr->backgroundLayer() ? true: false),
384 sprite->transparentColor()));
385 if (pasteImageConv)
386 pasteImage = pasteImageConv;
387 }
388
389 Cel* cel = layer->cel(dstFrame);
390 if (cel) {
391 api.replaceImage(sprite, cel->imageRef(), pasteImage);
392 }
393 else {
394 cel = api.addCel(static_cast<LayerImage*>(layer),
395 dstFrame, pasteImage);
396 }
397
398 if (cel) {
399 if (layer->isReference()) {
400 adjustRefCelBounds(
401 cel, gfx::RectF(0, 0, pasteSpr->width(), pasteSpr->height()));
402 }
403 else {
404 cel->setPosition(sprite->width()/2 - pasteSpr->width()/2,
405 sprite->height()/2 - pasteSpr->height()/2);
406 }
407 }
408 }
409 }
410#ifdef ENABLE_UI
411 // Paste new layer from clipboard
412 else if (params().fromClipboard() && layer->isImage()) {
413 context->clipboard()->paste(context, false);
414
415 if (layer->isReference()) {
416 if (Cel* cel = layer->cel(site.frame())) {
417 adjustRefCelBounds(
418 cel, cel->boundsF());
419 }
420 }
421 }
422#endif // ENABLE_UI
423 // Paste new layer from selection
424 else if ((params().viaCut() || params().viaCopy())
425 && document->isMaskVisible()) {
426 const doc::Mask* mask = document->mask();
427 ASSERT(mask);
428
429 RestoreVisibleLayers restore;
430 SelectedLayers layers;
431 SelectedFrames frames;
432 bool merged;
433 if (site.range().enabled()) {
434 merged = true;
435 layers = site.range().selectedLayers();
436 frames = site.range().selectedFrames();
437 restore.showSelectedLayers(site.sprite(), layers);
438 }
439 else {
440 merged = false;
441 layers.insert(site.layer());
442 frames.insert(site.frame());
443 }
444
445 for (frame_t frame : frames) {
446 ImageRef newImage(new_image_from_mask(site, mask, true, merged));
447 if (!newImage)
448 continue;
449
450 Cel* newCel = api.addCel(static_cast<LayerImage*>(layer),
451 frame, newImage);
452 if (newCel) {
453 gfx::Point pos = mask->bounds().origin();
454 newCel->setPosition(pos.x, pos.y);
455 }
456
457 for (Layer* layer : layers) {
458 if (!layer->isImage() ||
459 !layer->isEditable()) // Locked layers will not be modified
460 continue;
461
462 Cel* origCel = layer->cel(site.frame());
463 if (origCel &&
464 params().viaCut()) {
465 tx(new cmd::ClearMask(origCel));
466
467 if (layer->isTransparent()) {
468 // If the cel wasn't deleted by cmd::ClearMask, we trim it.
469 origCel = layer->cel(frame);
470 if (site.shouldTrimCel(origCel))
471 tx(new cmd::TrimCel(origCel));
472 }
473 }
474 }
475 }
476 }
477
478 tx.commit();
479 }
480
481#ifdef ENABLE_UI
482 if (context->isUIAvailable() && showTooltip) {
483 update_screen_for_document(document);
484
485 StatusBar::instance()->showTip(
486 1000, fmt::format("{} '{}' created",
487 layerPrefix(),
488 name));
489
490 App::instance()->mainWindow()->popTimeline();
491 }
492#endif
493}
494
495std::string NewLayerCommand::onGetFriendlyName() const
496{
497 std::string text;
498 if (m_place == Place::BeforeActiveLayer)
499 text = fmt::format(Strings::commands_NewLayer_BeforeActiveLayer(), layerPrefix());
500 else
501 text = fmt::format(Strings::commands_NewLayer(), layerPrefix());
502 if (params().fromClipboard())
503 text = fmt::format(Strings::commands_NewLayer_FromClipboard(), text);
504 if (params().viaCopy())
505 text = fmt::format(Strings::commands_NewLayer_ViaCopy(), text);
506 if (params().viaCut())
507 text = fmt::format(Strings::commands_NewLayer_ViaCut(), text);
508 if (params().ask())
509 text = fmt::format(Strings::commands_NewLayer_WithDialog(), text);
510 return text;
511}
512
513void NewLayerCommand::adjustRefCelBounds(Cel* cel, gfx::RectF bounds)
514{
515 Sprite* sprite = cel->sprite();
516 double scale = std::min(double(sprite->width()) / bounds.w,
517 double(sprite->height()) / bounds.h);
518 bounds.w *= scale;
519 bounds.h *= scale;
520 bounds.x = sprite->width()/2 - bounds.w/2;
521 bounds.y = sprite->height()/2 - bounds.h/2;
522 cel->setBoundsF(bounds);
523}
524
525std::string NewLayerCommand::getUniqueLayerName(const Sprite* sprite) const
526{
527 return fmt::format("{} {}",
528 layerPrefix(),
529 getMaxLayerNum(sprite->root())+1);
530}
531
532std::string NewLayerCommand::getUniqueTilesetName(const Sprite* sprite) const
533{
534 return fmt::format("{} {}",
535 Strings::instance()->tileset_selector_default_name(),
536 sprite->tilesets()->size()+1);
537}
538
539int NewLayerCommand::getMaxLayerNum(const Layer* layer) const
540{
541 std::string prefix = layerPrefix();
542 prefix += " ";
543
544 int max = 0;
545 if (std::strncmp(layer->name().c_str(), prefix.c_str(), prefix.size()) == 0)
546 max = std::strtol(layer->name().c_str()+prefix.size(), NULL, 10);
547
548 if (layer->isGroup()) {
549 for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers()) {
550 int tmp = getMaxLayerNum(child);
551 max = std::max(tmp, max);
552 }
553 }
554
555 return max;
556}
557
558std::string NewLayerCommand::layerPrefix() const
559{
560 switch (m_type) {
561 case Type::Layer: return Strings::commands_NewLayer_Layer();
562 case Type::Group: return Strings::commands_NewLayer_Group();
563 case Type::ReferenceLayer: return Strings::commands_NewLayer_ReferenceLayer();
564 case Type::TilemapLayer: return Strings::commands_NewLayer_TilemapLayer();
565 }
566 return "Unknown";
567}
568
569Command* CommandFactory::createNewLayerCommand()
570{
571 return new NewLayerCommand;
572}
573
574} // namespace app
575