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

List:       kde-kimageshop
Subject:    [krita] libs/ui: Fix "bended lines" stabilizer problem on Windows
From:       Dmitry Kazakov <dimula73 () gmail ! com>
Date:       2016-05-30 17:10:11
Message-ID: E1b7Qhf-0001Vy-Bg () scm ! kde ! org
[Download RAW message or body]

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@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@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@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@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@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()
_______________________________________________
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