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 | |
32 | namespace app { |
33 | |
34 | using namespace doc; |
35 | typedef doc::algorithm::SelectionModifier Modifier; |
36 | |
37 | class ModifySelectionWindow : public app::gen::ModifySelection { |
38 | }; |
39 | |
40 | class ModifySelectionCommand : public Command { |
41 | public: |
42 | ModifySelectionCommand(); |
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 | |
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 | |
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 | |
81 | bool ModifySelectionCommand::onEnabled(Context* context) |
82 | { |
83 | return context->checkFlags(ContextFlags::ActiveDocumentIsWritable | |
84 | ContextFlags::HasVisibleMask); |
85 | } |
86 | |
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 = 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 | |
150 | std::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 | |
161 | std::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 | |
171 | Command* CommandFactory::createModifySelectionCommand() |
172 | { |
173 | return new ModifySelectionCommand; |
174 | } |
175 | |
176 | } // namespace app |
177 | |