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
37namespace app {
38
39using namespace app::skin;
40
41enum { CIRCLE, SQUARE, HORZ, VERT };
42
43struct 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
54class OutlineFilterWrapper : public OutlineFilter {
55public:
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
73private:
74 Layer* m_layer;
75 app::Color m_color;
76 app::Color m_bgColor;
77};
78
79#ifdef ENABLE_UI
80
81static const char* ConfigSection = "Outline";
82
83class OutlineWindow : public FilterWindow {
84public:
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
115private:
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
192class OutlineCommand : public CommandWithNewParams<OutlineParams> {
193public:
194 OutlineCommand();
195
196protected:
197 bool onEnabled(Context* context) override;
198 void onExecute(Context* context) override;
199};
200
201OutlineCommand::OutlineCommand()
202 : CommandWithNewParams<OutlineParams>(CommandId::Outline(), CmdRecordableFlag)
203{
204}
205
206bool OutlineCommand::onEnabled(Context* context)
207{
208 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
209 ContextFlags::HasActiveSprite);
210}
211
212void 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
275Command* CommandFactory::createOutlineCommand()
276{
277 return new OutlineCommand;
278}
279
280} // namespace app
281