[calligra] krita: [FEATURE] Implemented "Delayed Stroke" feature for brush smoothing

Dmitry Kazakov dimula73 at gmail.com
Fri Jun 27 13:56:59 UTC 2014


Git commit 98b20b037c237f0bf05685373074e3f211ba8833 by Dmitry Kazakov.
Committed on 27/06/2014 at 11:10.
Pushed by dkazakov into branch 'master'.

[FEATURE] Implemented "Delayed Stroke" feature for brush smoothing

This patch adds two improvements to the Stabilizer algorithm implemented
by Juan Luis Boya García:

1) "Delayed Stroke" feature. If enabled the brush has a dead zone, where
   nothing is painted. This is extremely handy when one needs to paint
   a smooth line with explicit angles.

2) "Finish line" option for the brush stanilizer. When option is disabled,
   the line will not jump to the cursor position in the end of the stroke.

CCMAIL:kimageshop at kde.org

M  +6    -0    krita/image/brushengine/kis_paint_information.cc
M  +1    -0    krita/image/brushengine/kis_paint_information.h
M  +104  -38   krita/plugins/tools/defaulttools/kis_tool_brush.cc
M  +25   -0    krita/plugins/tools/defaulttools/kis_tool_brush.h
M  +30   -0    krita/ui/kis_config.cc
M  +9    -0    krita/ui/kis_config.h
M  +39   -5    krita/ui/tool/kis_smoothing_options.cpp
M  +17   -0    krita/ui/tool/kis_smoothing_options.h
M  +13   -1    krita/ui/tool/kis_tool_freehand.cc
M  +2    -1    krita/ui/tool/kis_tool_freehand.h
M  +103  -62   krita/ui/tool/kis_tool_freehand_helper.cpp
M  +13   -4    krita/ui/tool/kis_tool_freehand_helper.h
M  +42   -0    krita/ui/tool/kis_tool_paint.cc
M  +3    -0    krita/ui/tool/kis_tool_paint.h

http://commits.kde.org/calligra/98b20b037c237f0bf05685373074e3f211ba8833

diff --git a/krita/image/brushengine/kis_paint_information.cc b/krita/image/brushengine/kis_paint_information.cc
index f62baea..952f51f 100644
--- a/krita/image/brushengine/kis_paint_information.cc
+++ b/krita/image/brushengine/kis_paint_information.cc
@@ -321,6 +321,12 @@ QDebug operator<<(QDebug dbg, const KisPaintInformation &info)
     return dbg.space();
 }
 
+KisPaintInformation KisPaintInformation::mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
+{
+    QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
+    return mix(pt, t, pi1, pi2);
+}
+
 KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
 {
     qreal pressure = (1 - t) * pi1.pressure() + t * pi2.pressure();
diff --git a/krita/image/brushengine/kis_paint_information.h b/krita/image/brushengine/kis_paint_information.h
index 1599fc0..9b3be8f 100644
--- a/krita/image/brushengine/kis_paint_information.h
+++ b/krita/image/brushengine/kis_paint_information.h
@@ -184,6 +184,7 @@ public:
     
     /// (1-t) * p1 + t * p2
     static KisPaintInformation mix(const QPointF& p, qreal t, const KisPaintInformation& p1, const KisPaintInformation& p2);
+    static KisPaintInformation mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2);
     static qreal tiltDirection(const KisPaintInformation& info, bool normalize=true);
     static qreal tiltElevation(const KisPaintInformation& info, qreal maxTiltX=60.0, qreal maxTiltY=60.0, bool normalize=true);
 
diff --git a/krita/plugins/tools/defaulttools/kis_tool_brush.cc b/krita/plugins/tools/defaulttools/kis_tool_brush.cc
index 3101286..bbe88b5 100644
--- a/krita/plugins/tools/defaulttools/kis_tool_brush.cc
+++ b/krita/plugins/tools/defaulttools/kis_tool_brush.cc
@@ -48,74 +48,82 @@ KisToolBrush::~KisToolBrush()
 
 int KisToolBrush::smoothingType() const
 {
-    return m_smoothingOptions.smoothingType();
+    return smoothingOptions()->smoothingType();
 }
 
 bool KisToolBrush::smoothPressure() const
 {
-    return m_smoothingOptions.smoothPressure();
+    return smoothingOptions()->smoothPressure();
 }
 
 int KisToolBrush::smoothnessQuality() const
 {
-    return m_smoothingOptions.smoothnessDistance();
+    return smoothingOptions()->smoothnessDistance();
 }
 
 qreal KisToolBrush::smoothnessFactor() const
 {
-    return m_smoothingOptions.tailAggressiveness();
+    return smoothingOptions()->tailAggressiveness();
 }
 
 void KisToolBrush::slotSetSmoothingType(int index)
 {
     switch (index) {
     case 0:
-        m_smoothingOptions.setSmoothingType(KisSmoothingOptions::NO_SMOOTHING);
-        m_sliderSmoothnessDistance->setEnabled(false);
-        m_sliderTailAggressiveness->setEnabled(false);
-        m_chkSmoothPressure->setEnabled(false);
-        m_chkUseScalableDistance->setEnabled(false);
+        smoothingOptions()->setSmoothingType(KisSmoothingOptions::NO_SMOOTHING);
+        showControl(m_sliderSmoothnessDistance, false);
+        showControl(m_sliderTailAggressiveness, false);
+        showControl(m_chkSmoothPressure, false);
+        showControl(m_chkUseScalableDistance, false);
+        showControl(m_sliderDelayDistance, false);
+        showControl(m_chkFinishStabilizedCurve, false);
         break;
     case 1:
-        m_smoothingOptions.setSmoothingType(KisSmoothingOptions::SIMPLE_SMOOTHING);
-        m_sliderSmoothnessDistance->setEnabled(false);
-        m_sliderTailAggressiveness->setEnabled(false);
-        m_chkSmoothPressure->setEnabled(false);
-        m_chkUseScalableDistance->setEnabled(false);
+        smoothingOptions()->setSmoothingType(KisSmoothingOptions::SIMPLE_SMOOTHING);
+        showControl(m_sliderSmoothnessDistance, false);
+        showControl(m_sliderTailAggressiveness, false);
+        showControl(m_chkSmoothPressure, false);
+        showControl(m_chkUseScalableDistance, false);
+        showControl(m_sliderDelayDistance, false);
+        showControl(m_chkFinishStabilizedCurve, false);
         break;
     case 2:
-        m_smoothingOptions.setSmoothingType(KisSmoothingOptions::WEIGHTED_SMOOTHING);
-        m_sliderSmoothnessDistance->setEnabled(true);
-        m_sliderTailAggressiveness->setEnabled(true);
-        m_chkSmoothPressure->setEnabled(true);
-        m_chkUseScalableDistance->setEnabled(true);
+        smoothingOptions()->setSmoothingType(KisSmoothingOptions::WEIGHTED_SMOOTHING);
+        showControl(m_sliderSmoothnessDistance, true);
+        showControl(m_sliderTailAggressiveness, true);
+        showControl(m_chkSmoothPressure, true);
+        showControl(m_chkUseScalableDistance, true);
+        showControl(m_sliderDelayDistance, false);
+        showControl(m_chkFinishStabilizedCurve, false);
         break;
     case 3:
     default:
-        m_smoothingOptions.setSmoothingType(KisSmoothingOptions::STABILIZER);
-        m_sliderSmoothnessDistance->setEnabled(true);
-        m_sliderTailAggressiveness->setEnabled(false);
-        m_chkSmoothPressure->setEnabled(false);
-        m_chkUseScalableDistance->setEnabled(false);
+        smoothingOptions()->setSmoothingType(KisSmoothingOptions::STABILIZER);
+        showControl(m_sliderSmoothnessDistance, true);
+        showControl(m_sliderTailAggressiveness, false);
+        showControl(m_chkSmoothPressure, false);
+        showControl(m_chkUseScalableDistance, false);
+        showControl(m_sliderDelayDistance, true);
+        showControl(m_chkFinishStabilizedCurve, true);
     }
     emit smoothingTypeChanged();
 }
 
 void KisToolBrush::slotSetSmoothnessDistance(qreal distance)
 {
-    m_smoothingOptions.setSmoothnessDistance(distance);
+    smoothingOptions()->setSmoothnessDistance(distance);
     emit smoothnessQualityChanged();
 }
 
 void KisToolBrush::slotSetTailAgressiveness(qreal argh_rhhrr)
 {
-    m_smoothingOptions.setTailAggressiveness(argh_rhhrr);
+    smoothingOptions()->setTailAggressiveness(argh_rhhrr);
     emit smoothnessFactorChanged();
 }
 
 void KisToolBrush::setSmoothPressure(bool value)
 {
-    m_smoothingOptions.setSmoothPressure(value);
+    smoothingOptions()->setSmoothPressure(value);
 }
 
 void KisToolBrush::slotSetMagnetism(int magnetism)
@@ -125,7 +133,13 @@ void KisToolBrush::slotSetMagnetism(int magnetism)
 
 bool KisToolBrush::useScalableDistance() const
 {
-    return m_smoothingOptions.useScalableDistance();
+    return smoothingOptions()->useScalableDistance();
+}
+
+void KisToolBrush::setUseScalableDistance(bool value)
+{
+    smoothingOptions()->setUseScalableDistance(value);
+    emit useScalableDistanceChanged();
 }
 
 void KisToolBrush::resetCursorStyle()
@@ -136,7 +150,7 @@ void KisToolBrush::resetCursorStyle()
     // When the stabilizer is in use, we avoid using the brush outline cursor,
     // because it would hide the real position of the cursor to the user,
     // yielding unexpected results.
-    if (m_smoothingOptions.smoothingType() == KisSmoothingOptions::STABILIZER
+    if (smoothingOptions()->smoothingType() == KisSmoothingOptions::STABILIZER
         && cursorStyle == CURSOR_STYLE_OUTLINE) {
         useCursor(KisCursor::roundCursor());
     } else {
@@ -144,10 +158,38 @@ void KisToolBrush::resetCursorStyle()
     }
 }
 
-void KisToolBrush::setUseScalableDistance(bool value)
+bool KisToolBrush::useDelayDistance() const
 {
-    m_smoothingOptions.setUseScalableDistance(value);
-    emit useScalableDistanceChanged();
+    return smoothingOptions()->useDelayDistance();
+}
+
+qreal KisToolBrush::delayDistance() const
+{
+    return smoothingOptions()->delayDistance();
+}
+
+void KisToolBrush::setUseDelayDistance(bool value)
+{
+    smoothingOptions()->setUseDelayDistance(value);
+    enableControl(m_chkFinishStabilizedCurve, !value);
+    emit useDelayDistanceChanged();
+}
+
+void KisToolBrush::setDelayDistance(qreal value)
+{
+    smoothingOptions()->setDelayDistance(value);
+    emit delayDistanceChanged();
+}
+
+void KisToolBrush::setFinishStabilizedCurve(bool value)
+{
+    smoothingOptions()->setFinishStabilizedCurve(value);
+    emit finishStabilizedCurveChanged();
+}
+
+bool KisToolBrush::finishStabilizedCurve() const
+{
+    return smoothingOptions()->finishStabilizedCurve();
 }
 
 QWidget * KisToolBrush::createOptionWidget()
@@ -175,28 +217,52 @@ QWidget * KisToolBrush::createOptionWidget()
     m_sliderSmoothnessDistance->setRange(3.0, MAXIMUM_SMOOTHNESS_DISTANCE, 1);
     m_sliderSmoothnessDistance->setEnabled(true);
     connect(m_sliderSmoothnessDistance, SIGNAL(valueChanged(qreal)), SLOT(slotSetSmoothnessDistance(qreal)));
-    m_sliderSmoothnessDistance->setValue(m_smoothingOptions.smoothnessDistance());
+    m_sliderSmoothnessDistance->setValue(smoothingOptions()->smoothnessDistance());
     addOptionWidgetOption(m_sliderSmoothnessDistance, new QLabel(i18n("Distance:")));
 
+    // Finish stabilizer curve
+    m_chkFinishStabilizedCurve = new QCheckBox("", optionsWidget);
+    connect(m_chkFinishStabilizedCurve, SIGNAL(toggled(bool)), this, SLOT(setFinishStabilizedCurve(bool)));
+    m_chkFinishStabilizedCurve->setChecked(smoothingOptions()->finishStabilizedCurve());
+
+    // Delay Distance for Stabilizer
+    m_chkDelayDistance = new QCheckBox(i18n("Delay:"), optionsWidget);
+    m_chkDelayDistance->setToolTip(i18n("Delay the brush stroke to make the line smoother"));
+    m_chkDelayDistance->setLayoutDirection(Qt::RightToLeft);
+    connect(m_chkDelayDistance, SIGNAL(toggled(bool)), this, SLOT(setUseDelayDistance(bool)));
+    m_sliderDelayDistance = new KisDoubleSliderSpinBox(optionsWidget);
+    m_sliderDelayDistance->setToolTip(i18n("Radius where the brush is blocked"));
+    m_sliderDelayDistance->setRange(0, 500);
+    m_sliderDelayDistance->setSuffix(i18n(" px"));
+    connect(m_chkDelayDistance, SIGNAL(toggled(bool)), m_sliderDelayDistance, SLOT(setEnabled(bool)));
+    connect(m_sliderDelayDistance, SIGNAL(valueChanged(qreal)), SLOT(setDelayDistance(qreal)));
+
+    addOptionWidgetOption(m_sliderDelayDistance, m_chkDelayDistance);
+    addOptionWidgetOption(m_chkFinishStabilizedCurve, new QLabel(i18n("Finish line:")));
+
+    m_chkDelayDistance->setChecked(smoothingOptions()->useDelayDistance());
+    m_sliderDelayDistance->setValue(smoothingOptions()->delayDistance());
+
+
     m_sliderTailAggressiveness = new KisDoubleSliderSpinBox(optionsWidget);
     m_sliderTailAggressiveness->setRange(0.0, 1.0, 2);
     m_sliderTailAggressiveness->setEnabled(true);
     connect(m_sliderTailAggressiveness, SIGNAL(valueChanged(qreal)), SLOT(slotSetTailAgressiveness(qreal)));
-    m_sliderTailAggressiveness->setValue(m_smoothingOptions.tailAggressiveness());
+    m_sliderTailAggressiveness->setValue(smoothingOptions()->tailAggressiveness());
     addOptionWidgetOption(m_sliderTailAggressiveness, new QLabel(i18n("Stroke Ending:")));
 
     m_chkSmoothPressure = new QCheckBox("", optionsWidget);
-    m_chkSmoothPressure->setChecked(m_smoothingOptions.smoothPressure());
+    m_chkSmoothPressure->setChecked(smoothingOptions()->smoothPressure());
     connect(m_chkSmoothPressure, SIGNAL(toggled(bool)), this, SLOT(setSmoothPressure(bool)));
     addOptionWidgetOption(m_chkSmoothPressure, new QLabel(i18n("Smooth Pressure")));
 
     m_chkUseScalableDistance = new QCheckBox("", optionsWidget);
-    m_chkUseScalableDistance->setChecked(m_smoothingOptions.useScalableDistance());
+    m_chkUseScalableDistance->setChecked(smoothingOptions()->useScalableDistance());
     connect(m_chkUseScalableDistance, SIGNAL(toggled(bool)), this, SLOT(setUseScalableDistance(bool)));
     addOptionWidgetOption(m_chkUseScalableDistance, new QLabel(i18n("Scalable Distance")));
 
-    slotSetSmoothingType((int)m_smoothingOptions.smoothingType());
-    m_cmbSmoothingType->setCurrentIndex((int)m_smoothingOptions.smoothingType());
+    slotSetSmoothingType((int)smoothingOptions()->smoothingType());
+    m_cmbSmoothingType->setCurrentIndex((int)smoothingOptions()->smoothingType());
 
     // Drawing assistant configuration
     m_chkAssistant = new QCheckBox(i18n("Assistant:"), optionsWidget);
diff --git a/krita/plugins/tools/defaulttools/kis_tool_brush.h b/krita/plugins/tools/defaulttools/kis_tool_brush.h
index 7778b00..34a1e5c 100644
--- a/krita/plugins/tools/defaulttools/kis_tool_brush.h
+++ b/krita/plugins/tools/defaulttools/kis_tool_brush.h
@@ -43,6 +43,12 @@ class KisToolBrush : public KisToolFreehand
     Q_PROPERTY(int smoothingType READ smoothingType WRITE slotSetSmoothingType NOTIFY smoothingTypeChanged)
     Q_PROPERTY(bool useScalableDistance READ useScalableDistance WRITE setUseScalableDistance NOTIFY useScalableDistanceChanged)
 
+    Q_PROPERTY(bool useDelayDistance READ useDelayDistance WRITE setUseDelayDistance NOTIFY useDelayDistanceChanged)
+    Q_PROPERTY(bool delayDistance READ delayDistance WRITE setDelayDistance NOTIFY delayDistanceChanged)
+
+    Q_PROPERTY(bool finishStabilizedCurve READ finishStabilizedCurve WRITE setFinishStabilizedCurve NOTIFY finishStabilizedCurveChanged)
+
+
 public:
     KisToolBrush(KoCanvasBase * canvas);
     virtual ~KisToolBrush();
@@ -55,6 +61,11 @@ public:
     int smoothingType() const;
     bool useScalableDistance() const;
 
+    bool useDelayDistance() const;
+    qreal delayDistance() const;
+
+    bool finishStabilizedCurve() const;
+
 protected slots:
     virtual void resetCursorStyle();
 
@@ -66,6 +77,11 @@ public slots:
     void setSmoothPressure(bool value);
     void setUseScalableDistance(bool value);
 
+    void setUseDelayDistance(bool value);
+    void setDelayDistance(qreal value);
+
+    void setFinishStabilizedCurve(bool value);
+
 Q_SIGNALS:
     void smoothnessQualityChanged();
     void smoothnessFactorChanged();
@@ -73,6 +89,10 @@ Q_SIGNALS:
     void smoothingTypeChanged();
     void useScalableDistanceChanged();
 
+    void useDelayDistanceChanged();
+    void delayDistanceChanged();
+    void finishStabilizedCurveChanged();
+
 private:
     QGridLayout *m_optionLayout;
     QComboBox *m_cmbSmoothingType;
@@ -83,6 +103,11 @@ private:
     KisDoubleSliderSpinBox *m_sliderTailAggressiveness;
     QCheckBox *m_chkSmoothPressure;
     QCheckBox *m_chkUseScalableDistance;
+
+    QCheckBox *m_chkDelayDistance;
+    KisDoubleSliderSpinBox *m_sliderDelayDistance;
+
+    QCheckBox *m_chkFinishStabilizedCurve;
 };
 
 
diff --git a/krita/ui/kis_config.cc b/krita/ui/kis_config.cc
index e9f742d..ccc2220 100644
--- a/krita/ui/kis_config.cc
+++ b/krita/ui/kis_config.cc
@@ -1145,6 +1145,36 @@ void KisConfig::setLineSmoothingScalableDistance(bool value)
     m_cfg.writeEntry("LineSmoothingScalableDistance", value);
 }
 
+qreal KisConfig::lineSmoothingDelayDistance() const
+{
+    return m_cfg.readEntry("LineSmoothingDelayDistance", 50.0);
+}
+
+void KisConfig::setLineSmoothingDelayDistance(qreal value)
+{
+    m_cfg.writeEntry("LineSmoothingDelayDistance", value);
+}
+
+bool KisConfig::lineSmoothingUseDelayDistance() const
+{
+    return m_cfg.readEntry("LineSmoothingUseDelayDistance", true);
+}
+
+void KisConfig::setLineSmoothingUseDelayDistance(bool value)
+{
+    m_cfg.writeEntry("LineSmoothingUseDelayDistance", value);
+}
+
+bool KisConfig::lineSmoothingFinishStabilizedCurve() const
+{
+    return m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true);
+}
+
+void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value)
+{
+    m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value);
+}
+
 int KisConfig::paletteDockerPaletteViewSectionSize() const
 {
     return m_cfg.readEntry("paletteDockerPaletteViewSectionSize", 12);
diff --git a/krita/ui/kis_config.h b/krita/ui/kis_config.h
index a712c71..85062c4 100644
--- a/krita/ui/kis_config.h
+++ b/krita/ui/kis_config.h
@@ -348,6 +348,15 @@ public:
     bool lineSmoothingScalableDistance() const;
     void setLineSmoothingScalableDistance(bool value);
 
+    qreal lineSmoothingDelayDistance() const;
+    void setLineSmoothingDelayDistance(qreal value);
+
+    bool lineSmoothingUseDelayDistance() const;
+    void setLineSmoothingUseDelayDistance(bool value);
+
+    bool lineSmoothingFinishStabilizedCurve() const;
+    void setLineSmoothingFinishStabilizedCurve(bool value);
+
     int paletteDockerPaletteViewSectionSize() const;
     void setPaletteDockerPaletteViewSectionSize(int value) const;
 
diff --git a/krita/ui/tool/kis_smoothing_options.cpp b/krita/ui/tool/kis_smoothing_options.cpp
index ecc8909..801a56c 100644
--- a/krita/ui/tool/kis_smoothing_options.cpp
+++ b/krita/ui/tool/kis_smoothing_options.cpp
@@ -20,11 +20,6 @@
 #include "kis_config.h"
 
 KisSmoothingOptions::KisSmoothingOptions()
-    : m_smoothingType(WEIGHTED_SMOOTHING)
-    , m_smoothnessDistance(55.0)
-    , m_tailAggressiveness(0.15)
-    , m_smoothPressure(false)
-    , m_useScalableDistance(true)
 {
     KisConfig cfg;
     m_smoothingType = (SmoothingType)cfg.lineSmoothingType();
@@ -32,6 +27,9 @@ KisSmoothingOptions::KisSmoothingOptions()
     m_tailAggressiveness = cfg.lineSmoothingTailAggressiveness();
     m_smoothPressure = cfg.lineSmoothingSmoothPressure();
     m_useScalableDistance = cfg.lineSmoothingScalableDistance();
+    m_delayDistance = cfg.lineSmoothingDelayDistance();
+    m_useDelayDistance = cfg.lineSmoothingUseDelayDistance();
+    m_finishStabilizedCurve = cfg.lineSmoothingFinishStabilizedCurve();
 }
 
 KisSmoothingOptions::SmoothingType KisSmoothingOptions::smoothingType() const
@@ -93,3 +91,39 @@ void KisSmoothingOptions::setUseScalableDistance(bool value)
     cfg.setLineSmoothingScalableDistance(value);
     m_useScalableDistance = value;
 }
+
+qreal KisSmoothingOptions::delayDistance() const
+{
+    return m_delayDistance;
+}
+
+void KisSmoothingOptions::setDelayDistance(qreal value)
+{
+    KisConfig cfg;
+    cfg.setLineSmoothingDelayDistance(value);
+    m_delayDistance = value;
+}
+
+bool KisSmoothingOptions::useDelayDistance() const
+{
+    return m_useDelayDistance;
+}
+
+void KisSmoothingOptions::setUseDelayDistance(bool value)
+{
+    KisConfig cfg;
+    cfg.setLineSmoothingUseDelayDistance(value);
+    m_useDelayDistance = value;
+}
+
+void KisSmoothingOptions::setFinishStabilizedCurve(bool value)
+{
+    KisConfig cfg;
+    cfg.setLineSmoothingFinishStabilizedCurve(value);
+    m_finishStabilizedCurve = value;
+}
+
+bool KisSmoothingOptions::finishStabilizedCurve() const
+{
+    return m_finishStabilizedCurve;
+}
diff --git a/krita/ui/tool/kis_smoothing_options.h b/krita/ui/tool/kis_smoothing_options.h
index b995774..47da5e4 100644
--- a/krita/ui/tool/kis_smoothing_options.h
+++ b/krita/ui/tool/kis_smoothing_options.h
@@ -19,8 +19,11 @@
 #define KIS_SMOOTHING_OPTIONS_H
 
 #include <qglobal.h>
+#include <QSharedPointer>
 #include <krita_export.h>
 
+
+
 class KRITAUI_EXPORT KisSmoothingOptions
 {
 public:
@@ -50,12 +53,26 @@ public:
     bool useScalableDistance() const;
     void setUseScalableDistance(bool value);
 
+    qreal delayDistance() const;
+    void setDelayDistance(qreal value);
+
+    void setUseDelayDistance(bool value);
+    bool useDelayDistance() const;
+
+    void setFinishStabilizedCurve(bool value);
+    bool finishStabilizedCurve() const;
+
 private:
     SmoothingType m_smoothingType;
     qreal m_smoothnessDistance;
     qreal m_tailAggressiveness;
     bool m_smoothPressure;
     bool m_useScalableDistance;
+    qreal m_delayDistance;
+    bool m_useDelayDistance;
+    bool m_finishStabilizedCurve;
 };
 
+typedef QSharedPointer<KisSmoothingOptions> KisSmoothingOptionsSP;
+
 #endif // KIS_SMOOTHING_OPTIONS_H
diff --git a/krita/ui/tool/kis_tool_freehand.cc b/krita/ui/tool/kis_tool_freehand.cc
index 00f0e61..efc321e 100644
--- a/krita/ui/tool/kis_tool_freehand.cc
+++ b/krita/ui/tool/kis_tool_freehand.cc
@@ -71,6 +71,9 @@ KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor,
     m_infoBuilder = new KisToolPaintingInformationBuilder(this);
     m_recordingAdapter = new KisRecordingAdapter();
     m_helper = new KisToolFreehandHelper(m_infoBuilder, transactionText, m_recordingAdapter);
+
+    connect(m_helper, SIGNAL(requestExplicitUpdateOutline()),
+            SLOT(explicitUpdateOutline()));
 }
 
 KisToolFreehand::~KisToolFreehand()
@@ -80,6 +83,11 @@ KisToolFreehand::~KisToolFreehand()
     delete m_infoBuilder;
 }
 
+KisSmoothingOptionsSP KisToolFreehand::smoothingOptions() const
+{
+    return m_helper->smoothingOptions();
+}
+
 void KisToolFreehand::resetCursorStyle()
 {
     KisConfig cfg;
@@ -155,7 +163,6 @@ void KisToolFreehand::initStroke(KoPointerEvent *event)
 {
     setCurrentNodeLocked(true);
 
-    m_helper->setSmoothness(m_smoothingOptions);
     m_helper->initPaint(event, canvas()->resourceManager(),
                         image(),
                         image().data(),
@@ -370,6 +377,11 @@ qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint)
     return perspective;
 }
 
+void KisToolFreehand::explicitUpdateOutline()
+{
+    requestUpdateOutline(m_outlineDocPoint, 0);
+}
+
 QPainterPath KisToolFreehand::getOutlinePath(const QPointF &documentPos,
                                              const KoPointerEvent *event,
                                              KisPaintOpSettings::OutlineMode outlineMode)
diff --git a/krita/ui/tool/kis_tool_freehand.h b/krita/ui/tool/kis_tool_freehand.h
index 3ae5736..56cb836 100644
--- a/krita/ui/tool/kis_tool_freehand.h
+++ b/krita/ui/tool/kis_tool_freehand.h
@@ -88,6 +88,7 @@ protected:
 
 protected slots:
 
+    void explicitUpdateOutline();
     virtual void resetCursorStyle();
     void setAssistant(bool assistant);
 
@@ -108,7 +109,7 @@ private:
 
 protected:
 
-    KisSmoothingOptions m_smoothingOptions;
+    KisSmoothingOptionsSP smoothingOptions() const;
     bool m_assistant;
     double m_magnetism;
 
diff --git a/krita/ui/tool/kis_tool_freehand_helper.cpp b/krita/ui/tool/kis_tool_freehand_helper.cpp
index d33315a..80decb3 100644
--- a/krita/ui/tool/kis_tool_freehand_helper.cpp
+++ b/krita/ui/tool/kis_tool_freehand_helper.cpp
@@ -104,7 +104,7 @@ struct KisToolFreehandHelper::Private
     KisPaintInformation previousPaintInformation;
     KisPaintInformation olderPaintInformation;
 
-    KisSmoothingOptions smoothingOptions;
+    KisSmoothingOptionsSP smoothingOptions;
 
     QTimer airbrushingTimer;
 
@@ -117,6 +117,10 @@ struct KisToolFreehandHelper::Private
     QQueue<KisPaintInformation> stabilizerDeque;
     KisPaintInformation stabilizerLastPaintInfo;
     QTimer stabilizerPollTimer;
+
+    static KisPaintInformation
+    getStabilizedPaintInfo(const QQueue<KisPaintInformation> &queue,
+                           const KisPaintInformation &lastPaintInfo);
 };
 
 
@@ -127,14 +131,12 @@ KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *info
 {
     m_d->infoBuilder = infoBuilder;
     m_d->recordingAdapter = recordingAdapter;
-
     m_d->transactionText = transactionText;
+    m_d->smoothingOptions = KisSmoothingOptionsSP(new KisSmoothingOptions());
 
     m_d->strokeTimeoutTimer.setSingleShot(true);
     connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke()));
-
     connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing()));
-
     connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint()));
 }
 
@@ -143,11 +145,16 @@ KisToolFreehandHelper::~KisToolFreehandHelper()
     delete m_d;
 }
 
-void KisToolFreehandHelper::setSmoothness(const KisSmoothingOptions &smoothingOptions)
+void KisToolFreehandHelper::setSmoothness(KisSmoothingOptionsSP smoothingOptions)
 {
     m_d->smoothingOptions = smoothingOptions;
 }
 
+KisSmoothingOptionsSP KisToolFreehandHelper::smoothingOptions() const
+{
+    return m_d->smoothingOptions;
+}
+
 QPainterPath KisToolFreehandHelper::paintOpOutline(const QPointF &savedCursorPos,
                                                    const KoPointerEvent *event,
                                                    const KisPaintOpSettings *globalSettings,
@@ -166,7 +173,21 @@ QPainterPath KisToolFreehandHelper::paintOpOutline(const QPointF &savedCursorPos
     KisPaintInformation::DistanceInformationRegistrar registrar =
         info.registerDistanceInformation(&distanceInfo);
 
-    return settings->brushOutline(info, mode);
+    QPainterPath outline = settings->brushOutline(info, mode);
+
+
+
+    if (m_d->resources &&
+        m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER &&
+        m_d->smoothingOptions->useDelayDistance()) {
+
+        const qreal R = m_d->smoothingOptions->delayDistance() /
+            m_d->resources->effectiveZoom();
+
+        outline.addEllipse(info.pos(), R, R);
+    }
+
+    return outline;
 }
 
 void KisToolFreehandHelper::initPaint(KoPointerEvent *event,
@@ -223,7 +244,7 @@ void KisToolFreehandHelper::initPaint(KoPointerEvent *event,
         m_d->airbrushingTimer.start();
     }
 
-    if (m_d->smoothingOptions.smoothingType() == KisSmoothingOptions::STABILIZER) {
+    if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
         stabilizerStart(m_d->previousPaintInformation);
     }
 }
@@ -344,8 +365,8 @@ void KisToolFreehandHelper::paint(KoPointerEvent *event)
      * 4) The formila is a little bit different: 'Distance' parameter
      *    stands for $3 \Sigma$
      */
-    if (m_d->smoothingOptions.smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING
-        && m_d->smoothingOptions.smoothnessDistance() > 0.0) {
+    if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING
+        && m_d->smoothingOptions->smoothnessDistance() > 0.0) {
 
         { // initialize current distance
             QPointF prevPos;
@@ -368,9 +389,9 @@ void KisToolFreehandHelper::paint(KoPointerEvent *event)
 
         if (m_d->history.size() > 3) {
             const qreal effectiveSmoothnessDistance =
-                !m_d->smoothingOptions.useScalableDistance() ?
-                m_d->smoothingOptions.smoothnessDistance() :
-                m_d->smoothingOptions.smoothnessDistance() /
+                !m_d->smoothingOptions->useScalableDistance() ?
+                m_d->smoothingOptions->smoothnessDistance() :
+                m_d->smoothingOptions->smoothnessDistance() /
                 m_d->resources->effectiveZoom();
 
             const qreal sigma = effectiveSmoothnessDistance / 3.0; // '3.0' for (3 * sigma) range
@@ -395,7 +416,7 @@ void KisToolFreehandHelper::paint(KoPointerEvent *event)
                 if (i < m_d->history.size() - 1) {
                     pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure();
 
-                    const qreal tailAgressiveness = 40.0 * m_d->smoothingOptions.tailAggressiveness();
+                    const qreal tailAgressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness();
 
                     if (pressureGrad > 0.0 ) {
                         pressureGrad *= tailAgressiveness * (1.0 - nextInfo.pressure());
@@ -418,7 +439,7 @@ void KisToolFreehandHelper::paint(KoPointerEvent *event)
                 x += rate * nextInfo.pos().x();
                 y += rate * nextInfo.pos().y();
 
-                if (m_d->smoothingOptions.smoothPressure()) {
+                if (m_d->smoothingOptions->smoothPressure()) {
                     pressure += rate * nextInfo.pressure();
                 }
             }
@@ -427,14 +448,14 @@ void KisToolFreehandHelper::paint(KoPointerEvent *event)
                 x /= scaleSum;
                 y /= scaleSum;
 
-                if (m_d->smoothingOptions.smoothPressure()) {
+                if (m_d->smoothingOptions->smoothPressure()) {
                     pressure /= scaleSum;
                 }
             }
 
             if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
                 info.setPos(QPointF(x, y));
-                if (m_d->smoothingOptions.smoothPressure()) {
+                if (m_d->smoothingOptions->smoothPressure()) {
                     info.setPressure(pressure);
                 }
                 m_d->history.last() = info;
@@ -442,8 +463,8 @@ void KisToolFreehandHelper::paint(KoPointerEvent *event)
         }
     }
 
-    if (m_d->smoothingOptions.smoothingType() == KisSmoothingOptions::SIMPLE_SMOOTHING
-        || m_d->smoothingOptions.smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING)
+    if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::SIMPLE_SMOOTHING
+        || m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING)
     {
         // Now paint between the coordinates, using the bezier curve interpolation
         if (!m_d->haveTangent) {
@@ -463,11 +484,11 @@ void KisToolFreehandHelper::paint(KoPointerEvent *event)
         m_d->olderPaintInformation = m_d->previousPaintInformation;
         m_d->strokeTimeoutTimer.start(100);
     }
-    else if (m_d->smoothingOptions.smoothingType() == KisSmoothingOptions::NO_SMOOTHING){
+    else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING){
         paintLine(m_d->painterInfos, m_d->previousPaintInformation, info);
     }
 
-    if (m_d->smoothingOptions.smoothingType() == KisSmoothingOptions::STABILIZER) {
+    if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
         m_d->stabilizerLastPaintInfo = info;
     } else {
         m_d->previousPaintInformation = info;
@@ -482,7 +503,7 @@ void KisToolFreehandHelper::endPaint()
 {
     if (!m_d->hasPaintAtLeastOnce) {
         paintAt(m_d->painterInfos, m_d->previousPaintInformation);
-    } else if (m_d->smoothingOptions.smoothingType() != KisSmoothingOptions::NO_SMOOTHING) {
+    } else if (m_d->smoothingOptions->smoothingType() != KisSmoothingOptions::NO_SMOOTHING) {
         finishStroke();
     }
     m_d->strokeTimeoutTimer.stop();
@@ -491,7 +512,7 @@ void KisToolFreehandHelper::endPaint()
         m_d->airbrushingTimer.stop();
     }
 
-    if (m_d->smoothingOptions.smoothingType() == KisSmoothingOptions::STABILIZER) {
+    if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
         stabilizerEnd();
     }
 
@@ -513,7 +534,7 @@ void KisToolFreehandHelper::endPaint()
 void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo)
 {
     // FIXME: Ugly hack, this is no a "distance" in any way
-    int sampleSize = m_d->smoothingOptions.smoothnessDistance();
+    int sampleSize = m_d->smoothingOptions->smoothnessDistance();
     assert(sampleSize > 0);
 
     // Fill the deque with the current value repeated until filling the sample
@@ -528,58 +549,77 @@ void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo)
     m_d->stabilizerPollTimer.start();
 }
 
-void KisToolFreehandHelper::stabilizerPoll()
+KisPaintInformation
+KisToolFreehandHelper::Private::getStabilizedPaintInfo(const QQueue<KisPaintInformation> &queue,
+                                                       const KisPaintInformation &lastPaintInfo)
 {
-    // Remove the oldest entry
-    m_d->stabilizerDeque.dequeue();
+    KisPaintInformation result(lastPaintInfo);
 
-    // Add a new entry with the last paint info (position and pressure)
-    m_d->stabilizerDeque.enqueue(m_d->stabilizerLastPaintInfo);
-}
+    if (queue.size() > 1) {
+        QQueue<KisPaintInformation>::const_iterator it = queue.constBegin();
+        QQueue<KisPaintInformation>::const_iterator end = queue.constEnd();
 
-void KisToolFreehandHelper::stabilizerPaint()
-{
-    // Get the average position and pressure in the deque
-    qreal x = 0.0,
-          y = 0.0,
-          pressure = 0.0,
-          xTilt = 0.0,
-          yTilt = 0.0;
-
-    foreach (KisPaintInformation info, m_d->stabilizerDeque) {
-        x += info.pos().x();
-        y += info.pos().y();
-        pressure += info.pressure();
-        xTilt += info.xTilt();
-        yTilt += info.yTilt();
-    }
+        /**
+         * The first point is going to be overridden by lastPaintInfo, skip it.
+         */
+        it++;
+        int i = 2;
 
-    x /= m_d->stabilizerDeque.size();
-    y /= m_d->stabilizerDeque.size();
-    pressure /= m_d->stabilizerDeque.size();
-    xTilt /= m_d->stabilizerDeque.size();
-    yTilt /= m_d->stabilizerDeque.size();
+        while (it != end) {
+            qreal k = qreal(i - 1) / i; // coeff for uniform averaging
+            result = KisPaintInformation::mix(k, *it, result);
 
-    // Draw with these params
-    KisPaintInformation newInfo = m_d->stabilizerLastPaintInfo;
-    newInfo.setPos(QPointF(x, y));
-    newInfo.setPressure(pressure);
-    paintLine(m_d->painterInfos, m_d->previousPaintInformation, newInfo);
+            it++;
+            i++;
+        }
+    }
 
-    m_d->previousPaintInformation = newInfo;
+    return result;
 }
 
 void KisToolFreehandHelper::stabilizerPollAndPaint()
 {
-    // Update the deque and draw a line to the new average
-    stabilizerPoll();
-    stabilizerPaint();
+    KisPaintInformation newInfo =
+        m_d->getStabilizedPaintInfo(m_d->stabilizerDeque, m_d->stabilizerLastPaintInfo);
+
+    bool canPaint = true;
+
+    if (m_d->smoothingOptions->useDelayDistance()) {
+        const qreal R = m_d->smoothingOptions->delayDistance() /
+            m_d->resources->effectiveZoom();
+
+        QPointF diff = m_d->stabilizerLastPaintInfo.pos() - m_d->previousPaintInformation.pos();
+        qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y()));
+
+        canPaint = dx > R;
+    }
+
+    if (canPaint) {
+        paintLine(m_d->painterInfos, m_d->previousPaintInformation, newInfo);
+        m_d->previousPaintInformation = newInfo;
+
+        // Push the new entry through the queue
+        m_d->stabilizerDeque.dequeue();
+        m_d->stabilizerDeque.enqueue(m_d->stabilizerLastPaintInfo);
+
+        emit requestExplicitUpdateOutline();
+
+    } else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) {
+
+        QQueue<KisPaintInformation>::iterator it = m_d->stabilizerDeque.begin();
+        QQueue<KisPaintInformation>::iterator end = m_d->stabilizerDeque.end();
+
+        while (it != end) {
+            *it = m_d->previousPaintInformation;
+            ++it;
+        }
+    }
 }
 
 void KisToolFreehandHelper::stabilizerEnd()
 {
     // FIXME: Ugly hack, this is no a "distance" in any way
-    int sampleSize = m_d->smoothingOptions.smoothnessDistance();
+    int sampleSize = m_d->smoothingOptions->smoothnessDistance();
     assert(sampleSize > 0);
 
     // Stop the timer
@@ -590,8 +630,9 @@ void KisToolFreehandHelper::stabilizerEnd()
         // In each iteration we add the latest paint info and delete the oldest
         // After `sampleSize` iterations the deque will be filled with the latest
         // value and we will have reached the end point.
-        stabilizerPoll();
-        stabilizerPaint();
+        if (m_d->smoothingOptions->finishStabilizedCurve()) {
+            stabilizerPollAndPaint();
+        }
     }
 }
 
diff --git a/krita/ui/tool/kis_tool_freehand_helper.h b/krita/ui/tool/kis_tool_freehand_helper.h
index 1317dc0..a350514 100644
--- a/krita/ui/tool/kis_tool_freehand_helper.h
+++ b/krita/ui/tool/kis_tool_freehand_helper.h
@@ -27,6 +27,7 @@
 #include "strokes/freehand_stroke.h"
 #include "kis_default_bounds.h"
 #include "kis_paintop_settings.h"
+#include "kis_smoothing_options.h"
 
 class KoPointerEvent;
 class KoCanvasResourceManager;
@@ -36,7 +37,7 @@ class KisStrokesFacade;
 class KisPostExecutionUndoAdapter;
 class KisPaintOp;
 class KisPainter;
-struct KisSmoothingOptions;
+
 
 class KRITAUI_EXPORT KisToolFreehandHelper : public QObject
 {
@@ -53,7 +54,8 @@ public:
                           KisRecordingAdapter *recordingAdapter = 0);
     ~KisToolFreehandHelper();
 
-    void setSmoothness(const KisSmoothingOptions &smoothingOptions);
+    void setSmoothness(KisSmoothingOptionsSP smoothingOptions);
+    KisSmoothingOptionsSP smoothingOptions() const;
 
     void initPaint(KoPointerEvent *event,
                    KoCanvasResourceManager *resourceManager,
@@ -71,6 +73,15 @@ public:
                                 const KisPaintOpSettings *globalSettings,
                                 KisPaintOpSettings::OutlineMode mode) const;
 
+signals:
+    /**
+     * The signal is emitted when the outline should be updated
+     * explicitly by the tool. Used by Stabilizer option, because it
+     * paints on internal timer events instead of the on every paint()
+     * event
+     */
+    void requestExplicitUpdateOutline();
+
 protected:
 
     virtual void createPainters(QVector<PainterInfo*> &painterInfos,
@@ -109,8 +120,6 @@ private:
 
     void stabilizerStart(KisPaintInformation firstPaintInfo);
     void stabilizerEnd();
-    void stabilizerPoll();
-    void stabilizerPaint();
 
 private slots:
 
diff --git a/krita/ui/tool/kis_tool_paint.cc b/krita/ui/tool/kis_tool_paint.cc
index 7073f3c..27a1bd3 100644
--- a/krita/ui/tool/kis_tool_paint.cc
+++ b/krita/ui/tool/kis_tool_paint.cc
@@ -371,6 +371,48 @@ QWidget * KisToolPaint::createOptionWidget()
     return optionWidget;
 }
 
+QWidget* findLabelWidget(QGridLayout *layout, QWidget *control)
+{
+    QWidget *result = 0;
+
+    int index = layout->indexOf(control);
+
+    int row, col, rowSpan, colSpan;
+    layout->getItemPosition(index, &row, &col, &rowSpan, &colSpan);
+
+    if (col > 0) {
+        QLayoutItem *item = layout->itemAtPosition(row, col - 1);
+
+        if (item) {
+            result = item->widget();
+        }
+    } else {
+        QLayoutItem *item = layout->itemAtPosition(row, col + 1);
+        if (item) {
+            result = item->widget();
+        }
+    }
+
+    return result;
+}
+
+void KisToolPaint::showControl(QWidget *control, bool value)
+{
+    control->setVisible(value);
+    QWidget *label = findLabelWidget(m_optionsWidgetLayout, control);
+    if (label) {
+        label->setVisible(value);
+    }
+}
+
+void KisToolPaint::enableControl(QWidget *control, bool value)
+{
+    control->setEnabled(value);
+    QWidget *label = findLabelWidget(m_optionsWidgetLayout, control);
+    if (label) {
+        label->setEnabled(value);
+    }
+}
 
 void KisToolPaint::addOptionWidgetLayout(QLayout *layout)
 {
diff --git a/krita/ui/tool/kis_tool_paint.h b/krita/ui/tool/kis_tool_paint.h
index abdb408..c3b145e 100644
--- a/krita/ui/tool/kis_tool_paint.h
+++ b/krita/ui/tool/kis_tool_paint.h
@@ -119,6 +119,9 @@ protected:
     /// Add a widget and a label to the current option widget layout.
     virtual void addOptionWidgetOption(QWidget *control, QWidget *label = 0);
 
+    void showControl(QWidget *control, bool value);
+    void enableControl(QWidget *control, bool value);
+
     virtual QWidget * createOptionWidget();
 
     /**


More information about the kimageshop mailing list