[krita] libs/ui: Fix "bended lines" stabilizer problem on Windows

Dmitry Kazakov dimula73 at gmail.com
Mon May 30 17:10:11 UTC 2016


Git commit 23cbbf8b73cdd6c24a82ab70c8269df2b69a4ef7 by Dmitry Kazakov.
Committed on 30/05/2016 at 17:09.
Pushed by dkazakov into branch 'master'.

Fix "bended lines" stabilizer problem on Windows

The problem is that on Windows the tablet events are coming in bunches,
not uniformly. Therefore any timing-based smoothing system will not work
out of box.

This patch adds a special class KisStabilizedEventsSampler, that makes
the events uniform. It collects a set of events on a 50ms timeframe and
then distributes it uniformly.

The timeframe size should correlate with the maximum size of the delays
created by the events system. On Windows it is 50ms, on Linux 15-20ms.

The timeframe can be configured with "stabilizerSampleSize" config option.

BUG:362445
Ref T2414
CC:kimageshop at kde.org

M  +1    -0    libs/ui/CMakeLists.txt
M  +18   -1    libs/ui/kis_config.cc
M  +4    -0    libs/ui/kis_config.h
M  +6    -0    libs/ui/tests/CMakeLists.txt
A  +64   -0    libs/ui/tests/kis_stabilized_events_sampler_test.cpp     [License: GPL (v2+)]
A  +31   -0    libs/ui/tests/kis_stabilized_events_sampler_test.h     [License: GPL (v2+)]
A  +103  -0    libs/ui/tool/kis_stabilized_events_sampler.cpp     [License: GPL (v2+)]
A  +90   -0    libs/ui/tool/kis_stabilized_events_sampler.h     [License: GPL (v2+)]
M  +42   -28   libs/ui/tool/kis_tool_freehand_helper.cpp

http://commits.kde.org/krita/23cbbf8b73cdd6c24a82ab70c8269df2b69a4ef7

diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index a88c7b7..b91d0c7 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -182,6 +182,7 @@ set(kritaui_LIB_SRCS
     tool/kis_tool_freehand.cc
     tool/kis_speed_smoother.cpp
     tool/kis_painting_information_builder.cpp
+    tool/kis_stabilized_events_sampler.cpp
     tool/kis_tool_freehand_helper.cpp
     tool/kis_tool_multihand_helper.cpp
     tool/kis_figure_painting_tool_helper.cpp
diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc
index 119f6fa..064e25e 100644
--- a/libs/ui/kis_config.cc
+++ b/libs/ui/kis_config.cc
@@ -1104,7 +1104,7 @@ void KisConfig::setHideStatusbarFullscreen(const bool value) const
 
 bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const
 {
-#ifdef Q_OS_WINDOWS
+#ifdef Q_OS_WIN
     return false;
 #else
     return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true));
@@ -1655,3 +1655,20 @@ void KisConfig::setConvertToImageColorspaceOnImport(bool value)
 {
     m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value);
 }
+
+int KisConfig::stabilizerSampleSize(bool defaultValue) const
+{
+#ifdef Q_OS_WIN
+    const int defaultSampleSize = 50;
+#else
+    const int defaultSampleSize = 15;
+#endif
+
+    return defaultValue ?
+        defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize);
+}
+
+void KisConfig::setStabilizerSampleSize(int value)
+{
+    m_cfg.writeEntry("stabilizerSampleSize", value);
+}
diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h
index afb513f..3776859 100644
--- a/libs/ui/kis_config.h
+++ b/libs/ui/kis_config.h
@@ -470,6 +470,10 @@ public:
     bool convertToImageColorspaceOnImport(bool defaultValue = false) const;
     void setConvertToImageColorspaceOnImport(bool value);
 
+    int stabilizerSampleSize(bool defaultValue = false) const;
+    void setStabilizerSampleSize(int value);
+
+
     template<class T>
     void writeEntry(const QString& name, const T& value) {
         m_cfg.writeEntry(name, value);
diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt
index 67951c1..7219220 100644
--- a/libs/ui/tests/CMakeLists.txt
+++ b/libs/ui/tests/CMakeLists.txt
@@ -253,3 +253,9 @@ target_link_libraries(KisAnimationFrameCacheTest kritaui kritaimage ${QT_QTTEST_
 set(ResourceBundleTest_SRCS ResourceBundleTest.cpp)
 kde4_add_broken_unit_test(ResourceBundleTest TESTNAME krita-resourcemanager-ResourceBundleTest ${ResourceBundleTest_SRCS})
 target_link_libraries(ResourceBundleTest kritaui kritalibbrush kritalibpaintop Qt5::Test )
+
+########### next target ###############
+
+set(kis_stabilized_events_sampler_test_SRCS kis_stabilized_events_sampler_test.cpp)
+kde4_add_unit_test(KisStabilizedEventsSamplerTest TESTNAME krita-ui-StabilizedEventsSamplerTest ${kis_stabilized_events_sampler_test_SRCS})
+target_link_libraries(KisStabilizedEventsSamplerTest kritaui Qt5::Test)
diff --git a/libs/ui/tests/kis_stabilized_events_sampler_test.cpp b/libs/ui/tests/kis_stabilized_events_sampler_test.cpp
new file mode 100644
index 0000000..cbd3deb
--- /dev/null
+++ b/libs/ui/tests/kis_stabilized_events_sampler_test.cpp
@@ -0,0 +1,64 @@
+/*
+ *  Copyright (c) 2016 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "kis_stabilized_events_sampler_test.h"
+
+#include "kis_stabilized_events_sampler.h"
+#include "kis_paint_information.h"
+
+void KisStabilizedEventsSamplerTest::test()
+{
+    KisStabilizedEventsSampler sampler(20);
+
+    KisPaintInformation pi1(QPoint(10,10));
+    KisPaintInformation pi2(QPoint(20,20));
+
+    sampler.addEvent(pi1);
+
+    QTest::qSleep(50);
+
+    sampler.addEvent(pi2);
+
+    QTest::qSleep(70);
+
+    KisStabilizedEventsSampler::iterator it;
+    KisStabilizedEventsSampler::iterator end;
+    std::tie(it, end) = sampler.range();
+
+
+    int numTotal = 0;
+    int num1 = 0;
+    int num2 = 0;
+
+    for (; it != end; ++it) {
+        numTotal++;
+        if (it->pos().x() == 10) {
+            num1++;
+        } else if (it->pos().x() == 20) {
+            num2++;
+        }
+
+        qDebug() << ppVar(it->pos());
+    }
+
+    QVERIFY(numTotal >= 6);
+    QVERIFY(num1 >= 3);
+    QVERIFY(num2 >= 3);
+}
+
+QTEST_MAIN(KisStabilizedEventsSamplerTest)
diff --git a/libs/ui/tests/kis_stabilized_events_sampler_test.h b/libs/ui/tests/kis_stabilized_events_sampler_test.h
new file mode 100644
index 0000000..885ea2b
--- /dev/null
+++ b/libs/ui/tests/kis_stabilized_events_sampler_test.h
@@ -0,0 +1,31 @@
+/*
+ *  Copyright (c) 2016 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_STABILIZED_EVENTS_SAMPLER_TEST_H
+#define __KIS_STABILIZED_EVENTS_SAMPLER_TEST_H
+
+#include <QtTest/QtTest>
+
+class KisStabilizedEventsSamplerTest : public QObject
+{
+    Q_OBJECT
+private Q_SLOTS:
+    void test();
+};
+
+#endif /* __KIS_STABILIZED_EVENTS_SAMPLER_TEST_H */
diff --git a/libs/ui/tool/kis_stabilized_events_sampler.cpp b/libs/ui/tool/kis_stabilized_events_sampler.cpp
new file mode 100644
index 0000000..2d47f4b
--- /dev/null
+++ b/libs/ui/tool/kis_stabilized_events_sampler.cpp
@@ -0,0 +1,103 @@
+/*
+ *  Copyright (c) 2016 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "kis_stabilized_events_sampler.h"
+
+#include <QList>
+#include <QElapsedTimer>
+#include <QtMath>
+
+#include "kis_paint_information.h"
+
+
+struct KisStabilizedEventsSampler::Private
+{
+    Private(int _sampleTime) : sampleTime(_sampleTime) {}
+
+    std::function<void(const KisPaintInformation &)> paintLine;
+    QElapsedTimer lastPaintTime;
+    QList<KisPaintInformation> realEvents;
+    int sampleTime;
+
+    KisPaintInformation lastPaintInformation;
+};
+
+KisStabilizedEventsSampler::KisStabilizedEventsSampler(int sampleTime)
+    : m_d(new Private(sampleTime))
+{
+}
+
+KisStabilizedEventsSampler::~KisStabilizedEventsSampler()
+{
+}
+
+void KisStabilizedEventsSampler::setLineFunction(std::function<void(const KisPaintInformation &)> func)
+{
+    m_d->paintLine = func;
+}
+
+void KisStabilizedEventsSampler::clear()
+{
+    if (!m_d->realEvents.isEmpty()) {
+        m_d->lastPaintInformation = m_d->realEvents.last();
+    }
+
+    m_d->realEvents.clear();
+    m_d->lastPaintTime.start();
+}
+
+void KisStabilizedEventsSampler::addEvent(const KisPaintInformation &pi)
+{
+    if (!m_d->lastPaintTime.isValid()) {
+        m_d->lastPaintTime.start();
+    }
+
+    m_d->realEvents.append(pi);
+}
+
+void KisStabilizedEventsSampler::processAllEvents()
+{
+    const int elapsed = m_d->lastPaintTime.restart();
+
+    const qreal alpha = qreal(m_d->realEvents.size()) / elapsed;
+
+    for (int i = 0; i < elapsed; i += m_d->sampleTime) {
+        const int k = qFloor(alpha * i);
+
+        m_d->paintLine(m_d->realEvents[k]);
+    }
+}
+
+const KisPaintInformation& KisStabilizedEventsSampler::iterator::dereference() const
+{
+    const int k = qFloor(m_alpha * m_index);
+    return k < m_sampler->m_d->realEvents.size() ?
+        m_sampler->m_d->realEvents[k] : m_sampler->m_d->lastPaintInformation;
+}
+
+std::pair<KisStabilizedEventsSampler::iterator, KisStabilizedEventsSampler::iterator>
+KisStabilizedEventsSampler::range() const
+{
+    const int elapsed = m_d->lastPaintTime.restart() / m_d->sampleTime;
+    const qreal alpha = qreal(m_d->realEvents.size()) / elapsed;
+
+    return std::make_pair(iterator(this, 0, alpha),
+                          iterator(this, elapsed, alpha));
+}
+
+
diff --git a/libs/ui/tool/kis_stabilized_events_sampler.h b/libs/ui/tool/kis_stabilized_events_sampler.h
new file mode 100644
index 0000000..feb6fc6
--- /dev/null
+++ b/libs/ui/tool/kis_stabilized_events_sampler.h
@@ -0,0 +1,90 @@
+/*
+ *  Copyright (c) 2016 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_STABILIZED_EVENTS_SAMPLER_H
+#define __KIS_STABILIZED_EVENTS_SAMPLER_H
+
+#include <QScopedPointer>
+
+#include <functional>
+#include <boost/iterator/iterator_facade.hpp>
+
+#include "kritaui_export.h"
+
+class KisPaintInformation;
+#include <kis_paint_information.h>
+
+
+class KRITAUI_EXPORT KisStabilizedEventsSampler
+{
+public:
+    KisStabilizedEventsSampler(int sampleTime = 1);
+    ~KisStabilizedEventsSampler();
+
+    void setLineFunction(std::function<void(const KisPaintInformation &)> func);
+
+    void clear();
+    void addEvent(const KisPaintInformation &pi);
+    void processAllEvents();
+
+public:
+    class iterator :
+        public boost::iterator_facade <iterator,
+                                       KisPaintInformation const,
+                                       boost::forward_traversal_tag >
+    {
+    public:
+        iterator()
+            : m_sampler(0),
+              m_index(0),
+              m_alpha(0) {}
+
+        iterator(const KisStabilizedEventsSampler* sampler, int index, qreal alpha)
+            : m_sampler(sampler),
+              m_index(index),
+              m_alpha(alpha) {}
+
+    private:
+        friend class boost::iterator_core_access;
+
+        void increment() {
+            m_index++;
+        }
+
+        bool equal(iterator const& other) const {
+            return m_index == other.m_index &&
+                m_sampler == other.m_sampler;
+        }
+
+        const KisPaintInformation& dereference() const;
+
+    private:
+        const KisStabilizedEventsSampler* m_sampler;
+        int m_index;
+        qreal m_alpha;
+    };
+
+    std::pair<iterator, iterator> range() const;
+
+private:
+    struct Private;
+    const QScopedPointer<Private> m_d;
+};
+
+
+#endif /* __KIS_STABILIZED_EVENTS_SAMPLER_H */
diff --git a/libs/ui/tool/kis_tool_freehand_helper.cpp b/libs/ui/tool/kis_tool_freehand_helper.cpp
index 20d30a7..dde3424 100644
--- a/libs/ui/tool/kis_tool_freehand_helper.cpp
+++ b/libs/ui/tool/kis_tool_freehand_helper.cpp
@@ -35,6 +35,8 @@
 #include <brushengine/kis_paintop_utils.h>
 
 #include "kis_update_time_monitor.h"
+#include "kis_stabilized_events_sampler.h"
+#include "kis_config.h"
 
 
 #include <math.h>
@@ -75,8 +77,8 @@ struct KisToolFreehandHelper::Private
 
     // Stabilizer data
     QQueue<KisPaintInformation> stabilizerDeque;
-    KisPaintInformation stabilizerLastPaintInfo;
     QTimer stabilizerPollTimer;
+    KisStabilizedEventsSampler stabilizedSampler;
 
     int canvasRotation;
     bool canvasMirroredH;
@@ -516,7 +518,7 @@ void KisToolFreehandHelper::paint(KoPointerEvent *event)
     }
 
     if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
-        m_d->stabilizerLastPaintInfo = info;
+        m_d->stabilizedSampler.addEvent(info);
     } else {
         m_d->previousPaintInformation = info;
     }
@@ -601,11 +603,13 @@ void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo)
     for (int i = sampleSize; i > 0; i--) {
         m_d->stabilizerDeque.enqueue(firstPaintInfo);
     }
-    m_d->stabilizerLastPaintInfo = firstPaintInfo;
 
-    // Poll and draw each millisecond
-    m_d->stabilizerPollTimer.setInterval(1);
+    // Poll and draw regularly
+    KisConfig cfg;
+    m_d->stabilizerPollTimer.setInterval(cfg.stabilizerSampleSize());
     m_d->stabilizerPollTimer.start();
+
+    m_d->stabilizedSampler.clear();
 }
 
 KisPaintInformation
@@ -646,41 +650,51 @@ KisToolFreehandHelper::Private::getStabilizedPaintInfo(const QQueue<KisPaintInfo
 
 void KisToolFreehandHelper::stabilizerPollAndPaint()
 {
-    KisPaintInformation newInfo =
-        m_d->getStabilizedPaintInfo(m_d->stabilizerDeque, m_d->stabilizerLastPaintInfo);
+    KisStabilizedEventsSampler::iterator it;
+    KisStabilizedEventsSampler::iterator end;
+    std::tie(it, end) = m_d->stabilizedSampler.range();
 
-    bool canPaint = true;
+    for (; it != end; ++it) {
+        KisPaintInformation sampledInfo = *it;
 
-    if (m_d->smoothingOptions->useDelayDistance()) {
-        const qreal R = m_d->smoothingOptions->delayDistance() /
-            m_d->resources->effectiveZoom();
+        bool canPaint = true;
 
-        QPointF diff = m_d->stabilizerLastPaintInfo.pos() - m_d->previousPaintInformation.pos();
-        qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y()));
+        if (m_d->smoothingOptions->useDelayDistance()) {
+            const qreal R = m_d->smoothingOptions->delayDistance() /
+                    m_d->resources->effectiveZoom();
 
-        canPaint = dx > R;
-    }
+            QPointF diff = sampledInfo.pos() - m_d->previousPaintInformation.pos();
+            qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y()));
+
+            canPaint = dx > R;
+        }
+
+        if (canPaint) {
+            KisPaintInformation newInfo =
+                    m_d->getStabilizedPaintInfo(m_d->stabilizerDeque, sampledInfo);
 
-    if (canPaint) {
-        paintLine(m_d->previousPaintInformation, newInfo);
-        m_d->previousPaintInformation = newInfo;
+            paintLine(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);
+            // Push the new entry through the queue
+            m_d->stabilizerDeque.dequeue();
+            m_d->stabilizerDeque.enqueue(sampledInfo);
 
-        emit requestExplicitUpdateOutline();
 
-    } else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) {
+            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();
+            QQueue<KisPaintInformation>::iterator it = m_d->stabilizerDeque.begin();
+            QQueue<KisPaintInformation>::iterator end = m_d->stabilizerDeque.end();
 
-        while (it != end) {
-            *it = m_d->previousPaintInformation;
-            ++it;
+            while (it != end) {
+                *it = m_d->previousPaintInformation;
+                ++it;
+            }
         }
     }
+
+    m_d->stabilizedSampler.clear();
 }
 
 void KisToolFreehandHelper::stabilizerEnd()


More information about the kimageshop mailing list