1// Aseprite
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2001-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/app.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/doc.h"
20#include "app/find_widget.h"
21#include "app/ini_file.h"
22#include "app/load_widget.h"
23#include "app/pref/preferences.h"
24#include "doc/mask.h"
25#include "doc/sprite.h"
26#include "filters/median_filter.h"
27#include "ui/button.h"
28#include "ui/entry.h"
29#include "ui/grid.h"
30#include "ui/widget.h"
31#include "ui/window.h"
32
33#include "despeckle.xml.h"
34
35#include <stdio.h>
36
37namespace app {
38
39using namespace filters;
40
41struct DespeckleParams : public NewParams {
42 Param<bool> ui { this, true, "ui" };
43 Param<filters::Target> channels { this, 0, "channels" };
44 Param<int> width { this, 3, "width" };
45 Param<int> height { this, 3, "height" };
46 Param<filters::TiledMode> tiledMode { this, filters::TiledMode::NONE, "tiledMode" };
47};
48
49#ifdef ENABLE_UI
50
51static const char* ConfigSection = "Despeckle";
52
53class DespeckleWindow : public FilterWindow {
54public:
55 DespeckleWindow(MedianFilter& filter, FilterManagerImpl& filterMgr)
56 : FilterWindow("Median Blur", ConfigSection, &filterMgr,
57 WithChannelsSelector,
58 WithTiledCheckBox,
59 filter.getTiledMode())
60 , m_filter(filter)
61 , m_controlsWidget(new gen::Despeckle)
62 , m_widthEntry(m_controlsWidget->width())
63 , m_heightEntry(m_controlsWidget->height())
64 {
65 getContainer()->addChild(m_controlsWidget.get());
66
67 m_widthEntry->setTextf("%d", m_filter.getWidth());
68 m_heightEntry->setTextf("%d", m_filter.getHeight());
69
70 m_widthEntry->Change.connect(&DespeckleWindow::onSizeChange, this);
71 m_heightEntry->Change.connect(&DespeckleWindow::onSizeChange, this);
72 }
73
74private:
75 void onSizeChange() {
76 gfx::Size newSize(m_widthEntry->textInt(),
77 m_heightEntry->textInt());
78
79 // Avoid negative numbers
80 newSize.w = std::clamp(newSize.w, 1, 100);
81 newSize.h = std::clamp(newSize.h, 1, 100);
82
83 // If we had a previous filter preview running in the background,
84 // we explicitly request it be stopped. Otherwise, changing the
85 // size of the filter would cause a race condition on
86 // MedianFilter::m_channel field.
87 stopPreview();
88
89 m_filter.setSize(newSize.w, newSize.h);
90 restartPreview();
91 }
92
93 void setupTiledMode(TiledMode tiledMode) override {
94 m_filter.setTiledMode(tiledMode);
95 }
96
97 MedianFilter& m_filter;
98 std::unique_ptr<gen::Despeckle> m_controlsWidget;
99 ExprEntry* m_widthEntry;
100 ExprEntry* m_heightEntry;
101};
102
103#endif // ENABLE_UI
104
105class DespeckleCommand : public CommandWithNewParams<DespeckleParams> {
106public:
107 DespeckleCommand();
108
109protected:
110 bool onEnabled(Context* context) override;
111 void onExecute(Context* context) override;
112};
113
114DespeckleCommand::DespeckleCommand()
115 : CommandWithNewParams<DespeckleParams>(CommandId::Despeckle(), CmdRecordableFlag)
116{
117}
118
119bool DespeckleCommand::onEnabled(Context* context)
120{
121 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
122 ContextFlags::HasActiveSprite);
123}
124
125void DespeckleCommand::onExecute(Context* context)
126{
127#ifdef ENABLE_UI
128 const bool ui = (params().ui() && context->isUIAvailable());
129#endif
130
131 MedianFilter filter;
132 filter.setSize(3, 3); // Default size
133
134 FilterManagerImpl filterMgr(context, &filter);
135 filterMgr.setTarget(TARGET_RED_CHANNEL |
136 TARGET_GREEN_CHANNEL |
137 TARGET_BLUE_CHANNEL |
138 TARGET_GRAY_CHANNEL);
139
140#ifdef ENABLE_UI
141 if (ui) {
142 DocumentPreferences& docPref = Preferences::instance()
143 .document(context->activeDocument());
144 filter.setTiledMode((filters::TiledMode)docPref.tiled.mode());
145 filter.setSize(get_config_int(ConfigSection, "Width", 3),
146 get_config_int(ConfigSection, "Height", 3));
147 }
148#endif
149
150 if (params().width.isSet()) filter.setSize(params().width(), filter.getHeight());
151 if (params().height.isSet()) filter.setSize(filter.getWidth(), params().height());
152 if (params().channels.isSet()) filterMgr.setTarget(params().channels());
153 if (params().tiledMode.isSet()) filter.setTiledMode(params().tiledMode());
154
155#ifdef ENABLE_UI
156 if (ui) {
157 DespeckleWindow window(filter, filterMgr);
158 if (window.doModal()) {
159 set_config_int(ConfigSection, "Width", filter.getWidth());
160 set_config_int(ConfigSection, "Height", filter.getHeight());
161 }
162 }
163 else
164#endif // ENABLE_UI
165 {
166 start_filter_worker(&filterMgr);
167 }
168}
169
170Command* CommandFactory::createDespeckleCommand()
171{
172 return new DespeckleCommand;
173}
174
175} // namespace app
176