| 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 | |
| 32 | namespace app { |
| 33 | namespace cmd { |
| 34 | |
| 35 | FlattenLayers::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 | |
| 49 | void 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 | |