| 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 | |
| 37 | namespace app { |
| 38 | |
| 39 | using namespace filters; |
| 40 | |
| 41 | struct 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 | |
| 51 | static const char* ConfigSection = "Despeckle" ; |
| 52 | |
| 53 | class DespeckleWindow : public FilterWindow { |
| 54 | public: |
| 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 | |
| 74 | private: |
| 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 | |
| 105 | class DespeckleCommand : public CommandWithNewParams<DespeckleParams> { |
| 106 | public: |
| 107 | DespeckleCommand(); |
| 108 | |
| 109 | protected: |
| 110 | bool onEnabled(Context* context) override; |
| 111 | void onExecute(Context* context) override; |
| 112 | }; |
| 113 | |
| 114 | DespeckleCommand::DespeckleCommand() |
| 115 | : CommandWithNewParams<DespeckleParams>(CommandId::Despeckle(), CmdRecordableFlag) |
| 116 | { |
| 117 | } |
| 118 | |
| 119 | bool DespeckleCommand::onEnabled(Context* context) |
| 120 | { |
| 121 | return context->checkFlags(ContextFlags::ActiveDocumentIsWritable | |
| 122 | ContextFlags::HasActiveSprite); |
| 123 | } |
| 124 | |
| 125 | void 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 | |
| 170 | Command* CommandFactory::createDespeckleCommand() |
| 171 | { |
| 172 | return new DespeckleCommand; |
| 173 | } |
| 174 | |
| 175 | } // namespace app |
| 176 | |