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