diff --git a/data/strings/en.ini b/data/strings/en.ini index 2eb01c1a4..e7a280e29 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -576,6 +576,8 @@ as = As: merged_layers = Duplicate merged layers only [dynamics] +stabilizer = Stabilizer +stabilizer_tooltip = Stabilizer radius to avoid shaky lines pressure = Pressure pressure_tooltip = Control parameters through the pen pressure sensor velocity = Velocity diff --git a/data/widgets/dynamics.xml b/data/widgets/dynamics.xml index cd424537a..884770ee5 100644 --- a/data/widgets/dynamics.xml +++ b/data/widgets/dynamics.xml @@ -1,7 +1,14 @@ <!-- Aseprite --> -<!-- Copyright (C) 2020 Igara Studio S.A. --> +<!-- Copyright (C) 2020-2021 Igara Studio S.A. --> <gui> <vbox id="dynamics"> + <hbox> + <check id="stabilizer" text="@.stabilizer" + tooltip="@.stabilizer_tooltip" tooltip_dir="bottom" /> + <slider id="stabilizer_factor" value="16" min="0" max="64" expansive="true" style="mini_slider" + tooltip="@.stabilizer_tooltip" tooltip_dir="bottom" /> + </hbox> + <hbox> <buttonset id="values" columns="3"> <item text="" /> diff --git a/src/app/tools/dynamics.h b/src/app/tools/dynamics.h index f6360fbb7..459c63b79 100644 --- a/src/app/tools/dynamics.h +++ b/src/app/tools/dynamics.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020 Igara Studio S.A. +// Copyright (C) 2020-2021 Igara Studio S.A. // // This program is distributed under the terms of // the End-User License Agreement for Aseprite. @@ -25,6 +25,7 @@ namespace tools { }; struct DynamicsOptions { + int stabilizerFactor = 0; DynamicSensor size = DynamicSensor::Static; DynamicSensor angle = DynamicSensor::Static; DynamicSensor gradient = DynamicSensor::Static; diff --git a/src/app/tools/tool_loop_manager.cpp b/src/app/tools/tool_loop_manager.cpp index 23a9e2c99..488de1631 100644 --- a/src/app/tools/tool_loop_manager.cpp +++ b/src/app/tools/tool_loop_manager.cpp @@ -30,6 +30,7 @@ #include "gfx/region.h" #include <climits> +#include <cmath> #define TOOL_TRACE(...) // TRACEARGS(__VA_ARGS__) @@ -118,6 +119,8 @@ void ToolLoopManager::pressButton(const Pointer& pointer) return; } + m_stabilizerCenter = pointer.point(); + Stroke::Pt spritePoint = getSpriteStrokePt(pointer); m_toolLoop->getController()->pressButton(m_toolLoop, m_stroke, spritePoint); @@ -163,8 +166,27 @@ bool ToolLoopManager::releaseButton(const Pointer& pointer) return res; } -void ToolLoopManager::movement(const Pointer& pointer) +void ToolLoopManager::movement(Pointer pointer) { + // Filter points with the stabilizer + if (m_dynamics.stabilizerFactor > 0) { + const double f = m_dynamics.stabilizerFactor; + const gfx::Point delta = (pointer.point() - m_stabilizerCenter); + const double distance = std::sqrt(delta.x*delta.x + delta.y*delta.y); + + const double angle = std::atan2(delta.y, delta.x); + const gfx::PointF newPoint(m_stabilizerCenter.x + distance/f*std::cos(angle), + m_stabilizerCenter.y + distance/f*std::sin(angle)); + + m_stabilizerCenter = newPoint; + + pointer = Pointer(gfx::Point(newPoint), + pointer.velocity(), + pointer.button(), + pointer.type(), + pointer.pressure()); + } + m_lastPointer = pointer; if (isCanceled()) diff --git a/src/app/tools/tool_loop_manager.h b/src/app/tools/tool_loop_manager.h index 9ca4c6693..84824a6cc 100644 --- a/src/app/tools/tool_loop_manager.h +++ b/src/app/tools/tool_loop_manager.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 Igara Studio S.A. +// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -76,7 +76,7 @@ public: bool releaseButton(const Pointer& pointer); // Should be called each time the user moves the mouse inside the editor. - void movement(const Pointer& pointer); + void movement(Pointer pointer); const Pointer& lastPointer() const { return m_lastPointer; } @@ -97,6 +97,7 @@ private: gfx::Region m_nextDirtyArea; doc::Brush m_brush0; DynamicsOptions m_dynamics; + gfx::PointF m_stabilizerCenter; }; } // namespace tools diff --git a/src/app/ui/dynamics_popup.cpp b/src/app/ui/dynamics_popup.cpp index 9da937a8d..60f27b79e 100644 --- a/src/app/ui/dynamics_popup.cpp +++ b/src/app/ui/dynamics_popup.cpp @@ -215,6 +215,19 @@ DynamicsPopup::DynamicsPopup(Delegate* delegate) , m_ditheringSel(new DitheringSelector(DitheringSelector::SelectMatrix)) , m_fromTo(tools::ColorFromTo::BgToFg) { + m_dynamics->stabilizer()->Click.connect( + [this](){ + if (m_dynamics->stabilizer()->isSelected() && + m_dynamics->stabilizerFactor()->getValue() == 0) { + // TODO default value when we enable stabilizer when it's zero + m_dynamics->stabilizerFactor()->setValue(16); + } + }); + m_dynamics->stabilizerFactor()->Change.connect( + [this](){ + m_dynamics->stabilizer()->setSelected(m_dynamics->stabilizerFactor()->getValue() > 0); + }); + m_dynamics->values()->ItemChange.connect( [this](ButtonSet::Item* item){ onValuesChange(item); @@ -254,6 +267,14 @@ DynamicsPopup::DynamicsPopup(Delegate* delegate) tools::DynamicsOptions DynamicsPopup::getDynamics() const { tools::DynamicsOptions opts; + + if (m_dynamics->stabilizer()->isSelected()) { + opts.stabilizerFactor = m_dynamics->stabilizerFactor()->getValue(); + } + else { + opts.stabilizerFactor = 0; + } + opts.size = (isCheck(SIZE_WITH_PRESSURE) ? tools::DynamicSensor::Pressure: isCheck(SIZE_WITH_VELOCITY) ? tools::DynamicSensor::Velocity: