[aseprite] 203/250: Add symmetry mode (fix #208)
Tobias Hansen
thansen at moszumanska.debian.org
Sun Dec 20 15:27:31 UTC 2015
This is an automated email from the git hooks/post-receive script.
thansen pushed a commit to branch master
in repository aseprite.
commit 56854cdb9f1bd8eb397a918ef2f554636f55724b
Author: David Capello <davidcapello at gmail.com>
Date: Mon Oct 26 17:51:32 2015 -0300
Add symmetry mode (fix #208)
This is a first iteration of the feature, it doesn’t have handles to
move the symmetry line and it only contains two symmetry modes:
horizontal or vertical.
As an extra change, we have added the new Stroke type to wrap a vector
of gfx::Points and simplify some existing code in the ToolLoop.
---
data/gui.xml | 1 +
data/pref.xml | 13 ++
data/skins/default/sheet.png | Bin 13834 -> 13952 bytes
data/skins/default/skin.xml | 3 +
src/app/CMakeLists.txt | 3 +
src/app/commands/cmd_symmetry_mode.cpp | 60 ++++++++
src/app/commands/commands_list.h | 1 +
src/app/tools/controller.h | 13 +-
src/app/tools/controllers.h | 241 +++++++++++++++++----------------
src/app/tools/intertwine.h | 5 +-
src/app/tools/intertwiners.h | 200 ++++++++++++++-------------
src/app/tools/stroke.cpp | 62 +++++++++
src/app/tools/stroke.h | 66 +++++++++
src/app/tools/symmetries.cpp | 40 ++++++
src/app/tools/symmetries.h | 37 +++++
src/app/tools/symmetry.h | 31 +++++
src/app/tools/tool_box.cpp | 1 +
src/app/tools/tool_loop.h | 2 +
src/app/tools/tool_loop_manager.cpp | 87 ++++++------
src/app/tools/tool_loop_manager.h | 12 +-
src/app/ui/context_bar.cpp | 58 ++++++++
src/app/ui/context_bar.h | 3 +
src/app/ui/editor/editor.cpp | 31 +++++
src/app/ui/editor/tool_loop_impl.cpp | 26 ++++
src/app/ui_context.cpp | 13 +-
25 files changed, 726 insertions(+), 283 deletions(-)
diff --git a/data/gui.xml b/data/gui.xml
index a15d94a..090aa93 100644
--- a/data/gui.xml
+++ b/data/gui.xml
@@ -684,6 +684,7 @@
<param name="axis" value="y" />
</item>
</menu>
+ <item command="SymmetryMode" text="S&ymmetry Options" />
<separator />
<item command="SetLoopSection" text="Set &Loop Section" />
<item command="ShowOnionSkin" text="Show &Onion Skin" />
diff --git a/data/pref.xml b/data/pref.xml
index 83c9230..936bf08 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -64,6 +64,11 @@
<value id="SOUTH" value="7" />
<value id="SOUTHEAST" value="8" />
</enum>
+ <enum id="SymmetryMode">
+ <value id="NONE" value="0" />
+ <value id="HORIZONTAL" value="1" />
+ <value id="VERTICAL" value="2" />
+ </enum>
</types>
<global>
@@ -159,6 +164,9 @@
<option id="font_face" type="std::string" />
<option id="font_size" type="int" default="12" />
</section>
+ <section id="symmetry_mode">
+ <option id="enabled" type="bool" default="false" />
+ </section>
</global>
<tool>
@@ -191,6 +199,11 @@
<section id="tiled">
<option id="mode" type="filters::TiledMode" default="filters::TiledMode::NONE" migrate="Tools.Tiled" />
</section>
+ <section id="symmetry">
+ <option id="mode" type="SymmetryMode" default="SymmetryMode::NONE" />
+ <option id="x_axis" type="int" default="0" />
+ <option id="y_axis" type="int" default="0" />
+ </section>
<section id="grid">
<option id="snap" type="bool" default="false" migrate="Grid.SnapTo" />
<option id="visible" type="bool" default="false" migrate="Grid.Visible" />
diff --git a/data/skins/default/sheet.png b/data/skins/default/sheet.png
index af48929..af0bf50 100644
Binary files a/data/skins/default/sheet.png and b/data/skins/default/sheet.png differ
diff --git a/data/skins/default/skin.xml b/data/skins/default/skin.xml
index ca885b6..2cecd7b 100644
--- a/data/skins/default/skin.xml
+++ b/data/skins/default/skin.xml
@@ -403,6 +403,9 @@
<part id="icon_white" x="64" y="256" w="16" h="16" />
<part id="icon_transparent" x="80" y="256" w="16" h="16" />
<part id="color_wheel_indicator" x="48" y="192" w="4" h="4" />
+ <part id="no_symmetry" x="144" y="240" w="13" h="13" />
+ <part id="horizontal_symmetry" x="160" y="240" w="13" h="13" />
+ <part id="vertical_symmetry" x="176" y="240" w="13" h="13" />
</parts>
<stylesheet>
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index e1d1723..d4e30a0 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -248,6 +248,7 @@ add_library(app-lib
commands/cmd_sprite_properties.cpp
commands/cmd_sprite_size.cpp
commands/cmd_switch_colors.cpp
+ commands/cmd_symmetry_mode.cpp
commands/cmd_tiled_mode.cpp
commands/cmd_timeline.cpp
commands/cmd_toggle_preview.cpp
@@ -315,6 +316,8 @@ add_library(app-lib
tools/intertwine.cpp
tools/pick_ink.cpp
tools/point_shape.cpp
+ tools/stroke.cpp
+ tools/symmetries.cpp
tools/tool_box.cpp
tools/tool_loop_manager.cpp
transaction.cpp
diff --git a/src/app/commands/cmd_symmetry_mode.cpp b/src/app/commands/cmd_symmetry_mode.cpp
new file mode 100644
index 0000000..74957e2
--- /dev/null
+++ b/src/app/commands/cmd_symmetry_mode.cpp
@@ -0,0 +1,60 @@
+// Aseprite
+// Copyright (C) 2001-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/app.h"
+#include "app/commands/command.h"
+#include "app/commands/params.h"
+#include "app/context.h"
+#include "app/pref/preferences.h"
+
+namespace app {
+
+class SymmetryModeCommand : public Command {
+public:
+ SymmetryModeCommand();
+ Command* clone() const override { return new SymmetryModeCommand(*this); }
+
+protected:
+ bool onEnabled(Context* context) override;
+ bool onChecked(Context* context) override;
+ void onExecute(Context* context) override;
+};
+
+SymmetryModeCommand::SymmetryModeCommand()
+ : Command("SymmetryMode",
+ "Symmetry Mode",
+ CmdUIOnlyFlag)
+{
+}
+
+bool SymmetryModeCommand::onEnabled(Context* ctx)
+{
+ return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
+ ContextFlags::HasActiveSprite);
+}
+
+bool SymmetryModeCommand::onChecked(Context* ctx)
+{
+ return Preferences::instance().symmetryMode.enabled();
+}
+
+void SymmetryModeCommand::onExecute(Context* ctx)
+{
+ auto& enabled = Preferences::instance().symmetryMode.enabled;
+ enabled(!enabled());
+}
+
+Command* CommandFactory::createSymmetryModeCommand()
+{
+ return new SymmetryModeCommand;
+}
+
+} // namespace app
diff --git a/src/app/commands/commands_list.h b/src/app/commands/commands_list.h
index c7120c2..00c3edb 100644
--- a/src/app/commands/commands_list.h
+++ b/src/app/commands/commands_list.h
@@ -114,6 +114,7 @@ FOR_EACH_COMMAND(SnapToGrid)
FOR_EACH_COMMAND(SpriteProperties)
FOR_EACH_COMMAND(SpriteSize)
FOR_EACH_COMMAND(SwitchColors)
+FOR_EACH_COMMAND(SymmetryMode)
FOR_EACH_COMMAND(TiledMode)
FOR_EACH_COMMAND(Timeline)
FOR_EACH_COMMAND(TogglePreview)
diff --git a/src/app/tools/controller.h b/src/app/tools/controller.h
index 0408e4a..e0305e2 100644
--- a/src/app/tools/controller.h
+++ b/src/app/tools/controller.h
@@ -18,13 +18,12 @@
namespace app {
namespace tools {
+ class Stroke;
class ToolLoop;
// This class controls user input.
class Controller {
public:
- typedef std::vector<gfx::Point> Points;
-
virtual ~Controller() { }
virtual bool canSnapToGrid() { return true; }
@@ -41,16 +40,16 @@ namespace app {
// Called when the user starts drawing and each time a new button is
// pressed. The controller could be sure that this method is called
// at least one time.
- virtual void pressButton(Points& points, const gfx::Point& point) = 0;
+ virtual void pressButton(Stroke& stroke, const gfx::Point& point) = 0;
// Called each time a mouse button is released.
- virtual bool releaseButton(Points& points, const gfx::Point& point) = 0;
+ virtual bool releaseButton(Stroke& stroke, const gfx::Point& point) = 0;
// Called when the mouse is moved.
- virtual void movement(ToolLoop* loop, Points& points, const gfx::Point& point) = 0;
+ virtual void movement(ToolLoop* loop, Stroke& stroke, const gfx::Point& point) = 0;
- virtual void getPointsToInterwine(const Points& input, Points& output) = 0;
- virtual void getStatusBarText(const Points& points, std::string& text) = 0;
+ virtual void getStrokeToInterwine(const Stroke& input, Stroke& output) = 0;
+ virtual void getStatusBarText(const Stroke& stroke, std::string& text) = 0;
};
} // namespace tools
diff --git a/src/app/tools/controllers.h b/src/app/tools/controllers.h
index aa2487a..e6e176f 100644
--- a/src/app/tools/controllers.h
+++ b/src/app/tools/controllers.h
@@ -22,7 +22,7 @@ public:
m_movingOrigin = false;
}
- void pressButton(Points& points, const Point& point) override {
+ void pressButton(Stroke& stroke, const Point& point) override {
m_last = point;
}
@@ -37,13 +37,12 @@ public:
}
protected:
- bool isMovingOrigin(Points& points, const Point& point) {
+ bool isMovingOrigin(Stroke& stroke, const Point& point) {
bool used = false;
if (m_movingOrigin) {
Point delta = (point - m_last);
- for (auto& p : points)
- p += delta;
+ stroke.offset(delta);
onMoveOrigin(delta);
used = true;
@@ -80,38 +79,39 @@ class FreehandController : public Controller {
public:
bool isFreehand() override { return true; }
- void pressButton(Points& points, const Point& point) override {
- points.push_back(point);
+ void pressButton(Stroke& stroke, const Point& point) override {
+ stroke.addPoint(point);
}
- bool releaseButton(Points& points, const Point& point) override {
+ bool releaseButton(Stroke& stroke, const Point& point) override {
return false;
}
- void movement(ToolLoop* loop, Points& points, const Point& point) override {
- points.push_back(point);
+ void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
+ stroke.addPoint(point);
}
- void getPointsToInterwine(const Points& input, Points& output) override {
+ void getStrokeToInterwine(const Stroke& input, Stroke& output) override {
if (input.size() == 1) {
- output.push_back(input[0]);
+ output.addPoint(input[0]);
}
else if (input.size() >= 2) {
- output.push_back(input[input.size()-2]);
- output.push_back(input[input.size()-1]);
+ output.addPoint(input[input.size()-2]);
+ output.addPoint(input[input.size()-1]);
}
}
- void getStatusBarText(const Points& points, std::string& text) override {
- ASSERT(!points.empty());
- if (points.empty())
+ void getStatusBarText(const Stroke& stroke, std::string& text) override {
+ ASSERT(!stroke.empty());
+ if (stroke.empty())
return;
char buf[1024];
sprintf(buf, "Start %3d %3d End %3d %3d",
- points[0].x, points[0].y,
- points[points.size()-1].x,
- points[points.size()-1].y);
+ stroke.firstPoint().x,
+ stroke.firstPoint().y,
+ stroke.lastPoint().x,
+ stroke.lastPoint().y);
text = buf;
}
@@ -127,16 +127,16 @@ public:
m_fromCenter = (modifiers & ui::kKeyCtrlModifier) ? true: false;
}
- void pressButton(Points& points, const Point& point) override {
- MoveOriginCapability::pressButton(points, point);
+ void pressButton(Stroke& stroke, const Point& point) override {
+ MoveOriginCapability::pressButton(stroke, point);
m_first = point;
- points.push_back(point);
- points.push_back(point);
+ stroke.addPoint(point);
+ stroke.addPoint(point);
}
- bool releaseButton(Points& points, const Point& point) override {
+ bool releaseButton(Stroke& stroke, const Point& point) override {
return false;
}
@@ -154,19 +154,19 @@ public:
return processKey(key, false);
}
- void movement(ToolLoop* loop, Points& points, const Point& point) override {
- ASSERT(points.size() >= 2);
- if (points.size() < 2)
+ void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
+ ASSERT(stroke.size() >= 2);
+ if (stroke.size() < 2)
return;
- if (MoveOriginCapability::isMovingOrigin(points, point))
+ if (MoveOriginCapability::isMovingOrigin(stroke, point))
return;
- points[1] = point;
+ stroke[1] = point;
if (m_squareAspect) {
- int dx = points[1].x - m_first.x;
- int dy = points[1].y - m_first.y;
+ int dx = stroke[1].x - m_first.x;
+ int dy = stroke[1].y - m_first.y;
int minsize = MIN(ABS(dx), ABS(dy));
int maxsize = MAX(ABS(dx), ABS(dy));
@@ -178,84 +178,84 @@ public:
// Snap horizontally
if (angle < 18.0) {
- points[1].y = m_first.y;
+ stroke[1].y = m_first.y;
}
// Snap at 26.565
else if (angle < 36.0) {
- points[1].x = m_first.x + SGN(dx)*maxsize;
- points[1].y = m_first.y + SGN(dy)*maxsize/2;
+ stroke[1].x = m_first.x + SGN(dx)*maxsize;
+ stroke[1].y = m_first.y + SGN(dy)*maxsize/2;
}
// Snap at 45
else if (angle < 54.0) {
- points[1].x = m_first.x + SGN(dx)*minsize;
- points[1].y = m_first.y + SGN(dy)*minsize;
+ stroke[1].x = m_first.x + SGN(dx)*minsize;
+ stroke[1].y = m_first.y + SGN(dy)*minsize;
}
// Snap at 63.435
else if (angle < 72.0) {
- points[1].x = m_first.x + SGN(dx)*maxsize/2;
- points[1].y = m_first.y + SGN(dy)*maxsize;
+ stroke[1].x = m_first.x + SGN(dx)*maxsize/2;
+ stroke[1].y = m_first.y + SGN(dy)*maxsize;
}
// Snap vertically
else {
- points[1].x = m_first.x;
+ stroke[1].x = m_first.x;
}
}
// Rectangles and ellipses
else {
- points[1].x = m_first.x + SGN(dx)*minsize;
- points[1].y = m_first.y + SGN(dy)*minsize;
+ stroke[1].x = m_first.x + SGN(dx)*minsize;
+ stroke[1].y = m_first.y + SGN(dy)*minsize;
}
}
- points[0] = m_first;
+ stroke[0] = m_first;
if (m_fromCenter) {
- int rx = points[1].x - m_first.x;
- int ry = points[1].y - m_first.y;
- points[0].x = m_first.x - rx;
- points[0].y = m_first.y - ry;
- points[1].x = m_first.x + rx;
- points[1].y = m_first.y + ry;
+ int rx = stroke[1].x - m_first.x;
+ int ry = stroke[1].y - m_first.y;
+ stroke[0].x = m_first.x - rx;
+ stroke[0].y = m_first.y - ry;
+ stroke[1].x = m_first.x + rx;
+ stroke[1].y = m_first.y + ry;
}
// Adjust points for selection like tools (so we can select tiles)
if (loop->getController()->canSnapToGrid() &&
loop->getSnapToGrid() &&
loop->getInk()->isSelection()) {
- if (points[0].x < points[1].x)
- points[1].x--;
- else if (points[0].x > points[1].x)
- points[0].x--;
-
- if (points[0].y < points[1].y)
- points[1].y--;
- else if (points[0].y > points[1].y)
- points[0].y--;
+ if (stroke[0].x < stroke[1].x)
+ stroke[1].x--;
+ else if (stroke[0].x > stroke[1].x)
+ stroke[0].x--;
+
+ if (stroke[0].y < stroke[1].y)
+ stroke[1].y--;
+ else if (stroke[0].y > stroke[1].y)
+ stroke[0].y--;
}
}
- void getPointsToInterwine(const Points& input, Points& output) override {
+ void getStrokeToInterwine(const Stroke& input, Stroke& output) override {
ASSERT(input.size() >= 2);
if (input.size() < 2)
return;
- output.push_back(input[0]);
- output.push_back(input[1]);
+ output.addPoint(input[0]);
+ output.addPoint(input[1]);
}
- void getStatusBarText(const Points& points, std::string& text) override {
- ASSERT(points.size() >= 2);
- if (points.size() < 2)
+ void getStatusBarText(const Stroke& stroke, std::string& text) override {
+ ASSERT(stroke.size() >= 2);
+ if (stroke.size() < 2)
return;
char buf[1024];
sprintf(buf, "Start %3d %3d End %3d %3d (Size %3d %3d) Angle %.1f",
- points[0].x, points[0].y,
- points[1].x, points[1].y,
- ABS(points[1].x-points[0].x)+1,
- ABS(points[1].y-points[0].y)+1,
- 180.0 * std::atan2(static_cast<double>(points[0].y-points[1].y),
- static_cast<double>(points[1].x-points[0].x)) / PI);
+ stroke[0].x, stroke[0].y,
+ stroke[1].x, stroke[1].y,
+ ABS(stroke[1].x-stroke[0].x)+1,
+ ABS(stroke[1].y-stroke[0].y)+1,
+ 180.0 * std::atan2(static_cast<double>(stroke[0].y-stroke[1].y),
+ static_cast<double>(stroke[1].x-stroke[0].x)) / PI);
text = buf;
}
@@ -287,50 +287,51 @@ private:
class PointByPointController : public MoveOriginCapability {
public:
- void pressButton(Points& points, const Point& point) override {
- MoveOriginCapability::pressButton(points, point);
+ void pressButton(Stroke& stroke, const Point& point) override {
+ MoveOriginCapability::pressButton(stroke, point);
- points.push_back(point);
- points.push_back(point);
+ stroke.addPoint(point);
+ stroke.addPoint(point);
}
- bool releaseButton(Points& points, const Point& point) override {
- ASSERT(!points.empty());
- if (points.empty())
+ bool releaseButton(Stroke& stroke, const Point& point) override {
+ ASSERT(!stroke.empty());
+ if (stroke.empty())
return false;
- if (points[points.size()-2] == point &&
- points[points.size()-1] == point)
+ if (stroke[stroke.size()-2] == point &&
+ stroke[stroke.size()-1] == point)
return false; // Click in the same point (no-drag), we are done
else
return true; // Continue adding points
}
- void movement(ToolLoop* loop, Points& points, const Point& point) override {
- ASSERT(!points.empty());
- if (points.empty())
+ void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
+ ASSERT(!stroke.empty());
+ if (stroke.empty())
return;
- if (MoveOriginCapability::isMovingOrigin(points, point))
+ if (MoveOriginCapability::isMovingOrigin(stroke, point))
return;
- points[points.size()-1] = point;
+ stroke[stroke.size()-1] = point;
}
- void getPointsToInterwine(const Points& input, Points& output) override {
+ void getStrokeToInterwine(const Stroke& input, Stroke& output) override {
output = input;
}
- void getStatusBarText(const Points& points, std::string& text) override {
- ASSERT(!points.empty());
- if (points.empty())
+ void getStatusBarText(const Stroke& stroke, std::string& text) override {
+ ASSERT(!stroke.empty());
+ if (stroke.empty())
return;
char buf[1024];
sprintf(buf, "Start %3d %3d End %3d %3d",
- points[0].x, points[0].y,
- points[points.size()-1].x,
- points[points.size()-1].y);
+ stroke.firstPoint().x,
+ stroke.firstPoint().y,
+ stroke.lastPoint().x,
+ stroke.lastPoint().y);
text = buf;
}
@@ -342,30 +343,30 @@ public:
bool canSnapToGrid() override { return false; }
bool isOnePoint() override { return true; }
- void pressButton(Points& points, const Point& point) override {
- if (points.size() == 0)
- points.push_back(point);
+ void pressButton(Stroke& stroke, const Point& point) override {
+ if (stroke.size() == 0)
+ stroke.addPoint(point);
}
- bool releaseButton(Points& points, const Point& point) override {
+ bool releaseButton(Stroke& stroke, const Point& point) override {
return false;
}
- void movement(ToolLoop* loop, Points& points, const Point& point) override {
+ void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
// Do nothing
}
- void getPointsToInterwine(const Points& input, Points& output) override {
+ void getStrokeToInterwine(const Stroke& input, Stroke& output) override {
output = input;
}
- void getStatusBarText(const Points& points, std::string& text) override {
- ASSERT(!points.empty());
- if (points.empty())
+ void getStatusBarText(const Stroke& stroke, std::string& text) override {
+ ASSERT(!stroke.empty());
+ if (stroke.empty())
return;
char buf[1024];
- sprintf(buf, "Pos %3d %3d", points[0].x, points[0].y);
+ sprintf(buf, "Pos %3d %3d", stroke[0].x, stroke[0].y);
text = buf;
}
@@ -374,57 +375,57 @@ public:
class FourPointsController : public MoveOriginCapability {
public:
- void pressButton(Points& points, const Point& point) override {
- MoveOriginCapability::pressButton(points, point);
+ void pressButton(Stroke& stroke, const Point& point) override {
+ MoveOriginCapability::pressButton(stroke, point);
- if (points.size() == 0) {
- points.resize(4, point);
+ if (stroke.size() == 0) {
+ stroke.reset(4, point);
m_clickCounter = 0;
}
else
m_clickCounter++;
}
- bool releaseButton(Points& points, const Point& point) override {
+ bool releaseButton(Stroke& stroke, const Point& point) override {
m_clickCounter++;
return m_clickCounter < 4;
}
- void movement(ToolLoop* loop, Points& points, const Point& point) override {
- if (MoveOriginCapability::isMovingOrigin(points, point))
+ void movement(ToolLoop* loop, Stroke& stroke, const Point& point) override {
+ if (MoveOriginCapability::isMovingOrigin(stroke, point))
return;
switch (m_clickCounter) {
case 0:
- for (size_t i=1; i<points.size(); ++i)
- points[i] = point;
+ for (size_t i=1; i<stroke.size(); ++i)
+ stroke[i] = point;
break;
case 1:
case 2:
- points[1] = point;
- points[2] = point;
+ stroke[1] = point;
+ stroke[2] = point;
break;
case 3:
- points[2] = point;
+ stroke[2] = point;
break;
}
}
- void getPointsToInterwine(const Points& input, Points& output) override {
+ void getStrokeToInterwine(const Stroke& input, Stroke& output) override {
output = input;
}
- void getStatusBarText(const Points& points, std::string& text) override {
- ASSERT(points.size() >= 4);
- if (points.size() < 4)
+ void getStatusBarText(const Stroke& stroke, std::string& text) override {
+ ASSERT(stroke.size() >= 4);
+ if (stroke.size() < 4)
return;
char buf[1024];
sprintf(buf, "Start %3d %3d End %3d %3d (%3d %3d - %3d %3d)",
- points[0].x, points[0].y,
- points[3].x, points[3].y,
- points[1].x, points[1].y,
- points[2].x, points[2].y);
+ stroke[0].x, stroke[0].y,
+ stroke[3].x, stroke[3].y,
+ stroke[1].x, stroke[1].y,
+ stroke[2].x, stroke[2].y);
text = buf;
}
diff --git a/src/app/tools/intertwine.h b/src/app/tools/intertwine.h
index f480704..f73d8a2 100644
--- a/src/app/tools/intertwine.h
+++ b/src/app/tools/intertwine.h
@@ -15,6 +15,7 @@
namespace app {
namespace tools {
+ class Stroke;
class ToolLoop;
// Converts a sequence of points in several call to
@@ -28,8 +29,8 @@ namespace app {
virtual ~Intertwine() { }
virtual bool snapByAngle() { return false; }
virtual void prepareIntertwine() { }
- virtual void joinPoints(ToolLoop* loop, const Points& points) = 0;
- virtual void fillPoints(ToolLoop* loop, const Points& points) = 0;
+ virtual void joinStroke(ToolLoop* loop, const Stroke& stroke) = 0;
+ virtual void fillStroke(ToolLoop* loop, const Stroke& stroke) = 0;
protected:
static void doPointshapePoint(int x, int y, ToolLoop* loop);
diff --git a/src/app/tools/intertwiners.h b/src/app/tools/intertwiners.h
index 8780dc3..0742a79 100644
--- a/src/app/tools/intertwiners.h
+++ b/src/app/tools/intertwiners.h
@@ -11,15 +11,13 @@ namespace tools {
class IntertwineNone : public Intertwine {
public:
- void joinPoints(ToolLoop* loop, const Points& points) override
- {
- for (size_t c=0; c<points.size(); ++c)
- doPointshapePoint(points[c].x, points[c].y, loop);
+ void joinStroke(ToolLoop* loop, const Stroke& stroke) override {
+ for (size_t c=0; c<stroke.size(); ++c)
+ doPointshapePoint(stroke[c].x, stroke[c].y, loop);
}
- void fillPoints(ToolLoop* loop, const Points& points) override
- {
- joinPoints(loop, points);
+ void fillStroke(ToolLoop* loop, const Stroke& stroke) override {
+ joinStroke(loop, stroke);
}
};
@@ -27,20 +25,20 @@ class IntertwineAsLines : public Intertwine {
public:
bool snapByAngle() override { return true; }
- void joinPoints(ToolLoop* loop, const Points& points) override
+ void joinStroke(ToolLoop* loop, const Stroke& stroke) override
{
- if (points.size() == 0)
+ if (stroke.size() == 0)
return;
- if (points.size() == 1) {
- doPointshapePoint(points[0].x, points[0].y, loop);
+ if (stroke.size() == 1) {
+ doPointshapePoint(stroke[0].x, stroke[0].y, loop);
}
- else if (points.size() >= 2) {
- for (size_t c=0; c+1<points.size(); ++c) {
- int x1 = points[c].x;
- int y1 = points[c].y;
- int x2 = points[c+1].x;
- int y2 = points[c+1].y;
+ else if (stroke.size() >= 2) {
+ for (int c=0; c+1<stroke.size(); ++c) {
+ int x1 = stroke[c].x;
+ int y1 = stroke[c].y;
+ int x2 = stroke[c+1].x;
+ int y2 = stroke[c+1].y;
algo_line(x1, y1, x2, y2, loop, (AlgoPixel)doPointshapePoint);
}
@@ -48,44 +46,44 @@ public:
// Closed shape (polygon outline)
if (loop->getFilled()) {
- algo_line(points[0].x, points[0].y,
- points[points.size()-1].x,
- points[points.size()-1].y, loop, (AlgoPixel)doPointshapePoint);
+ algo_line(stroke[0].x, stroke[0].y,
+ stroke[stroke.size()-1].x,
+ stroke[stroke.size()-1].y, loop, (AlgoPixel)doPointshapePoint);
}
}
- void fillPoints(ToolLoop* loop, const Points& points) override
+ void fillStroke(ToolLoop* loop, const Stroke& stroke) override
{
- if (points.size() < 3) {
- joinPoints(loop, points);
+ if (stroke.size() < 3) {
+ joinStroke(loop, stroke);
return;
}
// Contour
- joinPoints(loop, points);
+ joinStroke(loop, stroke);
// Fill content
- doc::algorithm::polygon(points.size(), (const int*)&points[0], loop, (AlgoHLine)doPointshapeHline);
+ doc::algorithm::polygon(stroke.size(), (const int*)&stroke[0], loop, (AlgoHLine)doPointshapeHline);
}
};
class IntertwineAsRectangles : public Intertwine {
public:
- void joinPoints(ToolLoop* loop, const Points& points) override
+ void joinStroke(ToolLoop* loop, const Stroke& stroke) override
{
- if (points.size() == 0)
+ if (stroke.size() == 0)
return;
- if (points.size() == 1) {
- doPointshapePoint(points[0].x, points[0].y, loop);
+ if (stroke.size() == 1) {
+ doPointshapePoint(stroke[0].x, stroke[0].y, loop);
}
- else if (points.size() >= 2) {
- for (size_t c=0; c+1<points.size(); ++c) {
- int x1 = points[c].x;
- int y1 = points[c].y;
- int x2 = points[c+1].x;
- int y2 = points[c+1].y;
+ else if (stroke.size() >= 2) {
+ for (size_t c=0; c+1<stroke.size(); ++c) {
+ int x1 = stroke[c].x;
+ int y1 = stroke[c].y;
+ int x2 = stroke[c+1].x;
+ int y2 = stroke[c+1].y;
int y;
if (x1 > x2) std::swap(x1, x2);
@@ -102,18 +100,18 @@ public:
}
}
- void fillPoints(ToolLoop* loop, const Points& points) override
+ void fillStroke(ToolLoop* loop, const Stroke& stroke) override
{
- if (points.size() < 2) {
- joinPoints(loop, points);
+ if (stroke.size() < 2) {
+ joinStroke(loop, stroke);
return;
}
- for (size_t c=0; c+1<points.size(); ++c) {
- int x1 = points[c].x;
- int y1 = points[c].y;
- int x2 = points[c+1].x;
- int y2 = points[c+1].y;
+ for (size_t c=0; c+1<stroke.size(); ++c) {
+ int x1 = stroke[c].x;
+ int y1 = stroke[c].y;
+ int x2 = stroke[c+1].x;
+ int y2 = stroke[c+1].y;
int y;
if (x1 > x2) std::swap(x1, x2);
@@ -128,20 +126,20 @@ public:
class IntertwineAsEllipses : public Intertwine {
public:
- void joinPoints(ToolLoop* loop, const Points& points) override
+ void joinStroke(ToolLoop* loop, const Stroke& stroke) override
{
- if (points.size() == 0)
+ if (stroke.size() == 0)
return;
- if (points.size() == 1) {
- doPointshapePoint(points[0].x, points[0].y, loop);
+ if (stroke.size() == 1) {
+ doPointshapePoint(stroke[0].x, stroke[0].y, loop);
}
- else if (points.size() >= 2) {
- for (size_t c=0; c+1<points.size(); ++c) {
- int x1 = points[c].x;
- int y1 = points[c].y;
- int x2 = points[c+1].x;
- int y2 = points[c+1].y;
+ else if (stroke.size() >= 2) {
+ for (size_t c=0; c+1<stroke.size(); ++c) {
+ int x1 = stroke[c].x;
+ int y1 = stroke[c].y;
+ int x2 = stroke[c+1].x;
+ int y2 = stroke[c+1].y;
if (x1 > x2) std::swap(x1, x2);
if (y1 > y2) std::swap(y1, y2);
@@ -151,18 +149,18 @@ public:
}
}
- void fillPoints(ToolLoop* loop, const Points& points) override
+ void fillStroke(ToolLoop* loop, const Stroke& stroke) override
{
- if (points.size() < 2) {
- joinPoints(loop, points);
+ if (stroke.size() < 2) {
+ joinStroke(loop, stroke);
return;
}
- for (size_t c=0; c+1<points.size(); ++c) {
- int x1 = points[c].x;
- int y1 = points[c].y;
- int x2 = points[c+1].x;
- int y2 = points[c+1].y;
+ for (size_t c=0; c+1<stroke.size(); ++c) {
+ int x1 = stroke[c].x;
+ int y1 = stroke[c].y;
+ int x2 = stroke[c+1].x;
+ int y2 = stroke[c+1].y;
if (x1 > x2) std::swap(x1, x2);
if (y1 > y2) std::swap(y1, y2);
@@ -175,79 +173,79 @@ public:
class IntertwineAsBezier : public Intertwine {
public:
- void joinPoints(ToolLoop* loop, const Points& points) override
+ void joinStroke(ToolLoop* loop, const Stroke& stroke) override
{
- if (points.size() == 0)
+ if (stroke.size() == 0)
return;
- for (size_t c=0; c<points.size(); c += 4) {
- if (points.size()-c == 1) {
- doPointshapePoint(points[c].x, points[c].y, loop);
+ for (size_t c=0; c<stroke.size(); c += 4) {
+ if (stroke.size()-c == 1) {
+ doPointshapePoint(stroke[c].x, stroke[c].y, loop);
}
- else if (points.size()-c == 2) {
- algo_line(points[c].x, points[c].y,
- points[c+1].x, points[c+1].y, loop, (AlgoPixel)doPointshapePoint);
+ else if (stroke.size()-c == 2) {
+ algo_line(stroke[c].x, stroke[c].y,
+ stroke[c+1].x, stroke[c+1].y, loop, (AlgoPixel)doPointshapePoint);
}
- else if (points.size()-c == 3) {
- algo_spline(points[c ].x, points[c ].y,
- points[c+1].x, points[c+1].y,
- points[c+1].x, points[c+1].y,
- points[c+2].x, points[c+2].y, loop, (AlgoLine)doPointshapeLine);
+ else if (stroke.size()-c == 3) {
+ algo_spline(stroke[c ].x, stroke[c ].y,
+ stroke[c+1].x, stroke[c+1].y,
+ stroke[c+1].x, stroke[c+1].y,
+ stroke[c+2].x, stroke[c+2].y, loop, (AlgoLine)doPointshapeLine);
}
else {
- algo_spline(points[c ].x, points[c ].y,
- points[c+1].x, points[c+1].y,
- points[c+2].x, points[c+2].y,
- points[c+3].x, points[c+3].y, loop, (AlgoLine)doPointshapeLine);
+ algo_spline(stroke[c ].x, stroke[c ].y,
+ stroke[c+1].x, stroke[c+1].y,
+ stroke[c+2].x, stroke[c+2].y,
+ stroke[c+3].x, stroke[c+3].y, loop, (AlgoLine)doPointshapeLine);
}
}
}
- void fillPoints(ToolLoop* loop, const Points& points) override
+ void fillStroke(ToolLoop* loop, const Stroke& stroke) override
{
- joinPoints(loop, points);
+ joinStroke(loop, stroke);
}
};
class IntertwineAsPixelPerfect : public Intertwine {
struct PPData {
- Points& pts;
+ Stroke& pts;
ToolLoop* loop;
- PPData(Points& pts, ToolLoop* loop) : pts(pts), loop(loop) { }
+ PPData(Stroke& pts, ToolLoop* loop) : pts(pts), loop(loop) { }
};
static void pixelPerfectLine(int x, int y, PPData* data)
{
gfx::Point newPoint(x, y);
- if (data->pts.empty()
- || data->pts[data->pts.size()-1] != newPoint) {
- data->pts.push_back(newPoint);
+ if (data->pts.empty() ||
+ data->pts.lastPoint() != newPoint) {
+ data->pts.addPoint(newPoint);
}
}
- Points m_pts;
+ Stroke m_pts;
public:
void prepareIntertwine() override {
- m_pts.clear();
+ m_pts.reset();
}
- void joinPoints(ToolLoop* loop, const Points& points) override {
- if (points.size() == 0)
+ void joinStroke(ToolLoop* loop, const Stroke& stroke) override {
+ if (stroke.size() == 0)
return;
- else if (m_pts.empty() && points.size() == 1) {
- m_pts = points;
+ else if (m_pts.empty() && stroke.size() == 1) {
+ m_pts = stroke;
}
else {
PPData data(m_pts, loop);
- for (size_t c=0; c+1<points.size(); ++c) {
+ for (size_t c=0; c+1<stroke.size(); ++c) {
algo_line(
- points[c].x,
- points[c].y,
- points[c+1].x,
- points[c+1].y,
+ stroke[c].x,
+ stroke[c].y,
+ stroke[c+1].x,
+ stroke[c+1].y,
(void*)&data,
(AlgoPixel)&IntertwineAsPixelPerfect::pixelPerfectLine);
}
@@ -268,18 +266,18 @@ public:
}
}
- void fillPoints(ToolLoop* loop, const Points& points) override
+ void fillStroke(ToolLoop* loop, const Stroke& stroke) override
{
- if (points.size() < 3) {
- joinPoints(loop, points);
+ if (stroke.size() < 3) {
+ joinStroke(loop, stroke);
return;
}
// Contour
- joinPoints(loop, points);
+ joinStroke(loop, stroke);
// Fill content
- doc::algorithm::polygon(points.size(), (const int*)&points[0], loop, (AlgoHLine)doPointshapeHline);
+ doc::algorithm::polygon(stroke.size(), (const int*)&stroke[0], loop, (AlgoHLine)doPointshapeHline);
}
};
diff --git a/src/app/tools/stroke.cpp b/src/app/tools/stroke.cpp
new file mode 100644
index 0000000..fdf3b75
--- /dev/null
+++ b/src/app/tools/stroke.cpp
@@ -0,0 +1,62 @@
+// Aseprite
+// Copyright (C) 2001-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/tools/stroke.h"
+
+namespace app {
+namespace tools {
+
+void Stroke::reset()
+{
+ m_points.clear();
+}
+
+void Stroke::reset(int n, const gfx::Point& point)
+{
+ m_points.resize(n, point);
+}
+
+void Stroke::addPoint(const gfx::Point& point)
+{
+ m_points.push_back(point);
+}
+
+void Stroke::offset(const gfx::Point& delta)
+{
+ for (auto& p : m_points)
+ p += delta;
+}
+
+gfx::Rect Stroke::bounds() const
+{
+ if (m_points.empty())
+ return gfx::Rect();
+
+ gfx::Point
+ minpt(m_points[0]),
+ maxpt(m_points[0]);
+
+ for (size_t c=1; c<m_points.size(); ++c) {
+ int x = m_points[c].x;
+ int y = m_points[c].y;
+ if (minpt.x > x) minpt.x = x;
+ if (minpt.y > y) minpt.y = y;
+ if (maxpt.x < x) maxpt.x = x;
+ if (maxpt.y < y) maxpt.y = y;
+ }
+
+ return gfx::Rect(minpt.x, minpt.y,
+ maxpt.x - minpt.x + 1,
+ maxpt.y - minpt.y + 1);
+}
+
+} // namespace tools
+} // namespace app
diff --git a/src/app/tools/stroke.h b/src/app/tools/stroke.h
new file mode 100644
index 0000000..618ae0f
--- /dev/null
+++ b/src/app/tools/stroke.h
@@ -0,0 +1,66 @@
+// Aseprite
+// Copyright (C) 2001-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.
+
+#ifndef APP_TOOLS_STROKE_H_INCLUDED
+#define APP_TOOLS_STROKE_H_INCLUDED
+#pragma once
+
+#include "gfx/point.h"
+#include "gfx/rect.h"
+
+#include <vector>
+
+namespace app {
+ namespace tools {
+
+ class Stroke {
+ public:
+ typedef std::vector<gfx::Point> Points;
+ typedef Points::const_iterator const_iterator;
+
+ const_iterator begin() const { return m_points.begin(); }
+ const_iterator end() const { return m_points.end(); }
+
+ bool empty() const { return m_points.empty(); }
+ int size() const { return (int)m_points.size(); }
+
+ const gfx::Point& operator[](int i) const { return m_points[i]; }
+ gfx::Point& operator[](int i) { return m_points[i]; }
+
+ const gfx::Point& firstPoint() const {
+ ASSERT(!m_points.empty());
+ return m_points[0];
+ }
+
+ const gfx::Point& lastPoint() const {
+ ASSERT(!m_points.empty());
+ return m_points[m_points.size()-1];
+ }
+
+ // Clears the whole stroke.
+ void reset();
+
+ // Reset the stroke as "n" points in the given "point" position.
+ void reset(int n, const gfx::Point& point);
+
+ // Adds a new point to the stroke.
+ void addPoint(const gfx::Point& point);
+
+ // Displaces all X,Y coordinates the given delta.
+ void offset(const gfx::Point& delta);
+
+ // Returns the bounds of the stroke (minimum/maximum position).
+ gfx::Rect bounds() const;
+
+ public:
+ Points m_points;
+ };
+
+ } // namespace tools
+} // namespace app
+
+#endif
diff --git a/src/app/tools/symmetries.cpp b/src/app/tools/symmetries.cpp
new file mode 100644
index 0000000..6f34f79
--- /dev/null
+++ b/src/app/tools/symmetries.cpp
@@ -0,0 +1,40 @@
+// 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/tools/symmetries.h"
+
+#include "app/tools/stroke.h"
+
+namespace app {
+namespace tools {
+
+void HorizontalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& strokes)
+{
+ strokes.push_back(mainStroke);
+
+ Stroke stroke2;
+ for (const auto& pt : mainStroke)
+ stroke2.addPoint(gfx::Point(m_x - (pt.x - m_x + 1), pt.y));
+ strokes.push_back(stroke2);
+}
+
+void VerticalSymmetry::generateStrokes(const Stroke& mainStroke, Strokes& strokes)
+{
+ strokes.push_back(mainStroke);
+
+ Stroke stroke2;
+ for (const auto& pt : mainStroke)
+ stroke2.addPoint(gfx::Point(pt.x, m_y - (pt.y - m_y + 1)));
+ strokes.push_back(stroke2);
+}
+
+} // namespace tools
+} // namespace app
diff --git a/src/app/tools/symmetries.h b/src/app/tools/symmetries.h
new file mode 100644
index 0000000..388e6e1
--- /dev/null
+++ b/src/app/tools/symmetries.h
@@ -0,0 +1,37 @@
+// 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.
+
+#ifndef APP_TOOLS_SYMMETRIES_H_INCLUDED
+#define APP_TOOLS_SYMMETRIES_H_INCLUDED
+#pragma once
+
+#include "app/tools/stroke.h"
+#include "app/tools/symmetry.h"
+
+namespace app {
+namespace tools {
+
+class HorizontalSymmetry : public Symmetry {
+public:
+ HorizontalSymmetry(int x) : m_x(x) { }
+ void generateStrokes(const Stroke& mainStroke, Strokes& strokes) override;
+private:
+ int m_x;
+};
+
+class VerticalSymmetry : public Symmetry {
+public:
+ VerticalSymmetry(int y) : m_y(y) { }
+ void generateStrokes(const Stroke& mainStroke, Strokes& strokes) override;
+private:
+ int m_y;
+};
+
+} // namespace tools
+} // namespace app
+
+#endif
diff --git a/src/app/tools/symmetry.h b/src/app/tools/symmetry.h
new file mode 100644
index 0000000..84faae6
--- /dev/null
+++ b/src/app/tools/symmetry.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef APP_TOOLS_SYMMETRY_H_INCLUDED
+#define APP_TOOLS_SYMMETRY_H_INCLUDED
+#pragma once
+
+#include "app/tools/stroke.h"
+
+#include <vector>
+
+namespace app {
+ namespace tools {
+
+ typedef std::vector<Stroke> Strokes;
+
+ // This class controls user input.
+ class Symmetry {
+ public:
+ virtual ~Symmetry() { }
+ virtual void generateStrokes(const Stroke& stroke, Strokes& strokes) = 0;
+ };
+
+ } // namespace tools
+} // namespace app
+
+#endif
diff --git a/src/app/tools/tool_box.cpp b/src/app/tools/tool_box.cpp
index 2af9585..1844b6a 100644
--- a/src/app/tools/tool_box.cpp
+++ b/src/app/tools/tool_box.cpp
@@ -16,6 +16,7 @@
#include "app/tools/ink.h"
#include "app/tools/intertwine.h"
#include "app/tools/point_shape.h"
+#include "app/tools/stroke.h"
#include "app/tools/tool_group.h"
#include "app/tools/tool_loop.h"
#include "base/bind.h"
diff --git a/src/app/tools/tool_loop.h b/src/app/tools/tool_loop.h
index 51cb148..9e8306f 100644
--- a/src/app/tools/tool_loop.h
+++ b/src/app/tools/tool_loop.h
@@ -43,6 +43,7 @@ namespace app {
class Ink;
class Intertwine;
class PointShape;
+ class Symmetry;
class Tool;
using namespace doc;
@@ -202,6 +203,7 @@ namespace app {
virtual PointShape* getPointShape() = 0;
virtual Intertwine* getIntertwine() = 0;
virtual TracePolicy getTracePolicy() = 0;
+ virtual Symmetry* getSymmetry() = 0;
virtual const doc::Remap* getShadingRemap() = 0;
diff --git a/src/app/tools/tool_loop_manager.cpp b/src/app/tools/tool_loop_manager.cpp
index f700225..eccb1bc 100644
--- a/src/app/tools/tool_loop_manager.cpp
+++ b/src/app/tools/tool_loop_manager.cpp
@@ -17,12 +17,15 @@
#include "app/tools/ink.h"
#include "app/tools/intertwine.h"
#include "app/tools/point_shape.h"
+#include "app/tools/symmetry.h"
#include "app/tools/tool_loop.h"
#include "doc/image.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "gfx/region.h"
+#include <climits>
+
namespace app {
namespace tools {
@@ -49,7 +52,7 @@ void ToolLoopManager::prepareLoop(const Pointer& pointer,
ui::KeyModifiers modifiers)
{
// Start with no points at all
- m_points.clear();
+ m_stroke.reset();
// Prepare the ink
m_toolLoop->getInk()->prepareInk(m_toolLoop);
@@ -106,10 +109,10 @@ void ToolLoopManager::pressButton(const Pointer& pointer)
m_oldPoint = spritePoint;
snapToGrid(spritePoint);
- m_toolLoop->getController()->pressButton(m_points, spritePoint);
+ m_toolLoop->getController()->pressButton(m_stroke, spritePoint);
std::string statusText;
- m_toolLoop->getController()->getStatusBarText(m_points, statusText);
+ m_toolLoop->getController()->getStatusBarText(m_stroke, statusText);
m_toolLoop->updateStatusBar(statusText.c_str());
doLoopStep(false);
@@ -125,7 +128,7 @@ bool ToolLoopManager::releaseButton(const Pointer& pointer)
Point spritePoint = pointer.point();
snapToGrid(spritePoint);
- bool res = m_toolLoop->getController()->releaseButton(m_points, spritePoint);
+ bool res = m_toolLoop->getController()->releaseButton(m_stroke, spritePoint);
if (!res && (m_toolLoop->getInk()->isSelection() || m_toolLoop->getFilled())) {
m_toolLoop->getInk()->setFinalStep(m_toolLoop, true);
@@ -150,10 +153,10 @@ void ToolLoopManager::movement(const Pointer& pointer)
m_oldPoint = spritePoint;
snapToGrid(spritePoint);
- m_toolLoop->getController()->movement(m_toolLoop, m_points, spritePoint);
+ m_toolLoop->getController()->movement(m_toolLoop, m_stroke, spritePoint);
std::string statusText;
- m_toolLoop->getController()->getStatusBarText(m_points, statusText);
+ m_toolLoop->getController()->getStatusBarText(m_stroke, statusText);
m_toolLoop->updateStatusBar(statusText.c_str());
doLoopStep(false);
@@ -161,18 +164,28 @@ void ToolLoopManager::movement(const Pointer& pointer)
void ToolLoopManager::doLoopStep(bool last_step)
{
- Points points_to_interwine;
+ // Original set of points to interwine (original user stroke).
+ Stroke main_stroke;
if (!last_step)
- m_toolLoop->getController()->getPointsToInterwine(m_points, points_to_interwine);
+ m_toolLoop->getController()->getStrokeToInterwine(m_stroke, main_stroke);
else
- points_to_interwine = m_points;
-
- Point offset(m_toolLoop->getOffset());
- for (size_t i=0; i<points_to_interwine.size(); ++i)
- points_to_interwine[i] += offset;
+ main_stroke = m_stroke;
+ main_stroke.offset(m_toolLoop->getOffset());
+
+ // Apply symmetry
+ Symmetry* symmetry = m_toolLoop->getSymmetry();
+ Strokes strokes;
+ if (symmetry)
+ symmetry->generateStrokes(main_stroke, strokes);
+ else
+ strokes.push_back(main_stroke);
// Calculate the area to be updated in all document observers.
- calculateDirtyArea(points_to_interwine);
+ gfx::Rect strokeBounds;
+ for (const Stroke& stroke : strokes)
+ strokeBounds |= stroke.bounds();
+
+ calculateDirtyArea(strokeBounds);
// Validate source image area.
if (m_toolLoop->getInk()->needsSpecialSourceArea()) {
@@ -201,10 +214,12 @@ void ToolLoopManager::doLoopStep(bool last_step)
m_toolLoop->validateDstImage(m_dirtyArea);
// Get the modified area in the sprite with this intertwined set of points
- if (!m_toolLoop->getFilled() || (!last_step && !m_toolLoop->getPreviewFilled()))
- m_toolLoop->getIntertwine()->joinPoints(m_toolLoop, points_to_interwine);
- else
- m_toolLoop->getIntertwine()->fillPoints(m_toolLoop, points_to_interwine);
+ for (const Stroke& stroke : strokes) {
+ if (!m_toolLoop->getFilled() || (!last_step && !m_toolLoop->getPreviewFilled()))
+ m_toolLoop->getIntertwine()->joinStroke(m_toolLoop, stroke);
+ else
+ m_toolLoop->getIntertwine()->fillStroke(m_toolLoop, stroke);
+ }
if (m_toolLoop->getTracePolicy() == TracePolicy::Overlap) {
// Copy destination to source (yes, destination to source). In
@@ -226,7 +241,7 @@ void ToolLoopManager::snapToGrid(Point& point)
point = snap_to_grid(m_toolLoop->getGridBounds(), point);
}
-void ToolLoopManager::calculateDirtyArea(const Points& points)
+void ToolLoopManager::calculateDirtyArea(const gfx::Rect& strokeBounds)
{
// Save the current dirty area if it's needed
Region prevDirtyArea;
@@ -236,14 +251,19 @@ void ToolLoopManager::calculateDirtyArea(const Points& points)
// Start with a fresh dirty area
m_dirtyArea.clear();
- if (points.size() > 0) {
- Point minpt, maxpt;
- calculateMinMax(points, minpt, maxpt);
-
+ if (!strokeBounds.isEmpty()) {
// Expand the dirty-area with the pen width
Rect r1, r2;
- m_toolLoop->getPointShape()->getModifiedArea(m_toolLoop, minpt.x, minpt.y, r1);
- m_toolLoop->getPointShape()->getModifiedArea(m_toolLoop, maxpt.x, maxpt.y, r2);
+
+ m_toolLoop->getPointShape()->getModifiedArea(
+ m_toolLoop,
+ strokeBounds.x,
+ strokeBounds.y, r1);
+
+ m_toolLoop->getPointShape()->getModifiedArea(
+ m_toolLoop,
+ strokeBounds.x+strokeBounds.w-1,
+ strokeBounds.y+strokeBounds.h-1, r2);
m_dirtyArea.createUnion(m_dirtyArea, Region(r1.createUnion(r2)));
}
@@ -300,22 +320,5 @@ void ToolLoopManager::calculateDirtyArea(const Points& points)
}
}
-void ToolLoopManager::calculateMinMax(const Points& points, Point& minpt, Point& maxpt)
-{
- ASSERT(points.size() > 0);
-
- minpt.x = points[0].x;
- minpt.y = points[0].y;
- maxpt.x = points[0].x;
- maxpt.y = points[0].y;
-
- for (size_t c=1; c<points.size(); ++c) {
- minpt.x = MIN(minpt.x, points[c].x);
- minpt.y = MIN(minpt.y, points[c].y);
- maxpt.x = MAX(maxpt.x, points[c].x);
- maxpt.y = MAX(maxpt.y, points[c].y);
- }
-}
-
} // namespace tools
} // namespace app
diff --git a/src/app/tools/tool_loop_manager.h b/src/app/tools/tool_loop_manager.h
index e6efdcb..a3462e2 100644
--- a/src/app/tools/tool_loop_manager.h
+++ b/src/app/tools/tool_loop_manager.h
@@ -9,6 +9,7 @@
#define APP_TOOLS_TOOL_LOOP_MANAGER_H_INCLUDED
#pragma once
+#include "app/tools/stroke.h"
#include "gfx/point.h"
#include "gfx/region.h"
#include "ui/keys.h"
@@ -86,18 +87,13 @@ namespace app {
void movement(const Pointer& pointer);
private:
- typedef std::vector<gfx::Point> Points;
-
void doLoopStep(bool last_step);
void snapToGrid(gfx::Point& point);
- void calculateDirtyArea(const Points& points);
- void calculateMinMax(const Points& points,
- gfx::Point& minpt,
- gfx::Point& maxpt);
+ void calculateDirtyArea(const gfx::Rect& strokeBounds);
ToolLoop* m_toolLoop;
- Points m_points;
+ Stroke m_stroke;
Pointer m_lastPointer;
gfx::Point m_oldPoint;
gfx::Region& m_dirtyArea;
@@ -106,4 +102,4 @@ namespace app {
} // namespace tools
} // namespace app
-#endif // TOOLS_TOOL_H_INCLUDED
+#endif
diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp
index 15a2b5c..daeee2c 100644
--- a/src/app/ui/context_bar.cpp
+++ b/src/app/ui/context_bar.cpp
@@ -13,6 +13,7 @@
#include "app/app.h"
#include "app/commands/commands.h"
+#include "app/document.h"
#include "app/modules/gfx.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
@@ -1116,6 +1117,49 @@ protected:
}
};
+class ContextBar::SymmetryField : public ButtonSet {
+public:
+ SymmetryField() : ButtonSet(3) {
+ SkinTheme* theme = SkinTheme::instance();
+ addItem(theme->parts.noSymmetry());
+ addItem(theme->parts.horizontalSymmetry());
+ addItem(theme->parts.verticalSymmetry());
+ }
+
+ void setupTooltips(TooltipManager* tooltipManager) {
+ tooltipManager->addTooltipFor(at(0), "Without Symmetry", BOTTOM);
+ tooltipManager->addTooltipFor(at(1), "Horizontal Symmetry", BOTTOM);
+ tooltipManager->addTooltipFor(at(2), "Vertical Symmetry", BOTTOM);
+ }
+
+ void updateWithCurrentDocument() {
+ Document* doc = UIContext::instance()->activeDocument();
+ if (!doc)
+ return;
+
+ DocumentPreferences& docPref = Preferences::instance().document(doc);
+
+ setSelectedItem((int)docPref.symmetry.mode());
+ }
+
+private:
+ void onItemChange(Item* item) override {
+ ButtonSet::onItemChange(item);
+
+ Document* doc = UIContext::instance()->activeDocument();
+ if (!doc)
+ return;
+
+ DocumentPreferences& docPref =
+ Preferences::instance().document(doc);
+
+ docPref.symmetry.mode((app::gen::SymmetryMode)selectedItem());
+
+ // Redraw symmetry rules
+ doc->notifyGeneralUpdate();
+ }
+};
+
ContextBar::ContextBar()
: Box(HORIZONTAL)
{
@@ -1175,6 +1219,9 @@ ContextBar::ContextBar()
setup_mini_font(m_toleranceLabel);
setup_mini_font(m_inkOpacityLabel);
+ addChild(m_symmetry = new SymmetryField());
+ m_symmetry->setVisible(Preferences::instance().symmetryMode.enabled());
+
TooltipManager* tooltipManager = new TooltipManager();
addChild(tooltipManager);
@@ -1195,10 +1242,14 @@ ContextBar::ContextBar()
m_selectionMode->setupTooltips(tooltipManager);
m_dropPixels->setupTooltips(tooltipManager);
m_freehandAlgo->setupTooltips(tooltipManager);
+ m_symmetry->setupTooltips(tooltipManager);
Preferences::instance().toolBox.activeTool.AfterChange.connect(
Bind<void>(&ContextBar::onCurrentToolChange, this));
+ Preferences::instance().symmetryMode.enabled.AfterChange.connect(
+ Bind<void>(&ContextBar::onSymmetryModeChange, this));
+
m_dropPixels->DropPixels.connect(&ContextBar::onDropPixels, this);
setActiveBrush(createBrushFromPreferences());
@@ -1240,6 +1291,11 @@ void ContextBar::onCurrentToolChange()
}
}
+void ContextBar::onSymmetryModeChange()
+{
+ updateForCurrentTool();
+}
+
void ContextBar::onDropPixels(ContextBarObserver::DropAction action)
{
notifyObservers(&ContextBarObserver::onDropPixels, action);
@@ -1394,6 +1450,8 @@ void ContextBar::updateForTool(tools::Tool* tool)
m_pivot->setVisible(true);
m_dropPixels->setVisible(false);
m_selectBoxHelp->setVisible(false);
+ m_symmetry->setVisible(Preferences::instance().symmetryMode.enabled());
+ m_symmetry->updateWithCurrentDocument();
layout();
}
diff --git a/src/app/ui/context_bar.h b/src/app/ui/context_bar.h
index 83ce9c7..1fa8cfa 100644
--- a/src/app/ui/context_bar.h
+++ b/src/app/ui/context_bar.h
@@ -83,6 +83,7 @@ namespace app {
void onBrushSizeChange();
void onBrushAngleChange();
void onCurrentToolChange();
+ void onSymmetryModeChange();
void onDropPixels(ContextBarObserver::DropAction action);
struct BrushSlot {
@@ -120,6 +121,7 @@ namespace app {
class EyedropperField;
class DropPixelsField;
class AutoSelectLayerField;
+ class SymmetryField;
BrushTypeField* m_brushType;
BrushAngleField* m_brushAngle;
@@ -150,6 +152,7 @@ namespace app {
doc::BrushRef m_activeBrush;
BrushSlots m_brushes;
ui::Label* m_selectBoxHelp;
+ SymmetryField* m_symmetry;
ScopedConnection m_sizeConn;
ScopedConnection m_angleConn;
ScopedConnection m_opacityConn;
diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp
index b8a5236..d9e8b65 100644
--- a/src/app/ui/editor/editor.cpp
+++ b/src/app/ui/editor/editor.cpp
@@ -632,6 +632,37 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc)
}
}
+ // Symmetry mode
+ {
+ switch (docPref.symmetry.mode()) {
+ case app::gen::SymmetryMode::NONE:
+ // Do nothing
+ break;
+ case app::gen::SymmetryMode::HORIZONTAL: {
+ int x = docPref.symmetry.xAxis();
+ if (x > 0) {
+ gfx::Color color = color_utils::color_for_ui(docPref.grid.color());
+ g->drawVLine(color,
+ enclosingRect.x + m_zoom.apply(x),
+ enclosingRect.y,
+ enclosingRect.h);
+ }
+ break;
+ }
+ case app::gen::SymmetryMode::VERTICAL: {
+ int y = docPref.symmetry.yAxis();
+ if (y > 0) {
+ gfx::Color color = color_utils::color_for_ui(docPref.grid.color());
+ g->drawHLine(color,
+ enclosingRect.x,
+ enclosingRect.y + m_zoom.apply(y),
+ enclosingRect.w);
+ }
+ break;
+ }
+ }
+ }
+
if (m_flags & kShowOutside) {
// Draw the borders that enclose the sprite.
enclosingRect.enlarge(1);
diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp
index 27094e4..638714a 100644
--- a/src/app/ui/editor/tool_loop_impl.cpp
+++ b/src/app/ui/editor/tool_loop_impl.cpp
@@ -26,6 +26,7 @@
#include "app/tools/freehand_algorithm.h"
#include "app/tools/ink.h"
#include "app/tools/point_shape.h"
+#include "app/tools/symmetries.h"
#include "app/tools/tool.h"
#include "app/tools/tool_box.h"
#include "app/tools/tool_loop.h"
@@ -77,6 +78,7 @@ protected:
tools::PointShape* m_pointShape;
tools::Intertwine* m_intertwine;
tools::TracePolicy m_tracePolicy;
+ base::UniquePtr<tools::Symmetry> m_symmetry;
base::UniquePtr<doc::Remap> m_shadingRemap;
doc::color_t m_fgColor;
doc::color_t m_bgColor;
@@ -110,6 +112,7 @@ public:
, m_pointShape(m_tool->getPointShape(m_button))
, m_intertwine(m_tool->getIntertwine(m_button))
, m_tracePolicy(m_tool->getTracePolicy(m_button))
+ , m_symmetry(nullptr)
, m_fgColor(color_utils::color_for_target_mask(fgColor, ColorTarget(m_layer)))
, m_bgColor(color_utils::color_for_target_mask(bgColor, ColorTarget(m_layer)))
, m_primaryColor(button == tools::ToolLoop::Left ? m_fgColor: m_bgColor)
@@ -137,6 +140,28 @@ public:
}
}
+ // Symmetry mode
+ switch (m_docPref.symmetry.mode()) {
+
+ case app::gen::SymmetryMode::NONE:
+ ASSERT(m_symmetry == nullptr);
+ break;
+
+ case app::gen::SymmetryMode::HORIZONTAL:
+ if (m_docPref.symmetry.xAxis() == 0)
+ m_docPref.symmetry.xAxis(m_sprite->width()/2);
+
+ m_symmetry.reset(new app::tools::HorizontalSymmetry(m_docPref.symmetry.xAxis()));
+ break;
+
+ case app::gen::SymmetryMode::VERTICAL:
+ if (m_docPref.symmetry.yAxis() == 0)
+ m_docPref.symmetry.yAxis(m_sprite->height()/2);
+
+ m_symmetry.reset(new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis()));
+ break;
+ }
+
// Ignore opacity for these inks
if (!tools::inkHasOpacity(m_toolPref.ink()) &&
m_brush->type() != kImageBrushType &&
@@ -194,6 +219,7 @@ public:
tools::PointShape* getPointShape() override { return m_pointShape; }
tools::Intertwine* getIntertwine() override { return m_intertwine; }
tools::TracePolicy getTracePolicy() override { return m_tracePolicy; }
+ tools::Symmetry* getSymmetry() override { return m_symmetry.get(); }
doc::Remap* getShadingRemap() override { return m_shadingRemap; }
gfx::Region& getDirtyArea() override {
diff --git a/src/app/ui_context.cpp b/src/app/ui_context.cpp
index bb54022..527bda3 100644
--- a/src/app/ui_context.cpp
+++ b/src/app/ui_context.cpp
@@ -63,14 +63,21 @@ bool UIContext::isUIAvailable() const
DocumentView* UIContext::activeView() const
{
if (!isUIAvailable())
- return NULL;
+ return nullptr;
+
+ MainWindow* mainWindow = App::instance()->getMainWindow();
+ if (!mainWindow)
+ return nullptr;
+
+ Workspace* workspace = mainWindow->getWorkspace();
+ if (!workspace)
+ return nullptr;
- Workspace* workspace = App::instance()->getMainWindow()->getWorkspace();
WorkspaceView* view = workspace->activeView();
if (DocumentView* docView = dynamic_cast<DocumentView*>(view))
return docView;
else
- return NULL;
+ return nullptr;
}
void UIContext::setActiveView(DocumentView* docView)
--
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