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/cmd/flatten_layers.h"
13
14#include "app/cmd/add_layer.h"
15#include "app/cmd/configure_background.h"
16#include "app/cmd/copy_rect.h"
17#include "app/cmd/move_layer.h"
18#include "app/cmd/remove_layer.h"
19#include "app/cmd/set_layer_flags.h"
20#include "app/cmd/set_layer_name.h"
21#include "app/cmd/unlink_cel.h"
22#include "app/doc.h"
23#include "app/i18n/strings.h"
24#include "app/restore_visible_layers.h"
25#include "doc/algorithm/shrink_bounds.h"
26#include "doc/cel.h"
27#include "doc/layer.h"
28#include "doc/primitives.h"
29#include "doc/sprite.h"
30#include "render/render.h"
31
32namespace app {
33namespace cmd {
34
35FlattenLayers::FlattenLayers(doc::Sprite* sprite,
36 const doc::SelectedLayers& layers0,
37 const bool newBlend)
38 : WithSprite(sprite)
39{
40 m_newBlendMethod = newBlend;
41 doc::SelectedLayers layers(layers0);
42 layers.removeChildrenIfParentIsSelected();
43
44 m_layerIds.reserve(layers.size());
45 for (auto layer : layers)
46 m_layerIds.push_back(layer->id());
47}
48
49void FlattenLayers::onExecute()
50{
51 Sprite* sprite = this->sprite();
52 auto doc = static_cast<Doc*>(sprite->document());
53
54 // Set of layers to be flattened.
55 bool backgroundIsSel = false;
56 SelectedLayers layers;
57 for (auto layerId : m_layerIds) {
58 doc::Layer* layer = doc::get<doc::Layer>(layerId);
59 ASSERT(layer);
60 layers.insert(layer);
61 if (layer->isBackground())
62 backgroundIsSel = true;
63 }
64
65 LayerList list = layers.toBrowsableLayerList();
66 if (list.empty())
67 return; // Do nothing
68
69 // Create a temporary image.
70 ImageRef image(Image::create(sprite->spec()));
71
72 LayerImage* flatLayer; // The layer onto which everything will be flattened.
73 color_t bgcolor; // The background color to use for flatLayer.
74 bool newFlatLayer = false;
75
76 flatLayer = sprite->backgroundLayer();
77 if (backgroundIsSel && flatLayer && flatLayer->isVisible()) {
78 // There exists a visible background layer, so we will flatten onto that.
79 bgcolor = doc->bgColor(flatLayer);
80 }
81 else {
82 // Create a new transparent layer to flatten everything onto it.
83 flatLayer = new LayerImage(sprite);
84 ASSERT(flatLayer->isVisible());
85 flatLayer->setName(Strings::layer_properties_flattened());
86 newFlatLayer = true;
87 bgcolor = sprite->transparentColor();
88 }
89
90 render::Render render;
91 render.setNewBlend(m_newBlendMethod);
92 render.setBgOptions(render::BgOptions::MakeNone());
93
94 {
95 // Show only the layers to be flattened so other layers are hidden
96 // temporarily.
97 RestoreVisibleLayers restore;
98 restore.showSelectedLayers(sprite, layers);
99
100 // Copy all frames to the background.
101 for (frame_t frame(0); frame<sprite->totalFrames(); ++frame) {
102 // Clear the image and render this frame.
103 clear_image(image.get(), bgcolor);
104 render.renderSprite(image.get(), sprite, frame);
105
106 // TODO Keep cel links when possible
107
108 ImageRef cel_image;
109 Cel* cel = flatLayer->cel(frame);
110 if (cel) {
111 if (cel->links())
112 executeAndAdd(new cmd::UnlinkCel(cel));
113
114 cel_image = cel->imageRef();
115 ASSERT(cel_image);
116
117 executeAndAdd(
118 new cmd::CopyRect(cel_image.get(), image.get(),
119 gfx::Clip(0, 0, image->bounds())));
120 }
121 else {
122 gfx::Rect bounds(image->bounds());
123 if (doc::algorithm::shrink_bounds(
124 image.get(), image->maskColor(), nullptr, bounds)) {
125 cel_image.reset(
126 doc::crop_image(image.get(), bounds, image->maskColor()));
127 cel = new Cel(frame, cel_image);
128 cel->setPosition(bounds.origin());
129 flatLayer->addCel(cel);
130 }
131 }
132 }
133 }
134
135 // Add new flatten layer
136 if (newFlatLayer)
137 executeAndAdd(new cmd::AddLayer(list.front()->parent(), flatLayer, list.front()));
138
139 // Delete flattened layers.
140 for (Layer* layer : layers) {
141 // layer can be == flatLayer when we are flattening on the
142 // background layer.
143 if (layer != flatLayer) {
144 executeAndAdd(new cmd::RemoveLayer(layer));
145 }
146 }
147}
148
149} // namespace cmd
150} // namespace app
151