| 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 |  | 
|---|