1// Aseprite
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2015-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/set_mask.h"
13#include "app/commands/command.h"
14#include "app/commands/params.h"
15#include "app/context_access.h"
16#include "app/doc.h"
17#include "app/i18n/strings.h"
18#include "app/modules/gui.h"
19#include "app/pref/preferences.h"
20#include "app/tx.h"
21#include "base/convert_to.h"
22#include "doc/algorithm/modify_selection.h"
23#include "doc/brush_type.h"
24#include "doc/mask.h"
25#include "filters/neighboring_pixels.h"
26#include "fmt/format.h"
27
28#include "modify_selection.xml.h"
29
30#include <limits>
31
32namespace app {
33
34using namespace doc;
35typedef doc::algorithm::SelectionModifier Modifier;
36
37class ModifySelectionWindow : public app::gen::ModifySelection {
38};
39
40class ModifySelectionCommand : public Command {
41public:
42 ModifySelectionCommand();
43
44protected:
45 void onLoadParams(const Params& params) override;
46 bool onEnabled(Context* context) override;
47 void onExecute(Context* context) override;
48 std::string onGetFriendlyName() const override;
49
50private:
51 std::string getActionName() const;
52
53 Modifier m_modifier;
54 int m_quantity;
55 doc::BrushType m_brushType;
56};
57
58ModifySelectionCommand::ModifySelectionCommand()
59 : Command(CommandId::ModifySelection(), CmdRecordableFlag)
60 , m_modifier(Modifier::Expand)
61 , m_quantity(0)
62 , m_brushType(doc::kCircleBrushType)
63{
64}
65
66void ModifySelectionCommand::onLoadParams(const Params& params)
67{
68 const std::string modifier = params.get("modifier");
69 if (modifier == "border") m_modifier = Modifier::Border;
70 else if (modifier == "expand") m_modifier = Modifier::Expand;
71 else if (modifier == "contract") m_modifier = Modifier::Contract;
72
73 const int quantity = params.get_as<int>("quantity");
74 m_quantity = std::max<int>(0, quantity);
75
76 const std::string brush = params.get("brush");
77 if (brush == "circle") m_brushType = doc::kCircleBrushType;
78 else if (brush == "square") m_brushType = doc::kSquareBrushType;
79}
80
81bool ModifySelectionCommand::onEnabled(Context* context)
82{
83 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
84 ContextFlags::HasVisibleMask);
85}
86
87void ModifySelectionCommand::onExecute(Context* context)
88{
89 int quantity = m_quantity;
90 doc::BrushType brush = m_brushType;
91
92 if (quantity == 0) {
93 Preferences& pref = Preferences::instance();
94 ModifySelectionWindow window;
95
96 window.setText(getActionName() + " Selection");
97 if (m_modifier == Modifier::Border)
98 window.byLabel()->setText("Width:");
99 else
100 window.byLabel()->setText(getActionName() + " By:");
101
102 window.quantity()->setTextf("%d", pref.selection.modifySelectionQuantity());
103
104 brush = (pref.selection.modifySelectionBrush() == app::gen::BrushType::CIRCLE
105 ? doc::kCircleBrushType:
106 doc::kSquareBrushType);
107 window.circle()->setSelected(brush == doc::kCircleBrushType);
108 window.square()->setSelected(brush == doc::kSquareBrushType);
109
110 window.openWindowInForeground();
111 if (window.closer() != window.ok())
112 return;
113
114 quantity = window.quantity()->textInt();
115 quantity = std::clamp(quantity, 1, 100);
116
117 brush = (window.circle()->isSelected() ? doc::kCircleBrushType:
118 doc::kSquareBrushType);
119
120 pref.selection.modifySelectionQuantity(quantity);
121 pref.selection.modifySelectionBrush(
122 (brush == doc::kCircleBrushType ? app::gen::BrushType::CIRCLE:
123 app::gen::BrushType::SQUARE));
124 }
125
126 // Lock sprite
127 ContextWriter writer(context);
128 Doc* document(writer.document());
129 Sprite* sprite(writer.sprite());
130
131 std::unique_ptr<Mask> mask(new Mask);
132 {
133 mask->reserve(sprite->bounds());
134 mask->freeze();
135 doc::algorithm::modify_selection(
136 m_modifier, document->mask(), mask.get(), quantity, brush);
137 mask->unfreeze();
138 }
139
140 // Set the new mask
141 Tx tx(writer.context(),
142 friendlyName(),
143 DoesntModifyDocument);
144 tx(new cmd::SetMask(document, mask.get()));
145 tx.commit();
146
147 update_screen_for_document(document);
148}
149
150std::string ModifySelectionCommand::onGetFriendlyName() const
151{
152 std::string quantity;
153 if (m_quantity > 0)
154 quantity = fmt::format(Strings::commands_ModifySelection_Quantity(), m_quantity);
155
156 return fmt::format(getBaseFriendlyName(),
157 getActionName(),
158 quantity);
159}
160
161std::string ModifySelectionCommand::getActionName() const
162{
163 switch (m_modifier) {
164 case Modifier::Border: return Strings::commands_ModifySelection_Border();
165 case Modifier::Expand: return Strings::commands_ModifySelection_Expand();
166 case Modifier::Contract: return Strings::commands_ModifySelection_Contract();
167 default: return Strings::commands_ModifySelection_Modify();
168 }
169}
170
171Command* CommandFactory::createModifySelectionCommand()
172{
173 return new ModifySelectionCommand;
174}
175
176} // namespace app
177