[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-kimageshop
Subject:    [calligra] krita: [FEATURE] Implemented "Delayed Stroke" feature for brush smoothing
From:       Dmitry Kazakov <dimula73 () gmail ! com>
Date:       2014-06-27 13:56:59
Message-ID: E1X0WeB-0003A9-Ew () scm ! kde ! org
[Download RAW message or body]

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@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();
 
     /**
_______________________________________________
Krita mailing list
kimageshop@kde.org
https://mail.kde.org/mailman/listinfo/kimageshop


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic