[calligra/krita-animation-pentikainen] krita: Fixed LoD painting for Imagepipe and Text Brushes

Dmitry Kazakov dimula73 at gmail.com
Fri Jul 17 16:15:16 UTC 2015


Git commit c4bd3a2294a79ad2f4640836f334ab3c2bfca272 by Dmitry Kazakov.
Committed on 17/07/2015 at 16:08.
Pushed by dkazakov into branch 'krita-animation-pentikainen'.

Fixed LoD painting for Imagepipe and Text Brushes

Now every KisPainInformation has it's own source of random numbers,
which must be used by all the paint ops and brushes. The sharing and
forking of the source is performed automatically by the c-tor of the
KisPaintInformation.

NOTE: all this 'painting source' adventure is based on an assumption that
      painting on a scaled down plan scales linearly, and therefore the
      number of calls to the random source will be strictly the same.
      This is true for most of the brushes, but some settings can break
      this assumption, e.g. if we have Auto Spacing (which is non-linear)
      or if we add some weird sensor to Spacing option.

CC:kimageshop at kde.org

M  +1    -0    krita/image/CMakeLists.txt
M  +22   -0    krita/image/brushengine/kis_paint_information.cc
M  +14   -0    krita/image/brushengine/kis_paint_information.h
A  +89   -0    krita/image/brushengine/kis_random_source.cpp     [License: GPL (v2+)]
A  +52   -0    krita/image/brushengine/kis_random_source.h     [License: GPL (v2+)]
M  +22   -0    krita/image/tests/kis_paint_information_test.cpp
M  +2    -0    krita/image/tests/kis_paint_information_test.h
M  +6    -1    krita/libbrush/kis_brush.cpp
M  +10   -1    krita/libbrush/kis_brush.h
M  +18   -11   krita/libbrush/kis_brushes_pipe.h
M  +30   -7    krita/libbrush/kis_imagepipe_brush.cpp
M  +2    -1    krita/libbrush/kis_imagepipe_brush.h
M  +22   -4    krita/libbrush/kis_text_brush.cpp
M  +2    -1    krita/libbrush/kis_text_brush.h
M  +2    -0    krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp
M  +1    -1    krita/plugins/paintops/libpaintop/kis_dab_cache.cpp
M  +9    -7    krita/sdk/tests/stroke_testing_utils.cpp
M  +3    -3    krita/sdk/tests/stroke_testing_utils.h
A  +-    --    krita/ui/tests/data/Basic_tip_default.kpp
A  +-    --    krita/ui/tests/data/testing_predefined_lod.kpp
A  +-    --    krita/ui/tests/data/testing_predefined_lod_spc13.kpp
M  +20   -16   krita/ui/tests/freehand_stroke_test.cpp
M  +3    -1    krita/ui/tests/freehand_stroke_test.h
M  +1    -1    krita/ui/tool/strokes/freehand_stroke.cpp
M  +5    -0    krita/ui/tool/strokes/freehand_stroke.h
M  +3    -1    krita/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp

http://commits.kde.org/calligra/c4bd3a2294a79ad2f4640836f334ab3c2bfca272

diff --git a/krita/image/CMakeLists.txt b/krita/image/CMakeLists.txt
index 4c2c45e..da521aa 100644
--- a/krita/image/CMakeLists.txt
+++ b/krita/image/CMakeLists.txt
@@ -62,6 +62,7 @@ set(kritaimage_LIB_SRCS
    kis_painter.cc
    kis_progress_updater.cpp
    brushengine/kis_paint_information.cc
+   brushengine/kis_random_source.cpp
    brushengine/kis_paintop.cc
    brushengine/kis_paintop_factory.cpp
    brushengine/kis_paintop_preset.cpp
diff --git a/krita/image/brushengine/kis_paint_information.cc b/krita/image/brushengine/kis_paint_information.cc
index 028a087..6602ea2 100644
--- a/krita/image/brushengine/kis_paint_information.cc
+++ b/krita/image/brushengine/kis_paint_information.cc
@@ -50,6 +50,7 @@ struct KisPaintInformation::Private {
         time(time_),
         speed(speed_),
         isHoveringMode(isHoveringMode_),
+        randomSource(new KisRandomSource()),
         currentDistanceInfo(0)
     {
     }
@@ -78,6 +79,7 @@ struct KisPaintInformation::Private {
         time = rhs.time;
         speed = rhs.speed;
         isHoveringMode = rhs.isHoveringMode;
+        randomSource = rhs.randomSource;
         currentDistanceInfo = rhs.currentDistanceInfo;
 
         if (rhs.drawingAngleOverride) {
@@ -96,6 +98,7 @@ struct KisPaintInformation::Private {
     qreal time;
     qreal speed;
     bool isHoveringMode;
+    KisRandomSourceSP randomSource;
 
     QScopedPointer<qreal> drawingAngleOverride;
     KisDistanceInformation *currentDistanceInfo;
@@ -361,6 +364,21 @@ qreal KisPaintInformation::currentTime() const
     return d->time;
 }
 
+KisRandomSourceSP KisPaintInformation::randomSource() const
+{
+    return d->randomSource;
+}
+
+void KisPaintInformation::forkRandomSource() const
+{
+    d->randomSource = new KisRandomSource(*d->randomSource);
+}
+
+void KisPaintInformation::shareRandomSourceFrom(const KisPaintInformation &rhs) const
+{
+    d->randomSource = rhs.d->randomSource;
+}
+
 QDebug operator<<(QDebug dbg, const KisPaintInformation &info)
 {
 #ifdef NDEBUG
@@ -394,6 +412,8 @@ KisPaintInformation KisPaintInformation::mixOnlyPosition(qreal t, const KisPaint
                                basePi.currentTime(),
                                basePi.drawingSpeed());
 
+    result.shareRandomSourceFrom(basePi);
+
     return result;
 }
 
@@ -428,6 +448,8 @@ KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const Ki
     KIS_ASSERT_RECOVER_NOOP(pi1.isHoveringMode() == pi2.isHoveringMode());
     result.d->isHoveringMode = pi1.isHoveringMode();
 
+    result.shareRandomSourceFrom(pi1);
+
     return result;
 }
 
diff --git a/krita/image/brushengine/kis_paint_information.h b/krita/image/brushengine/kis_paint_information.h
index decd7eb..829cf18 100644
--- a/krita/image/brushengine/kis_paint_information.h
+++ b/krita/image/brushengine/kis_paint_information.h
@@ -26,11 +26,14 @@
 #include "kis_vec.h"
 #include "krita_export.h"
 #include "kis_distance_information.h"
+#include "kis_random_source.h"
+
 
 class QDomDocument;
 class QDomElement;
 class KisDistanceInformation;
 
+
 /**
  * KisPaintInformation contains information about the input event that
  * causes the brush action to happen to the brush engine's paint
@@ -181,6 +184,17 @@ public:
     /// Number of ms since the beginning of the stroke
     qreal currentTime() const;
 
+    // random source for generating in-stroke effects
+    KisRandomSourceSP randomSource() const;
+
+    // creates a non-shared copy of a rendom source, keeping the seed
+    // the same
+    void forkRandomSource() const;
+
+    // fetch the random source from \rhs and make it shared among the
+    // two objects
+    void shareRandomSourceFrom(const KisPaintInformation &rhs) const;
+
     /**
      * The paint information may be generated not only during real
      * stroke when the actual painting is happening, but also when the
diff --git a/krita/image/brushengine/kis_random_source.cpp b/krita/image/brushengine/kis_random_source.cpp
new file mode 100644
index 0000000..70c4d0f
--- /dev/null
+++ b/krita/image/brushengine/kis_random_source.cpp
@@ -0,0 +1,89 @@
+/*
+ *  Copyright (c) 2015 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_random_source.h"
+
+#include <boost/random/taus88.hpp>
+#include <boost/random/uniform_smallint.hpp>
+
+struct KisRandomSource::Private
+{
+    Private() : uniformSource(qrand()) {}
+    Private(int seed) : uniformSource(seed) {}
+
+    /**
+     * Taus88's numbers are not too random, but it works fast and it
+     * can be copied very quickly (three 32-bit integers only).
+     *
+     * Average cycle: 2^88 steps
+     */
+    boost::taus88 uniformSource;
+};
+
+
+KisRandomSource::KisRandomSource()
+    : m_d(new Private)
+{
+}
+
+KisRandomSource::KisRandomSource(int seed)
+    : m_d(new Private(seed))
+{
+}
+
+KisRandomSource::KisRandomSource(const KisRandomSource &rhs)
+    : KisShared(),
+      m_d(new Private(*rhs.m_d))
+{
+}
+
+KisRandomSource& KisRandomSource::operator=(const KisRandomSource &rhs)
+{
+    if (this != &rhs) {
+        *m_d = *rhs.m_d;
+    }
+
+    return *this;
+}
+
+KisRandomSource::~KisRandomSource()
+{
+}
+
+int KisRandomSource::min() const
+{
+    return m_d->uniformSource.min();
+}
+
+int KisRandomSource::max() const
+{
+    return m_d->uniformSource.max();
+}
+
+int KisRandomSource::generate() const
+{
+    return m_d->uniformSource();
+}
+
+int KisRandomSource::generate(int min, int max) const
+{
+    boost::uniform_smallint<int> smallint(min, max);
+    return smallint(m_d->uniformSource);
+}
+
+
diff --git a/krita/image/brushengine/kis_random_source.h b/krita/image/brushengine/kis_random_source.h
new file mode 100644
index 0000000..8a1dd72
--- /dev/null
+++ b/krita/image/brushengine/kis_random_source.h
@@ -0,0 +1,52 @@
+/*
+ *  Copyright (c) 2015 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_RANDOM_SOURCE_H
+#define __KIS_RANDOM_SOURCE_H
+
+#include <QScopedPointer>
+#include "kis_shared.h"
+#include "kis_shared_ptr.h"
+
+
+class KRITAIMAGE_EXPORT KisRandomSource : public KisShared
+{
+public:
+    KisRandomSource();
+    KisRandomSource(int seed);
+    KisRandomSource(const KisRandomSource &rhs);
+    KisRandomSource& operator=(const KisRandomSource &rhs);
+
+    ~KisRandomSource();
+
+    int min() const;
+    int max() const;
+
+    int generate() const;
+    int generate(int min, int max) const;
+
+private:
+    struct Private;
+    const QScopedPointer<Private> m_d;
+};
+
+class KisRandomSource;
+typedef KisSharedPtr<KisRandomSource> KisRandomSourceSP;
+typedef KisWeakSharedPtr<KisRandomSource> KisRandomSourceWSP;
+
+#endif /* __KIS_RANDOM_SOURCE_H */
diff --git a/krita/image/tests/kis_paint_information_test.cpp b/krita/image/tests/kis_paint_information_test.cpp
index f842e91..706db3b 100644
--- a/krita/image/tests/kis_paint_information_test.cpp
+++ b/krita/image/tests/kis_paint_information_test.cpp
@@ -21,6 +21,8 @@
 
 #include <qtest_kde.h>
 #include "kis_paint_information.h"
+#include "kis_debug.h"
+
 
 #include <QDomDocument>
 #include <Eigen/Core>
@@ -55,6 +57,26 @@ void KisPaintInformationTest::testSerialisation()
      */
 }
 
+#include <boost/random/taus88.hpp>
+#include <boost/random/uniform_smallint.hpp>
+
+void KisPaintInformationTest::benchmarkTausRandomGeneration()
+{
+    boost::taus88 rnd;
+
+    QBENCHMARK {
+        // make a copy
+        boost::taus88 rnd2(rnd);
+
+        // use smallint shaper
+        boost::uniform_smallint<int> smallint(0,10);
+
+        // generate
+        int value = smallint(rnd2);
+        Q_UNUSED(value);
+    }
+}
+
 
 QTEST_KDEMAIN(KisPaintInformationTest, GUI)
 #include "kis_paint_information_test.moc"
diff --git a/krita/image/tests/kis_paint_information_test.h b/krita/image/tests/kis_paint_information_test.h
index fa5bc2c..f10ee3c 100644
--- a/krita/image/tests/kis_paint_information_test.h
+++ b/krita/image/tests/kis_paint_information_test.h
@@ -29,6 +29,8 @@ private Q_SLOTS:
 
     void testCreation();
     void testSerialisation();
+
+    void benchmarkTausRandomGeneration();
 };
 
 #endif
diff --git a/krita/libbrush/kis_brush.cpp b/krita/libbrush/kis_brush.cpp
index e81d054..05a40e7 100644
--- a/krita/libbrush/kis_brush.cpp
+++ b/krita/libbrush/kis_brush.cpp
@@ -418,10 +418,15 @@ qreal KisBrush::autoSpacingCoeff() const
     return d->autoSpacingCoeff;
 }
 
-void KisBrush::notifyCachedDabPainted()
+void KisBrush::notifyStrokeStarted()
 {
 }
 
+void KisBrush::notifyCachedDabPainted(const KisPaintInformation& info)
+{
+    Q_UNUSED(info);
+}
+
 void KisBrush::prepareBrushPyramid() const
 {
     if (!d->brushPyramid) {
diff --git a/krita/libbrush/kis_brush.h b/krita/libbrush/kis_brush.h
index 1a1bf5c..4a2c5e4 100644
--- a/krita/libbrush/kis_brush.h
+++ b/krita/libbrush/kis_brush.h
@@ -217,6 +217,15 @@ public:
      **/
     virtual bool canPaintFor(const KisPaintInformation& /*info*/);
 
+
+    /**
+     * Is called by the paint op when a paintop starts a stroke.  The
+     * point is that we store brushes a server while the paint ops are
+     * are recreated all the time. Is means that upon a stroke start
+     * the brushes may need to clear its state.
+     */
+    virtual void notifyStrokeStarted();
+
     /**
      * Is called by the cache, when cache hit has happened.
      * Having got this notification the brush can update the counters
@@ -225,7 +234,7 @@ public:
      * Currently, this is used by pipe'd brushes to implement
      * incremental and random parasites
      */
-    void notifyCachedDabPainted();
+    virtual void notifyCachedDabPainted(const KisPaintInformation& info);
 
     /**
      * Return a fixed paint device that contains a correctly scaled image dab.
diff --git a/krita/libbrush/kis_brushes_pipe.h b/krita/libbrush/kis_brushes_pipe.h
index 2d03393..cd49f00 100644
--- a/krita/libbrush/kis_brushes_pipe.h
+++ b/krita/libbrush/kis_brushes_pipe.h
@@ -53,20 +53,20 @@ public:
         return m_brushes.last();
     }
 
-    BrushType* currentBrush(const KisPaintInformation& info) const {
+    BrushType* currentBrush(const KisPaintInformation& info) {
         return !m_brushes.isEmpty() ? m_brushes.at(chooseNextBrush(info)) : 0;
     }
 
-    int brushIndex(const KisPaintInformation& info) const {
+    int brushIndex(const KisPaintInformation& info) {
         return chooseNextBrush(info);
     }
 
-    qint32 maskWidth(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const {
+    qint32 maskWidth(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) {
         BrushType *brush = currentBrush(info);
         return brush ? brush->maskWidth(scale, angle, subPixelX, subPixelY, info) : 0;
     }
 
-    qint32 maskHeight(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const {
+    qint32 maskHeight(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) {
         BrushType *brush = currentBrush(info);
         return brush ? brush->maskHeight(scale, angle, subPixelX, subPixelY, info) : 0;
     }
@@ -96,8 +96,8 @@ public:
         return false;
     }
 
-    void notifyCachedDabPainted() {
-        updateBrushIndexes();
+    void notifyCachedDabPainted(const KisPaintInformation& info) {
+        updateBrushIndexes(info);
     }
 
     void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
@@ -110,7 +110,7 @@ public:
 
 
         brush->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, scaleX, scaleY, angle, info, subPixelX, subPixelY, softnessFactor);
-        updateBrushIndexes();
+        updateBrushIndexes(info);
     }
 
     KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace,
@@ -123,7 +123,7 @@ public:
 
 
         KisFixedPaintDeviceSP device = brush->paintDevice(colorSpace, scale, angle, info, subPixelX, subPixelY);
-        updateBrushIndexes();
+        updateBrushIndexes(info);
         return device;
     }
 
@@ -133,9 +133,16 @@ public:
 
     void testingSelectNextBrush(const KisPaintInformation& info) {
         (void) chooseNextBrush(info);
-        updateBrushIndexes();
+        updateBrushIndexes(info);
     }
 
+    /**
+     * Is called by the paint op when a paintop starts a stroke. The
+     * brushes are shared among different strokes, so sometimes the
+     * brush should be reset.
+     */
+    virtual void notifyStrokeStarted() = 0;
+
 protected:
     void addBrush(BrushType *brush) {
         m_brushes.append(brush);
@@ -149,14 +156,14 @@ protected:
      * The method is const, so no internal counters of the brush should
      * change during its execution
      */
-    virtual int chooseNextBrush(const KisPaintInformation& info) const = 0;
+    virtual int chooseNextBrush(const KisPaintInformation& info) = 0;
 
     /**
      * Updates internal counters of the brush *after* a dab has been
      * painted on the canvas. Some incremental switching of the brushes
      * may me implemented in this method.
      */
-    virtual void updateBrushIndexes() = 0;
+    virtual void updateBrushIndexes(const KisPaintInformation& info) = 0;
 
 protected:
     QVector<BrushType*> m_brushes;
diff --git a/krita/libbrush/kis_imagepipe_brush.cpp b/krita/libbrush/kis_imagepipe_brush.cpp
index 4d301cb..29bebba 100644
--- a/krita/libbrush/kis_imagepipe_brush.cpp
+++ b/krita/libbrush/kis_imagepipe_brush.cpp
@@ -23,6 +23,12 @@
 
 class KisImageBrushesPipe : public KisBrushesPipe<KisGbrBrush>
 {
+public:
+    KisImageBrushesPipe()
+        : m_isInitialized(false)
+    {
+    }
+
 
     /*
        pre and post are split because:
@@ -71,7 +77,8 @@ protected:
     }
 
     static int selectPost(KisParasite::SelectionMode mode,
-                          int index, int rank) {
+                          int index, int rank,
+                          const KisPaintInformation& info) {
 
         switch (mode) {
         case KisParasite::Constant: break;
@@ -79,7 +86,7 @@ protected:
             index = (index + 1) % rank;
             break;
         case KisParasite::Random:
-            index = int(float(rank) * KRandom::random() / RAND_MAX);
+            index = info.randomSource()->generate(0, rank);
             break;
         case KisParasite::Pressure:
         case KisParasite::Angular:
@@ -95,9 +102,14 @@ protected:
         return index;
     }
 
-    int chooseNextBrush(const KisPaintInformation& info) const {
+    int chooseNextBrush(const KisPaintInformation& info) {
         quint32 brushIndex = 0;
 
+        if (!m_isInitialized) {
+            updateBrushIndexes(info);
+            m_isInitialized = true;
+        }
+
         for (int i = 0; i < m_parasite.dim; i++) {
             int index = selectPre(m_parasite.selection[i],
                                   m_parasite.index[i],
@@ -109,11 +121,12 @@ protected:
         return brushIndex;
     }
 
-    void updateBrushIndexes() {
+    void updateBrushIndexes(const KisPaintInformation& info) {
         for (int i = 0; i < m_parasite.dim; i++) {
             m_parasite.index[i] = selectPost(m_parasite.selection[i],
                                              m_parasite.index[i],
-                                             m_parasite.rank[i]);
+                                             m_parasite.rank[i],
+                                             info);
         }
     }
 
@@ -149,8 +162,13 @@ public:
         return true;
     }
 
+    void notifyStrokeStarted() {
+        m_isInitialized = false;
+    }
+
 private:
     KisPipeBrushParasite m_parasite;
+    bool m_isInitialized;
 };
 
 
@@ -326,9 +344,14 @@ bool KisImagePipeBrush::saveToDevice(QIODevice* dev) const
     return m_d->brushesPipe.saveToDevice(dev);
 }
 
-void KisImagePipeBrush::notifyCachedDabPainted()
+void KisImagePipeBrush::notifyStrokeStarted()
+{
+    m_d->brushesPipe.notifyStrokeStarted();
+}
+
+void KisImagePipeBrush::notifyCachedDabPainted(const KisPaintInformation& info)
 {
-    m_d->brushesPipe.notifyCachedDabPainted();
+    m_d->brushesPipe.notifyCachedDabPainted(info);
 }
 
 void KisImagePipeBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
diff --git a/krita/libbrush/kis_imagepipe_brush.h b/krita/libbrush/kis_imagepipe_brush.h
index a8ccea9..b85aa4e 100644
--- a/krita/libbrush/kis_imagepipe_brush.h
+++ b/krita/libbrush/kis_imagepipe_brush.h
@@ -96,7 +96,8 @@ public:
     qint32 maskWidth(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const;
     qint32 maskHeight(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const;
 
-    void notifyCachedDabPainted();
+    void notifyStrokeStarted();
+    void notifyCachedDabPainted(const KisPaintInformation& info);
 
     void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
             double scaleX, double scaleY, double angle,
diff --git a/krita/libbrush/kis_text_brush.cpp b/krita/libbrush/kis_text_brush.cpp
index 045c45b..aa8f5c9 100644
--- a/krita/libbrush/kis_text_brush.cpp
+++ b/krita/libbrush/kis_text_brush.cpp
@@ -117,13 +117,26 @@ public:
         return m_brushesMap.value(m_text.at(0));
     }
 
+    void notifyStrokeStarted() {
+        m_charIndex = 0;
+        updateBrushIndexesImpl();
+    }
+
 protected:
-    int chooseNextBrush(const KisPaintInformation& info) const {
+
+    int chooseNextBrush(const KisPaintInformation& info) {
         Q_UNUSED(info);
         return m_currentBrushIndex;
     }
-    void updateBrushIndexes() {
+    void updateBrushIndexes(const KisPaintInformation& info) {
+        Q_UNUSED(info);
+
         m_charIndex++;
+        updateBrushIndexesImpl();
+    }
+
+private:
+    void updateBrushIndexesImpl() {
         if (m_charIndex >= m_text.size()) {
             m_charIndex = 0;
         }
@@ -189,9 +202,14 @@ QFont KisTextBrush::font()
     return m_font;
 }
 
-void KisTextBrush::notifyCachedDabPainted()
+void KisTextBrush::notifyStrokeStarted()
+{
+    m_brushesPipe->notifyStrokeStarted();
+}
+
+void KisTextBrush::notifyCachedDabPainted(const KisPaintInformation& info)
 {
-    m_brushesPipe->notifyCachedDabPainted();
+    m_brushesPipe->notifyCachedDabPainted(info);
 }
 
 void KisTextBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, double scaleX, double scaleY, double angle, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const
diff --git a/krita/libbrush/kis_text_brush.h b/krita/libbrush/kis_text_brush.h
index 965b0ef..6e64b92 100644
--- a/krita/libbrush/kis_text_brush.h
+++ b/krita/libbrush/kis_text_brush.h
@@ -37,7 +37,8 @@ public:
     KisTextBrush(const KisTextBrush &rhs);
     virtual ~KisTextBrush();
 
-    void notifyCachedDabPainted();
+    void notifyStrokeStarted();
+    void notifyCachedDabPainted(const KisPaintInformation& info);
 
     void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation,
             double scaleX, double scaleY, double angle,
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp
index fe03f94..52f9680 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp
+++ b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp
@@ -85,6 +85,8 @@ KisBrushBasedPaintOp::KisBrushBasedPaintOp(const KisPropertiesConfiguration* set
         m_brush = brushOption.brush();
     }
 
+    m_brush->notifyStrokeStarted();
+
     m_precisionOption.readOptionSetting(settings);
     m_dabCache = new KisDabCache(m_brush);
     m_dabCache->setPrecisionOption(&m_precisionOption);
diff --git a/krita/plugins/paintops/libpaintop/kis_dab_cache.cpp b/krita/plugins/paintops/libpaintop/kis_dab_cache.cpp
index 628e9a0..a811205 100644
--- a/krita/plugins/paintops/libpaintop/kis_dab_cache.cpp
+++ b/krita/plugins/paintops/libpaintop/kis_dab_cache.cpp
@@ -247,7 +247,7 @@ KisFixedPaintDeviceSP KisDabCache::tryFetchFromCache(const SavedDabParameters &p
         *dstDabRect = correctDabRectWhenFetchedFromCache(*dstDabRect, m_d->dab->bounds().size());
     }
 
-    m_d->brush->notifyCachedDabPainted();
+    m_d->brush->notifyCachedDabPainted(info);
     return m_d->dab;
 }
 
diff --git a/krita/sdk/tests/stroke_testing_utils.cpp b/krita/sdk/tests/stroke_testing_utils.cpp
index e8e2e2d..999cd66 100644
--- a/krita/sdk/tests/stroke_testing_utils.cpp
+++ b/krita/sdk/tests/stroke_testing_utils.cpp
@@ -146,6 +146,11 @@ void utils::StrokeTester::setBaseFuzziness(int value)
     m_baseFuzziness = value;
 }
 
+void utils::StrokeTester::testSimpleStroke()
+{
+    testOneStroke(false, true, false, true);
+}
+
 void utils::StrokeTester::test()
 {
     testOneStroke(false, false, false);
@@ -256,7 +261,6 @@ QImage utils::StrokeTester::doStroke(bool cancelled,
     for (int i = 0; i < m_numIterations; i++) {
         modifyResourceManager(manager, image, i);
 
-        KisPainter *painter = new KisPainter();
         KisResourcesSnapshotSP resources =
             new KisResourcesSnapshot(image,
                                      image->rootLayer()->firstChild(),
@@ -273,7 +277,7 @@ QImage utils::StrokeTester::doStroke(bool cancelled,
 
         KisStrokeStrategy *stroke = createStroke(indirectPainting, resources, image);
         m_strokeId = image->startStroke(stroke);
-        addPaintingJobs(image, resources, painter, i);
+        addPaintingJobs(image, resources, i);
 
         if(!cancelled) {
             image->endStroke(m_strokeId);
@@ -330,19 +334,17 @@ void utils::StrokeTester::initImage(KisImageWSP image, KisNodeSP activeNode)
 
 void utils::StrokeTester::addPaintingJobs(KisImageWSP image,
                                           KisResourcesSnapshotSP resources,
-                                          KisPainter *painter, int iteration)
+                                          int iteration)
 {
     Q_UNUSED(iteration);
-    addPaintingJobs(image, resources, painter);
+    addPaintingJobs(image, resources);
 }
 
 void utils::StrokeTester::addPaintingJobs(KisImageWSP image,
-                                          KisResourcesSnapshotSP resources,
-                                          KisPainter *painter)
+                                          KisResourcesSnapshotSP resources)
 {
     Q_UNUSED(image);
     Q_UNUSED(resources);
-    Q_UNUSED(painter);
 }
 
 void utils::StrokeTester::beforeCheckingResult(KisImageWSP image, KisNodeSP activeNode)
diff --git a/krita/sdk/tests/stroke_testing_utils.h b/krita/sdk/tests/stroke_testing_utils.h
index 7100de0..27fba8f 100644
--- a/krita/sdk/tests/stroke_testing_utils.h
+++ b/krita/sdk/tests/stroke_testing_utils.h
@@ -43,6 +43,7 @@ namespace utils {
         StrokeTester(const QString &name, const QSize &imageSize, const QString &presetFileName = "autobrush_300px.kpp");
         virtual ~StrokeTester();
 
+        void testSimpleStroke();
         void test();
         void benchmark();
 
@@ -73,12 +74,11 @@ namespace utils {
 
         virtual void addPaintingJobs(KisImageWSP image,
                                      KisResourcesSnapshotSP resources,
-                                     KisPainter *painter, int iteration);
+                                     int iteration);
 
         // overload
         virtual void addPaintingJobs(KisImageWSP image,
-                                     KisResourcesSnapshotSP resources,
-                                     KisPainter *painter);
+                                     KisResourcesSnapshotSP resources);
 
     private:
         void testOneStroke(bool cancelled, bool indirectPainting,
diff --git a/krita/ui/tests/data/Basic_tip_default.kpp b/krita/ui/tests/data/Basic_tip_default.kpp
new file mode 100644
index 0000000..730c84c
Binary files /dev/null and b/krita/ui/tests/data/Basic_tip_default.kpp differ
diff --git a/krita/ui/tests/data/testing_predefined_lod.kpp b/krita/ui/tests/data/testing_predefined_lod.kpp
new file mode 100644
index 0000000..f366c27
Binary files /dev/null and b/krita/ui/tests/data/testing_predefined_lod.kpp differ
diff --git a/krita/ui/tests/data/testing_predefined_lod_spc13.kpp b/krita/ui/tests/data/testing_predefined_lod_spc13.kpp
new file mode 100644
index 0000000..feb419f
Binary files /dev/null and b/krita/ui/tests/data/testing_predefined_lod_spc13.kpp differ
diff --git a/krita/ui/tests/freehand_stroke_test.cpp b/krita/ui/tests/freehand_stroke_test.cpp
index bdd11e7..ddb80de 100644
--- a/krita/ui/tests/freehand_stroke_test.cpp
+++ b/krita/ui/tests/freehand_stroke_test.cpp
@@ -95,21 +95,16 @@ protected:
         QScopedPointer<FreehandStrokeStrategy> stroke(
             new FreehandStrokeStrategy(indirectPainting, COMPOSITE_ALPHA_DARKEN, resources, painterInfo, kundo2_noi18n("Freehand Stroke")));
 
-        return m_useLod ? stroke->createLodClone(1) : stroke.take();
+        return stroke.take();
     }
 
     virtual void addPaintingJobs(KisImageWSP image,
-                                 KisResourcesSnapshotSP resources,
-                                 KisPainter *painter)
+                                 KisResourcesSnapshotSP resources)
     {
-        addPaintingJobs(image, resources, painter, 0);
+        addPaintingJobs(image, resources, 0);
     }
 
-    void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, KisPainter *painter, int iteration) {
-
-        Q_ASSERT(painter == m_painterInfo->painter);
-        Q_UNUSED(painter);
-
+    void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, int iteration) {
         KisPaintInformation pi1;
         KisPaintInformation pi2;
 
@@ -125,7 +120,7 @@ protected:
             new FreehandStrokeStrategy::Data(resources->currentNode(),
                                              0, pi1, pi2));
 
-        image->addJob(strokeId(), m_useLod ? data->createLodClone(1) : data.take());
+        image->addJob(strokeId(), data.take());
     }
 
 private:
@@ -165,12 +160,6 @@ void FreehandStrokeTest::testAutoTextured38()
     tester.test();
 }
 
-void FreehandStrokeTest::testAutobrushStrokeLod()
-{
-    FreehandStrokeTester tester("autobrush_300px.kpp", true);
-    tester.test();
-}
-
 void FreehandStrokeTest::testMixDullCompositioning()
 {
     FreehandStrokeTester tester("Mix_dull.kpp");
@@ -179,5 +168,20 @@ void FreehandStrokeTest::testMixDullCompositioning()
     tester.test();
 }
 
+void FreehandStrokeTest::testAutobrushStrokeLod()
+{
+    FreehandStrokeTester tester("Basic_tip_default.kpp", true);
+    tester.testSimpleStroke();
+}
+
+void FreehandStrokeTest::testPredefinedBrushStrokeLod()
+{
+    qsrand(QTime::currentTime().msec());
+
+    FreehandStrokeTester tester("testing_predefined_lod_spc13.kpp", true);
+    //FreehandStrokeTester tester("testing_predefined_lod.kpp", true);
+    tester.testSimpleStroke();
+}
+
 QTEST_KDEMAIN(FreehandStrokeTest, GUI)
 #include "freehand_stroke_test.moc"
diff --git a/krita/ui/tests/freehand_stroke_test.h b/krita/ui/tests/freehand_stroke_test.h
index 2bf3061..0774b1b 100644
--- a/krita/ui/tests/freehand_stroke_test.h
+++ b/krita/ui/tests/freehand_stroke_test.h
@@ -32,8 +32,10 @@ private Q_SLOTS:
     void testColorSmudgeStroke();
     void testAutoTextured17();
     void testAutoTextured38();
-    void testAutobrushStrokeLod();
     void testMixDullCompositioning();
+
+    void testAutobrushStrokeLod();
+    void testPredefinedBrushStrokeLod();
 };
 
 #endif /* __FREEHAND_STROKE_TEST_H */
diff --git a/krita/ui/tool/strokes/freehand_stroke.cpp b/krita/ui/tool/strokes/freehand_stroke.cpp
index e7e601b..fb9051c 100644
--- a/krita/ui/tool/strokes/freehand_stroke.cpp
+++ b/krita/ui/tool/strokes/freehand_stroke.cpp
@@ -55,7 +55,7 @@ FreehandStrokeStrategy::FreehandStrokeStrategy(bool needsIndirectPainting,
 
 FreehandStrokeStrategy::FreehandStrokeStrategy(const FreehandStrokeStrategy &rhs)
     : KisPainterBasedStrokeStrategy(rhs),
-      m_d(new Private())
+      m_d(new Private(*rhs.m_d))
 {
 }
 
diff --git a/krita/ui/tool/strokes/freehand_stroke.h b/krita/ui/tool/strokes/freehand_stroke.h
index fefbc1f..8efb80c 100644
--- a/krita/ui/tool/strokes/freehand_stroke.h
+++ b/krita/ui/tool/strokes/freehand_stroke.h
@@ -106,14 +106,19 @@ public:
             switch(type) {
             case Data::POINT:
                 pi1 = t.map(rhs.pi1);
+                pi1.forkRandomSource();
                 break;
             case Data::LINE:
                 pi1 = t.map(rhs.pi1);
                 pi2 = t.map(rhs.pi2);
+                pi1.forkRandomSource();
+                pi2.forkRandomSource();
                 break;
             case Data::CURVE:
                 pi1 = t.map(rhs.pi1);
                 pi2 = t.map(rhs.pi2);
+                pi1.forkRandomSource();
+                pi2.forkRandomSource();
                 control1 = t.map(rhs.control1);
                 control2 = t.map(rhs.control2);
                 break;
diff --git a/krita/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp b/krita/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
index 162e3cd..1f9e1d2 100644
--- a/krita/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
+++ b/krita/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp
@@ -95,7 +95,9 @@ void KisPainterBasedStrokeStrategy::init()
 KisPainterBasedStrokeStrategy::KisPainterBasedStrokeStrategy(const KisPainterBasedStrokeStrategy &rhs)
     : KisSimpleStrokeStrategy(rhs),
       m_resources(rhs.m_resources),
-      m_undoEnabled(true)
+      m_transaction(rhs.m_transaction),
+      m_undoEnabled(true),
+      m_useMergeID(rhs.m_useMergeID)
 {
     foreach(PainterInfo *info, rhs.m_painterInfos) {
         m_painterInfos.append(new PainterInfo(*info));


More information about the kimageshop mailing list