[aseprite] 51/308: Add "Select > Modify" commands to expand/contract/border the selection
Tobias Hansen
thansen at moszumanska.debian.org
Tue Mar 8 02:44:51 UTC 2016
This is an automated email from the git hooks/post-receive script.
thansen pushed a commit to branch master
in repository aseprite.
commit 5dc149d30834c417280f4285136cc67bb0ebbed6
Author: David Capello <davidcapello at gmail.com>
Date: Fri Dec 4 17:46:32 2015 -0300
Add "Select > Modify" commands to expand/contract/border the selection
---
data/gui.xml | 11 ++
data/pref.xml | 2 +
data/widgets/modify_selection.xml | 25 +++
src/app/CMakeLists.txt | 1 +
src/app/commands/cmd_modify_selection.cpp | 255 ++++++++++++++++++++++++++++++
src/app/commands/commands_list.h | 1 +
6 files changed, 295 insertions(+)
diff --git a/data/gui.xml b/data/gui.xml
index da9c5c0..db5157a 100644
--- a/data/gui.xml
+++ b/data/gui.xml
@@ -667,6 +667,17 @@
<item command="InvertMask" text="&Inverse" />
<separator />
<item command="MaskByColor" text="&Color Range" />
+ <menu text="&Modify">
+ <item command="ModifySelection" text="&Border">
+ <param name="modifier" value="border" />
+ </item>
+ <item command="ModifySelection" text="&Expand">
+ <param name="modifier" value="expand" />
+ </item>
+ <item command="ModifySelection" text="&Contract">
+ <param name="modifier" value="contract" />
+ </item>
+ </menu>
<separator />
<item command="LoadMask" text="&Load from MSK file" />
<item command="SaveMask" text="&Save to MSK file" />
diff --git a/data/pref.xml b/data/pref.xml
index 9924412..aea4a04 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -136,6 +136,8 @@
<option id="auto_opaque" type="bool" default="true" />
<option id="transparent_color" type="app::Color" />
<option id="rotation_algorithm" type="app::tools::RotationAlgorithm" default="app::tools::RotationAlgorithm::DEFAULT" />
+ <option id="modify_selection_quantity" type="int" default="1" />
+ <option id="modify_selection_brush" type="BrushType" default="BrushType::CIRCLE" />
</section>
<section id="quantization">
<option id="with_alpha" type="bool" default="true" />
diff --git a/data/widgets/modify_selection.xml b/data/widgets/modify_selection.xml
new file mode 100644
index 0000000..e1ddb56
--- /dev/null
+++ b/data/widgets/modify_selection.xml
@@ -0,0 +1,25 @@
+<!-- Aseprite -->
+<!-- Copyright (C) 2015 by David Capello -->
+<gui>
+<window id="modify_selection" text="Modify Selection">
+ <vbox>
+ <grid columns="2">
+
+ <label id="by_label" text="By:" />
+ <entry id="quantity" expansive="true" maxsize="4" magnet="true" suffix="px" />
+
+ <hbox />
+ <vbox>
+ <radio id="circle" text="Circle Brush" group="1" />
+ <radio id="square" text="Square Brush" group="1" />
+ </vbox>
+
+ </grid>
+ <separator horizontal="true" cell_hspan="3" />
+ <hbox homogeneous="true" cell_hspan="3" cell_align="right">
+ <button text="&OK" closewindow="true" id="ok" magnet="true" minwidth="60" />
+ <button text="&Cancel" closewindow="true" />
+ </hbox>
+ </vbox>
+</window>
+</gui>
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index a4d14f8..a1e7c71 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -209,6 +209,7 @@ add_library(app-lib
commands/cmd_mask_by_color.cpp
commands/cmd_mask_content.cpp
commands/cmd_merge_down_layer.cpp
+ commands/cmd_modify_selection.cpp
commands/cmd_move_cel.cpp
commands/cmd_move_mask.cpp
commands/cmd_new_brush.cpp
diff --git a/src/app/commands/cmd_modify_selection.cpp b/src/app/commands/cmd_modify_selection.cpp
new file mode 100644
index 0000000..e7e6029
--- /dev/null
+++ b/src/app/commands/cmd_modify_selection.cpp
@@ -0,0 +1,255 @@
+// Aseprite
+// Copyright (C) 2015 David Capello
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "app/cmd/set_mask.h"
+#include "app/commands/command.h"
+#include "app/commands/params.h"
+#include "app/context_access.h"
+#include "app/document.h"
+#include "app/modules/gui.h"
+#include "app/pref/preferences.h"
+#include "app/transaction.h"
+#include "base/convert_to.h"
+#include "doc/brush_type.h"
+#include "doc/mask.h"
+#include "filters/neighboring_pixels.h"
+
+#include "modify_selection.xml.h"
+
+#include <limits>
+
+namespace app {
+
+using namespace doc;
+
+class ModifySelectionWindow : public app::gen::ModifySelection {
+};
+
+class ModifySelectionCommand : public Command {
+public:
+ enum Modifier { Border, Expand, Contract };
+
+ ModifySelectionCommand();
+ Command* clone() const override { return new ModifySelectionCommand(*this); }
+
+protected:
+ void onLoadParams(const Params& params) override;
+ bool onEnabled(Context* context) override;
+ void onExecute(Context* context) override;
+ std::string onGetFriendlyName() const override;
+
+private:
+ std::string getActionName() const;
+ void applyModifier(const Mask* srcMask, Mask* dstMask,
+ const int brushRadius,
+ const doc::BrushType brushType) const;
+
+ Modifier m_modifier;
+ int m_quantity;
+ doc::BrushType m_brushType;
+};
+
+ModifySelectionCommand::ModifySelectionCommand()
+ : Command("ModifySelection",
+ "Modify Selection",
+ CmdRecordableFlag)
+ , m_modifier(Expand)
+ , m_quantity(0)
+ , m_brushType(doc::kCircleBrushType)
+{
+}
+
+void ModifySelectionCommand::onLoadParams(const Params& params)
+{
+ const std::string modifier = params.get("modifier");
+ if (modifier == "border") m_modifier = Border;
+ else if (modifier == "expand") m_modifier = Expand;
+ else if (modifier == "contract") m_modifier = Contract;
+
+ const int quantity = params.get_as<int>("quantity");
+ m_quantity = std::max<int>(0, quantity);
+
+ const std::string brush = params.get("brush");
+ if (brush == "circle") m_brushType = doc::kCircleBrushType;
+ else if (brush == "square") m_brushType = doc::kSquareBrushType;
+}
+
+bool ModifySelectionCommand::onEnabled(Context* context)
+{
+ return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
+ ContextFlags::HasVisibleMask);
+}
+
+void ModifySelectionCommand::onExecute(Context* context)
+{
+ int quantity = m_quantity;
+ doc::BrushType brush = m_brushType;
+
+ if (quantity == 0) {
+ Preferences& pref = Preferences::instance();
+ ModifySelectionWindow window;
+
+ window.setText(getActionName() + " Selection");
+ if (m_modifier == Border)
+ window.byLabel()->setText("Width:");
+ else
+ window.byLabel()->setText(getActionName() + " By:");
+
+ window.quantity()->setTextf("%d", pref.selection.modifySelectionQuantity());
+
+ brush = (pref.selection.modifySelectionBrush() == app::gen::BrushType::CIRCLE
+ ? doc::kCircleBrushType:
+ doc::kSquareBrushType);
+ window.circle()->setSelected(brush == doc::kCircleBrushType);
+ window.square()->setSelected(brush == doc::kSquareBrushType);
+
+ window.openWindowInForeground();
+ if (window.closer() != window.ok())
+ return;
+
+ quantity = window.quantity()->textInt();
+ quantity = MID(1, quantity, 100);
+
+ brush = (window.circle()->isSelected() ? doc::kCircleBrushType:
+ doc::kSquareBrushType);
+
+ pref.selection.modifySelectionQuantity(quantity);
+ pref.selection.modifySelectionBrush(
+ (brush == doc::kCircleBrushType ? app::gen::BrushType::CIRCLE:
+ app::gen::BrushType::SQUARE));
+ }
+
+ // Lock sprite
+ ContextWriter writer(context);
+ Document* document(writer.document());
+ Sprite* sprite(writer.sprite());
+
+ base::UniquePtr<Mask> mask(new Mask());
+ {
+ mask->reserve(sprite->bounds());
+ mask->freeze();
+ applyModifier(document->mask(), mask, quantity, brush);
+ mask->unfreeze();
+ }
+
+ // Set the new mask
+ Transaction transaction(writer.context(),
+ getActionName() + " Selection",
+ DoesntModifyDocument);
+ transaction.execute(new cmd::SetMask(document, mask));
+ transaction.commit();
+
+ document->generateMaskBoundaries();
+ update_screen_for_document(document);
+}
+
+std::string ModifySelectionCommand::onGetFriendlyName() const
+{
+ std::string text;
+
+ text += getActionName();
+ text += " Selection";
+
+ if (m_quantity > 0) {
+ text += " by ";
+ text += base::convert_to<std::string>(m_quantity);
+ text += " pixel";
+ if (m_quantity > 1)
+ text += "s";
+ }
+
+ return text;
+}
+
+std::string ModifySelectionCommand::getActionName() const
+{
+ switch (m_modifier) {
+ case Border: return "Border";
+ case Expand: return "Expand";
+ case Contract: return "Contract";
+ default: return "Modify";
+ }
+}
+
+// TODO create morphological operators/functions in "doc" namespace
+// TODO the impl is not optimal, but is good enough as a first version
+void ModifySelectionCommand::applyModifier(const Mask* srcMask, Mask* dstMask,
+ const int radius,
+ const doc::BrushType brush) const
+{
+ const doc::Image* srcImage = srcMask->bitmap();
+ const doc::Image* dstImage = dstMask->bitmap();
+
+ // Image bounds to clip get/put pixels
+ const gfx::Rect srcBounds = srcImage->bounds();
+
+ // Create a kernel
+ const int size = 2*radius+1;
+ base::UniquePtr<doc::Image> kernel(doc::Image::create(IMAGE_BITMAP, size, size));
+ doc::clear_image(kernel, 0);
+ if (brush == doc::kCircleBrushType)
+ doc::fill_ellipse(kernel, 0, 0, size-1, size-1, 1);
+ else
+ doc::fill_rect(kernel, 0, 0, size-1, size-1, 1);
+ doc::put_pixel(kernel, radius, radius, 0);
+
+ int total = 0; // Number of 1s in the kernel image
+ for (int v=0; v<size; ++v)
+ for (int u=0; u<size; ++u)
+ total += kernel->getPixel(u, v);
+
+ for (int y=-radius; y<srcBounds.h+radius; ++y) {
+ for (int x=-radius; x<srcBounds.w+radius; ++x) {
+ doc::color_t c;
+ if (srcBounds.contains(x, y))
+ c = srcImage->getPixel(x, y);
+ else
+ c = 0;
+
+ int accum = 0;
+ for (int v=0; v<size; ++v) {
+ for (int u=0; u<size; ++u) {
+ if (kernel->getPixel(u, v)) {
+ if (srcBounds.contains(x+u-radius, y+v-radius))
+ accum += srcImage->getPixel(x-radius+u, y-radius+v);
+ }
+ }
+ }
+
+ switch (m_modifier) {
+ case Border: {
+ c = (c && accum < total) ? 1: 0;
+ break;
+ }
+ case Expand: {
+ c = (c || accum > 0) ? 1: 0;
+ break;
+ }
+ case Contract: {
+ c = (c && accum == total) ? 1: 0;
+ break;
+ }
+ }
+
+ if (c)
+ doc::put_pixel(dstMask->bitmap(),
+ srcMask->bounds().x+x,
+ srcMask->bounds().y+y, 1);
+ }
+ }
+}
+
+Command* CommandFactory::createModifySelectionCommand()
+{
+ return new ModifySelectionCommand;
+}
+
+} // namespace app
diff --git a/src/app/commands/commands_list.h b/src/app/commands/commands_list.h
index 599a115..baeddf1 100644
--- a/src/app/commands/commands_list.h
+++ b/src/app/commands/commands_list.h
@@ -67,6 +67,7 @@ FOR_EACH_COMMAND(MaskAll)
FOR_EACH_COMMAND(MaskByColor)
FOR_EACH_COMMAND(MaskContent)
FOR_EACH_COMMAND(MergeDownLayer)
+FOR_EACH_COMMAND(ModifySelection)
FOR_EACH_COMMAND(MoveCel)
FOR_EACH_COMMAND(MoveMask)
FOR_EACH_COMMAND(NewBrush)
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/aseprite.git
More information about the Pkg-games-commits
mailing list