1 | // Aseprite |
2 | // Copyright (C) 2019-2022 Igara Studio S.A. |
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/color.h" |
12 | #include "app/color_utils.h" |
13 | #include "app/commands/command.h" |
14 | #include "app/commands/filters/filter_manager_impl.h" |
15 | #include "app/commands/filters/filter_window.h" |
16 | #include "app/commands/filters/filter_worker.h" |
17 | #include "app/commands/new_params.h" |
18 | #include "app/context.h" |
19 | #include "app/ini_file.h" |
20 | #include "app/modules/gui.h" |
21 | #include "app/pref/preferences.h" |
22 | #include "app/ui/color_bar.h" |
23 | #include "app/ui/color_button.h" |
24 | #include "app/ui/skin/skin_theme.h" |
25 | #include "doc/image.h" |
26 | #include "doc/mask.h" |
27 | #include "doc/sprite.h" |
28 | #include "filters/outline_filter.h" |
29 | #include "ui/button.h" |
30 | #include "ui/label.h" |
31 | #include "ui/slider.h" |
32 | #include "ui/widget.h" |
33 | #include "ui/window.h" |
34 | |
35 | #include "outline.xml.h" |
36 | |
37 | namespace app { |
38 | |
39 | using namespace app::skin; |
40 | |
41 | enum { CIRCLE, SQUARE, HORZ, VERT }; |
42 | |
43 | struct OutlineParams : public NewParams { |
44 | Param<bool> ui { this, true, "ui" }; |
45 | Param<filters::Target> channels { this, 0, "channels" }; |
46 | Param<filters::OutlineFilter::Place> place { this, OutlineFilter::Place::Outside, "place" }; |
47 | Param<filters::OutlineFilter::Matrix> matrix { this, OutlineFilter::Matrix::Circle, "matrix" }; |
48 | Param<app::Color> color { this, app::Color(), "color" }; |
49 | Param<app::Color> bgColor { this, app::Color(), "bgColor" }; |
50 | Param<filters::TiledMode> tiledMode { this, filters::TiledMode::NONE, "tiledMode" }; |
51 | }; |
52 | |
53 | // Wrapper for ReplaceColorFilter to handle colors in an easy way |
54 | class OutlineFilterWrapper : public OutlineFilter { |
55 | public: |
56 | OutlineFilterWrapper(Layer* layer) : m_layer(layer) { } |
57 | |
58 | void color(const app::Color& color) { |
59 | m_color = color; |
60 | if (m_layer) |
61 | OutlineFilter::color(color_utils::color_for_layer(color, m_layer)); |
62 | } |
63 | |
64 | void bgColor(const app::Color& color) { |
65 | m_bgColor = color; |
66 | if (m_layer) |
67 | OutlineFilter::bgColor(color_utils::color_for_layer(color, m_layer)); |
68 | } |
69 | |
70 | app::Color color() const { return m_color; } |
71 | app::Color bgColor() const { return m_bgColor; } |
72 | |
73 | private: |
74 | Layer* m_layer; |
75 | app::Color m_color; |
76 | app::Color m_bgColor; |
77 | }; |
78 | |
79 | #ifdef ENABLE_UI |
80 | |
81 | static const char* ConfigSection = "Outline" ; |
82 | |
83 | class OutlineWindow : public FilterWindow { |
84 | public: |
85 | OutlineWindow(OutlineFilterWrapper& filter, |
86 | FilterManagerImpl& filterMgr) |
87 | : FilterWindow("Outline" , ConfigSection, &filterMgr, |
88 | WithChannelsSelector, |
89 | WithTiledCheckBox, |
90 | filter.tiledMode()) |
91 | , m_filter(filter) { |
92 | getContainer()->addChild(&m_panel); |
93 | |
94 | m_panel.color()->setColor(m_filter.color()); |
95 | m_panel.bgColor()->setColor(m_filter.bgColor()); |
96 | m_panel.place()->setSelectedItem((int)m_filter.place()); |
97 | updateButtonsFromMatrix(); |
98 | |
99 | m_panel.color()->Change.connect(&OutlineWindow::onColorChange, this); |
100 | m_panel.bgColor()->Change.connect(&OutlineWindow::onBgColorChange, this); |
101 | m_panel.outlineType()->ItemChange.connect( |
102 | [this](ButtonSet::Item*){ |
103 | onMatrixTypeChange(); |
104 | }); |
105 | m_panel.outlineMatrix()->ItemChange.connect( |
106 | [this](ButtonSet::Item* item){ |
107 | onMatrixPixelChange(m_panel.outlineMatrix()->getItemIndex(item)); |
108 | }); |
109 | m_panel.place()->ItemChange.connect( |
110 | [this](ButtonSet::Item*){ |
111 | onPlaceChange((OutlineFilter::Place)m_panel.place()->selectedItem()); |
112 | }); |
113 | } |
114 | |
115 | private: |
116 | void updateButtonsFromMatrix() { |
117 | const OutlineFilter::Matrix matrix = m_filter.matrix(); |
118 | |
119 | int commonMatrix = -1; |
120 | switch (matrix) { |
121 | case OutlineFilter::Matrix::Circle: commonMatrix = CIRCLE; break; |
122 | case OutlineFilter::Matrix::Square: commonMatrix = SQUARE; break; |
123 | case OutlineFilter::Matrix::Horizontal: commonMatrix = HORZ; break; |
124 | case OutlineFilter::Matrix::Vertical: commonMatrix = VERT; break; |
125 | } |
126 | m_panel.outlineType()->setSelectedItem(commonMatrix, false); |
127 | |
128 | auto theme = SkinTheme::get(this); |
129 | auto emptyIcon = theme->parts.outlineEmptyPixel(); |
130 | auto pixelIcon = theme->parts.outlineFullPixel(); |
131 | |
132 | for (int i=0; i<9; ++i) { |
133 | m_panel.outlineMatrix() |
134 | ->getItem(i)->setIcon( |
135 | (((int)matrix) & (1 << (8-i))) ? pixelIcon: emptyIcon); |
136 | } |
137 | } |
138 | |
139 | void onColorChange(const app::Color& color) { |
140 | stopPreview(); |
141 | m_filter.color(color); |
142 | restartPreview(); |
143 | } |
144 | |
145 | void onBgColorChange(const app::Color& color) { |
146 | stopPreview(); |
147 | m_filter.bgColor(color); |
148 | restartPreview(); |
149 | } |
150 | |
151 | void onPlaceChange(OutlineFilter::Place place) { |
152 | stopPreview(); |
153 | m_filter.place(place); |
154 | restartPreview(); |
155 | } |
156 | |
157 | void onMatrixTypeChange() { |
158 | stopPreview(); |
159 | |
160 | OutlineFilter::Matrix matrix = OutlineFilter::Matrix::None; |
161 | switch (m_panel.outlineType()->selectedItem()) { |
162 | case CIRCLE: matrix = OutlineFilter::Matrix::Circle; break; |
163 | case SQUARE: matrix = OutlineFilter::Matrix::Square; break; |
164 | case HORZ: matrix = OutlineFilter::Matrix::Horizontal; break; |
165 | case VERT: matrix = OutlineFilter::Matrix::Vertical; break; |
166 | } |
167 | m_filter.matrix(matrix); |
168 | updateButtonsFromMatrix(); |
169 | restartPreview(); |
170 | } |
171 | |
172 | void onMatrixPixelChange(const int index) { |
173 | stopPreview(); |
174 | |
175 | int matrix = (int)m_filter.matrix(); |
176 | matrix ^= (1 << (8-index)); |
177 | m_filter.matrix((OutlineFilter::Matrix)matrix); |
178 | updateButtonsFromMatrix(); |
179 | restartPreview(); |
180 | } |
181 | |
182 | void setupTiledMode(TiledMode tiledMode) override { |
183 | m_filter.tiledMode(tiledMode); |
184 | } |
185 | |
186 | OutlineFilterWrapper& m_filter; |
187 | gen::Outline m_panel; |
188 | }; |
189 | |
190 | #endif // ENABLE_UI |
191 | |
192 | class OutlineCommand : public CommandWithNewParams<OutlineParams> { |
193 | public: |
194 | OutlineCommand(); |
195 | |
196 | protected: |
197 | bool onEnabled(Context* context) override; |
198 | void onExecute(Context* context) override; |
199 | }; |
200 | |
201 | OutlineCommand::OutlineCommand() |
202 | : CommandWithNewParams<OutlineParams>(CommandId::Outline(), CmdRecordableFlag) |
203 | { |
204 | } |
205 | |
206 | bool OutlineCommand::onEnabled(Context* context) |
207 | { |
208 | return context->checkFlags(ContextFlags::ActiveDocumentIsWritable | |
209 | ContextFlags::HasActiveSprite); |
210 | } |
211 | |
212 | void OutlineCommand::onExecute(Context* context) |
213 | { |
214 | #ifdef ENABLE_UI |
215 | const bool ui = (params().ui() && context->isUIAvailable()); |
216 | #endif |
217 | |
218 | Site site = context->activeSite(); |
219 | |
220 | OutlineFilterWrapper filter(site.layer()); |
221 | if (site.layer() && site.layer()->isBackground() && site.image()) { |
222 | // TODO configure default pixel (same as Autocrop/Trim refpixel) |
223 | filter.bgColor(app::Color::fromImage(site.image()->pixelFormat(), |
224 | site.image()->getPixel(0, 0))); |
225 | } |
226 | else { |
227 | filter.bgColor(app::Color::fromMask()); |
228 | } |
229 | |
230 | #ifdef ENABLE_UI |
231 | if (ui) { |
232 | filter.place((OutlineFilter::Place)get_config_int(ConfigSection, "Place" , int(OutlineFilter::Place::Outside))); |
233 | filter.matrix((OutlineFilter::Matrix)get_config_int(ConfigSection, "Matrix" , int(OutlineFilter::Matrix::Circle))); |
234 | filter.color(ColorBar::instance()->getFgColor()); |
235 | |
236 | DocumentPreferences& docPref = Preferences::instance() |
237 | .document(site.document()); |
238 | filter.tiledMode(docPref.tiled.mode()); |
239 | } |
240 | #endif // ENABLE_UI |
241 | |
242 | if (params().place.isSet()) filter.place(params().place()); |
243 | if (params().matrix.isSet()) filter.matrix(params().matrix()); |
244 | if (params().color.isSet()) filter.color(params().color()); |
245 | if (params().bgColor.isSet()) filter.bgColor(params().bgColor()); |
246 | if (params().tiledMode.isSet()) filter.tiledMode(params().tiledMode()); |
247 | |
248 | FilterManagerImpl filterMgr(context, &filter); |
249 | filterMgr.setTarget( |
250 | site.sprite()->pixelFormat() == IMAGE_INDEXED ? |
251 | TARGET_INDEX_CHANNEL: |
252 | TARGET_RED_CHANNEL | |
253 | TARGET_GREEN_CHANNEL | |
254 | TARGET_BLUE_CHANNEL | |
255 | TARGET_GRAY_CHANNEL | |
256 | TARGET_ALPHA_CHANNEL); |
257 | |
258 | if (params().channels.isSet()) filterMgr.setTarget(params().channels()); |
259 | |
260 | #ifdef ENABLE_UI |
261 | if (ui) { |
262 | OutlineWindow window(filter, filterMgr); |
263 | if (window.doModal()) { |
264 | set_config_int(ConfigSection, "Place" , int(filter.place())); |
265 | set_config_int(ConfigSection, "Matrix" , int(filter.matrix())); |
266 | } |
267 | } |
268 | else |
269 | #endif // ENABLE_UI |
270 | { |
271 | start_filter_worker(&filterMgr); |
272 | } |
273 | } |
274 | |
275 | Command* CommandFactory::createOutlineCommand() |
276 | { |
277 | return new OutlineCommand; |
278 | } |
279 | |
280 | } // namespace app |
281 | |