1 // Aseprite
2 // Copyright (C) 2015-2018 David Capello
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/set_mask.h"
12 #include "app/commands/command.h"
13 #include "app/commands/params.h"
14 #include "app/context_access.h"
15 #include "app/doc.h"
16 #include "app/i18n/strings.h"
17 #include "app/modules/gui.h"
18 #include "app/pref/preferences.h"
19 #include "app/transaction.h"
20 #include "base/convert_to.h"
21 #include "doc/algorithm/modify_selection.h"
22 #include "doc/brush_type.h"
23 #include "doc/mask.h"
24 #include "filters/neighboring_pixels.h"
25 #include "fmt/format.h"
26
27 #include "modify_selection.xml.h"
28
29 #include <limits>
30
31 namespace app {
32
33 using namespace doc;
34 typedef doc::algorithm::SelectionModifier Modifier;
35
36 class ModifySelectionWindow : public app::gen::ModifySelection {
37 };
38
39 class ModifySelectionCommand : public Command {
40 public:
41 ModifySelectionCommand();
clone() const42 Command* clone() const override { return new ModifySelectionCommand(*this); }
43
44 protected:
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
50 private:
51 std::string getActionName() const;
52
53 Modifier m_modifier;
54 int m_quantity;
55 doc::BrushType m_brushType;
56 };
57
ModifySelectionCommand()58 ModifySelectionCommand::ModifySelectionCommand()
59 : Command(CommandId::ModifySelection(), CmdRecordableFlag)
60 , m_modifier(Modifier::Expand)
61 , m_quantity(0)
62 , m_brushType(doc::kCircleBrushType)
63 {
64 }
65
onLoadParams(const Params & params)66 void 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
onEnabled(Context * context)81 bool ModifySelectionCommand::onEnabled(Context* context)
82 {
83 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
84 ContextFlags::HasVisibleMask);
85 }
86
onExecute(Context * context)87 void 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 = MID(1, quantity, 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 base::UniquePtr<Mask> mask(new Mask);
132 {
133 mask->reserve(sprite->bounds());
134 mask->freeze();
135 doc::algorithm::modify_selection(
136 m_modifier, document->mask(), mask, quantity, brush);
137 mask->unfreeze();
138 }
139
140 // Set the new mask
141 Transaction transaction(writer.context(),
142 friendlyName(),
143 DoesntModifyDocument);
144 transaction.execute(new cmd::SetMask(document, mask));
145 transaction.commit();
146
147 document->generateMaskBoundaries();
148 update_screen_for_document(document);
149 }
150
onGetFriendlyName() const151 std::string ModifySelectionCommand::onGetFriendlyName() const
152 {
153 std::string quantity;
154 if (m_quantity > 0)
155 quantity = fmt::format(Strings::commands_ModifySelection_Quantity(), m_quantity);
156
157 return fmt::format(getBaseFriendlyName(),
158 getActionName(),
159 quantity);
160 }
161
getActionName() const162 std::string ModifySelectionCommand::getActionName() const
163 {
164 switch (m_modifier) {
165 case Modifier::Border: return Strings::commands_ModifySelection_Border();
166 case Modifier::Expand: return Strings::commands_ModifySelection_Expand();
167 case Modifier::Contract: return Strings::commands_ModifySelection_Contract();
168 default: return Strings::commands_ModifySelection_Modify();
169 }
170 }
171
createModifySelectionCommand()172 Command* CommandFactory::createModifySelectionCommand()
173 {
174 return new ModifySelectionCommand;
175 }
176
177 } // namespace app
178