[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