[krita/kazakov/lazy-brush-remastered] /: Implement prefiltering algorithms for Lazy Fill Tool

Dmitry Kazakov null at kde.org
Fri Nov 17 16:02:05 UTC 2017


Git commit 42a63fff267ae1ebd3d4d24e619207b99289905c by Dmitry Kazakov.
Committed on 17/11/2017 at 14:03.
Pushed by dkazakov into branch 'kazakov/lazy-brush-remastered'.

Implement prefiltering algorithms for Lazy Fill Tool

Now colorize mask has several options:

* "Edge detection (px)" --- if activated, the mask tries to find edges of
  the filled areas in the source image. This option should be activated
  **only** if the source image has vast areas filled with (almost) solid
  color.

  Important: in case you image is a mixture of lines and solid blobs, make
             sure that the "size" of edge detection filter is set to the
             size of the lines in your image. That will give you the best
             results.

* "Gap close hint (px)" is the size of the gaps the filter will try to
  "close" before running the flood filling. This value should be about
  the size of the maximum unclosed gap in your image.

* "Clean up (%)" is a special mode of the mask, when it tries to remove
  some of your key strokes if they go beyond the "closed" areas. When 0%,
  clean up phase is skipped, when 100% it tries to remove as much
  "uncertain" details as possible.

CC:kimageshop at kde.org

M  +5    -6    libs/image/kis_gaussian_kernel.cpp
M  +2    -1    libs/image/kis_gaussian_kernel.h
M  +15   -9    libs/image/lazybrush/KisWatershedWorker.cpp
M  +21   -2    libs/image/lazybrush/KisWatershedWorker.h
M  +54   -3    libs/image/lazybrush/kis_colorize_mask.cpp
M  +13   -0    libs/image/lazybrush/kis_colorize_mask.h
M  +45   -6    libs/image/lazybrush/kis_colorize_stroke_strategy.cpp
M  +7    -0    libs/image/lazybrush/kis_colorize_stroke_strategy.h
M  +16   -0    libs/image/lazybrush/kis_lazy_fill_tools.cpp
M  +14   -0    libs/image/lazybrush/kis_lazy_fill_tools.h
M  +2    -1    libs/image/tests/KisWatershedWorkerTest.cpp
M  +2    -0    libs/image/tests/kis_lazy_brush_test.cpp
M  +13   -2    plugins/impex/libkra/kis_kra_loader.cpp
M  +11   -0    plugins/impex/libkra/kis_kra_savexml_visitor.cpp
M  +4    -0    plugins/impex/libkra/kis_kra_tags.h
M  +76   -4    plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp
M  +6    -0    plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.h
M  +52   -18   plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.ui

https://commits.kde.org/krita/42a63fff267ae1ebd3d4d24e619207b99289905c

diff --git a/libs/image/kis_gaussian_kernel.cpp b/libs/image/kis_gaussian_kernel.cpp
index bc12d76b4c8..297c443e16a 100644
--- a/libs/image/kis_gaussian_kernel.cpp
+++ b/libs/image/kis_gaussian_kernel.cpp
@@ -148,13 +148,13 @@ void KisGaussianKernel::applyGaussian(KisPaintDeviceSP device,
 }
 
 Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic>
-KisGaussianKernel::createLoGMatrix(qreal radius)
+KisGaussianKernel::createLoGMatrix(qreal radius, qreal coeff)
 {
     int kernelSize = 4 * std::ceil(radius) + 1;
     Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix(kernelSize, kernelSize);
 
     const qreal sigma = radius/* / sqrt(2)*/;
-    const qreal multiplicand = -1 / (M_PI * pow2(pow2(sigma)));
+    const qreal multiplicand = -1.0 / (M_PI * pow2(pow2(sigma)));
     const qreal exponentMultiplicand = 1 / (2 * pow2(sigma));
 
     /**
@@ -200,7 +200,7 @@ KisGaussianKernel::createLoGMatrix(qreal radius)
     }
 
 
-    const qreal scale = 2.0 / positiveSum;
+    const qreal scale = coeff * 2.0 / positiveSum;
     matrix *= scale;
     positiveSum *= scale;
     sideSum *= scale;
@@ -215,7 +215,7 @@ KisGaussianKernel::createLoGMatrix(qreal radius)
 
 void KisGaussianKernel::applyLoG(KisPaintDeviceSP device,
                                  const QRect& rect,
-                                 qreal radius,
+                                 qreal radius, qreal coeff,
                                  const QBitArray &channelFlags,
                                  KoUpdater *progressUpdater)
 {
@@ -225,8 +225,7 @@ void KisGaussianKernel::applyLoG(KisPaintDeviceSP device,
     painter.setChannelFlags(channelFlags);
     painter.setProgress(progressUpdater);
 
-    Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createLoGMatrix(radius);
-    qDebug() << ppVar(matrix.sum());
+    Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> matrix = createLoGMatrix(radius, coeff);
     KisConvolutionKernelSP kernel =
         KisConvolutionKernel::fromMatrix(matrix,
                                          0,
diff --git a/libs/image/kis_gaussian_kernel.h b/libs/image/kis_gaussian_kernel.h
index 031822fcbb3..e25005d162b 100644
--- a/libs/image/kis_gaussian_kernel.h
+++ b/libs/image/kis_gaussian_kernel.h
@@ -50,11 +50,12 @@ public:
                               const QBitArray &channelFlags,
                               KoUpdater *updater);
 
-    static Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> createLoGMatrix(qreal radius);
+    static Eigen::Matrix<qreal, Eigen::Dynamic, Eigen::Dynamic> createLoGMatrix(qreal radius, qreal coeff = 1.0);
 
     static void applyLoG(KisPaintDeviceSP device,
                          const QRect& rect,
                          qreal radius,
+                         qreal coeff,
                          const QBitArray &channelFlags,
                          KoUpdater *progressUpdater);
 };
diff --git a/libs/image/lazybrush/KisWatershedWorker.cpp b/libs/image/lazybrush/KisWatershedWorker.cpp
index 47779d0df8b..00b95adfeac 100644
--- a/libs/image/lazybrush/KisWatershedWorker.cpp
+++ b/libs/image/lazybrush/KisWatershedWorker.cpp
@@ -151,13 +151,13 @@ struct CompareTaskPoints {
 void mergeHeightmapOntoStroke(KisPaintDeviceSP stroke, KisPaintDeviceSP heightMap, const QRect &rc)
 {
     KisSequentialIterator dstIt(stroke, rc);
-    KisSequentialIterator mapIt(heightMap, rc);
+    KisSequentialConstIterator mapIt(heightMap, rc);
 
     do {
         quint8 *dstPtr = dstIt.rawData();
 
         if (*dstPtr > 0) {
-            quint8 *mapPtr = mapIt.rawData();
+            const quint8 *mapPtr = mapIt.rawDataConst();
             *dstPtr = qMax(quint8(1), *mapPtr);
         } else {
             *dstPtr = 0;
@@ -241,7 +241,7 @@ struct KisWatershedWorker::Private
     void updateNarrowRegionMetrics();
 
     QVector<GroupLevelPair> calculateConflictingPairs();
-    void cleanupForeignEdgeGroups();
+    void cleanupForeignEdgeGroups(qreal cleanUpAmount);
 
     void dumpGroupMaps();
     void calcNumGroupMaps();
@@ -281,7 +281,7 @@ void KisWatershedWorker::addKeyStroke(KisPaintDeviceSP dev, const KoColor &color
     m_d->keyStrokes << KeyStroke(dev, color);
 }
 
-void KisWatershedWorker::run(bool doCleanUp)
+void KisWatershedWorker::run(qreal cleanUpAmount)
 {
     if (!m_d->heightMap) return;
 
@@ -306,8 +306,8 @@ void KisWatershedWorker::run(bool doCleanUp)
 //    m_d->dumpGroupMaps();
 //    m_d->calcNumGroupMaps();
 
-    if (doCleanUp) {
-        m_d->cleanupForeignEdgeGroups();
+    if (cleanUpAmount > 0) {
+        m_d->cleanupForeignEdgeGroups(cleanUpAmount);
     }
 
     m_d->writeColoring();
@@ -509,8 +509,9 @@ void KisWatershedWorker::Private::visitNeighbour(const QPoint &currPt, const QPo
         pt.y = currPt.y();
         pt.group = prevGroupId;
         pt.level = newLevel;
-        pt.distance = prevDistance + 1;
+        pt.distance = newLevel == prevLevel ? prevDistance + 1 : 0;
         pt.prevDirection = fromDirection;
+
         pointsQueue.push(pt);
     }
 
@@ -781,8 +782,13 @@ QVector<GroupLevelPair> KisWatershedWorker::Private::calculateConflictingPairs()
 #include <boost/accumulators/statistics/mean.hpp>
 #include <boost/accumulators/statistics/min.hpp>
 
-void KisWatershedWorker::Private::cleanupForeignEdgeGroups()
+void KisWatershedWorker::Private::cleanupForeignEdgeGroups(qreal cleanUpAmount)
 {
+    KIS_SAFE_ASSERT_RECOVER_NOOP(cleanUpAmount > 0.0);
+
+    // convert into the threshold range [0.05...0.5]
+    const qreal foreignEdgePortionThreshold = 0.05 + 0.45 * (1.0 - qBound(0.0, cleanUpAmount, 1.0));
+
     QVector<GroupLevelPair> conflicts = calculateConflictingPairs();
 
     // sort the pairs by the total edge size
@@ -828,7 +834,7 @@ void KisWatershedWorker::Private::cleanupForeignEdgeGroups()
 //                 << ppVar(minMetric)
 //                 << ppVar(meanMetric);
 
-        if (thisForeignPortion < 0.35) continue;
+        if (!(thisForeignPortion > foreignEdgePortionThreshold)) continue;
 
         if (minMetric > 1.0 && meanMetric > 1.2) {
             //qDebug() << "   * removing...";
diff --git a/libs/image/lazybrush/KisWatershedWorker.h b/libs/image/lazybrush/KisWatershedWorker.h
index 580aec1c442..a83a3233206 100644
--- a/libs/image/lazybrush/KisWatershedWorker.h
+++ b/libs/image/lazybrush/KisWatershedWorker.h
@@ -29,13 +29,32 @@ class KoColor;
 class KRITAIMAGE_EXPORT KisWatershedWorker
 {
 public:
-    KisWatershedWorker(KisPaintDeviceSP src,
+    /**
+     * Creates an empty watershed worker withouth any strokes attached. The strokes
+     * should be attached manually with addKeyStroke() call.
+     *
+     * @param heightMap prefiltered height map in alpha8 colorspace, with "0" meaning
+     *                  background color and "255" meaning line art. Heightmap is *never*
+     *                  modified by the worker!
+     * @param dst destination device where the result will be written
+     * @param boundingRect the worker refuses to fill outside the bounding rect, considering
+     *                     that outer area as having +inf height
+     */
+    KisWatershedWorker(KisPaintDeviceSP heightMap,
                        KisPaintDeviceSP dst,
                        const QRect &boundingRect);
     ~KisWatershedWorker();
 
     void addKeyStroke(KisPaintDeviceSP dev, const KoColor &color);
-    void run(bool doCleanUp = false);
+
+    /**
+     * @brief run the filling process using the passes height map, strokes, and write
+     *        the result coloring into the destination device
+     * @param cleanUpAmount shows how agressively we should try to clean up the filnal
+     *                      coloring. Should be in range [0.0...1.0]
+     */
+
+    void run(qreal cleanUpAmount = 0.0);
 
     int testingGroupPositiveEdge(qint32 group, quint8 level);
     int testingGroupNegativeEdge(qint32 group, quint8 level);
diff --git a/libs/image/lazybrush/kis_colorize_mask.cpp b/libs/image/lazybrush/kis_colorize_mask.cpp
index 17193ab1055..16a0e1803f3 100644
--- a/libs/image/lazybrush/kis_colorize_mask.cpp
+++ b/libs/image/lazybrush/kis_colorize_mask.cpp
@@ -60,7 +60,8 @@ struct KisColorizeMask::Private
           showColoring(true),
           needsUpdate(true),
           originalSequenceNumber(-1),
-          updateCompressor(1, KisSignalCompressor::POSTPONE, q)
+          updateCompressor(1, KisSignalCompressor::POSTPONE, q),
+          filteringOptions(false, 4.0, 15, 0.7)
     {
     }
 
@@ -74,7 +75,8 @@ struct KisColorizeMask::Private
           needsUpdate(false),
           originalSequenceNumber(-1),
           updateCompressor(1000, KisSignalCompressor::POSTPONE, q),
-          offset(rhs.offset)
+          offset(rhs.offset),
+          filteringOptions(rhs.filteringOptions)
     {
         Q_FOREACH (const KeyStroke &stroke, rhs.keyStrokes) {
             keyStrokes << KeyStroke(KisPaintDeviceSP(new KisPaintDevice(*stroke.dev)), stroke.color, stroke.isTransparent);
@@ -101,6 +103,9 @@ struct KisColorizeMask::Private
 
     KisSignalCompressor updateCompressor;
     QPoint offset;
+
+    FilteringOptions filteringOptions;
+    bool filteringDirty = true;
 };
 
 KisColorizeMask::KisColorizeMask()
@@ -254,8 +259,9 @@ void KisColorizeMask::slotUpdateRegenerateFilling()
     KisPaintDeviceSP src = parent()->original();
     KIS_ASSERT_RECOVER_RETURN(src);
 
-    bool filteredSourceValid = m_d->originalSequenceNumber == src->sequenceNumber();
+    bool filteredSourceValid = m_d->originalSequenceNumber == src->sequenceNumber() && !m_d->filteringDirty;
     m_d->originalSequenceNumber = src->sequenceNumber();
+    m_d->filteringDirty = false;
     m_d->coloringProjection->clear();
 
     KisLayerSP parentLayer(qobject_cast<KisLayer*>(parent().data()));
@@ -271,6 +277,8 @@ void KisColorizeMask::slotUpdateRegenerateFilling()
                                           image->bounds(),
                                           KisColorizeMaskSP(this));
 
+        strategy->setFilteringOptions(m_d->filteringOptions);
+
         Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
             const KoColor color =
                 !stroke.isTransparent ?
@@ -817,6 +825,49 @@ void KisColorizeMask::resetCache()
     rerenderFakePaintDevice();
 }
 
+void KisColorizeMask::setUseEdgeDetection(bool value)
+{
+    m_d->filteringOptions.useEdgeDetection = value;
+    m_d->filteringDirty = true;
+}
+
+bool KisColorizeMask::useEdgeDetection() const
+{
+    return m_d->filteringOptions.useEdgeDetection;
+}
+
+void KisColorizeMask::setEdgeDetectionSize(qreal value)
+{
+    m_d->filteringOptions.edgeDetectionSize = value;
+    m_d->filteringDirty = true;
+}
+
+qreal KisColorizeMask::edgeDetectionSize() const
+{
+    return m_d->filteringOptions.edgeDetectionSize;
+}
+
+void KisColorizeMask::setFuzzyRadius(qreal value)
+{
+    m_d->filteringOptions.fuzzyRadius = value;
+    m_d->filteringDirty = true;
+}
+
+qreal KisColorizeMask::fuzzyRadius() const
+{
+    return m_d->filteringOptions.fuzzyRadius;
+}
+
+void KisColorizeMask::setCleanUpAmount(qreal value)
+{
+    m_d->filteringOptions.cleanUpAmount = value;
+}
+
+qreal KisColorizeMask::cleanUpAmount() const
+{
+    return m_d->filteringOptions.cleanUpAmount;
+}
+
 
 void KisColorizeMask::rerenderFakePaintDevice()
 {
diff --git a/libs/image/lazybrush/kis_colorize_mask.h b/libs/image/lazybrush/kis_colorize_mask.h
index 0f25c387476..0d0ef201546 100644
--- a/libs/image/lazybrush/kis_colorize_mask.h
+++ b/libs/image/lazybrush/kis_colorize_mask.h
@@ -101,6 +101,19 @@ public:
     QVector<KisPaintDeviceSP> allPaintDevices() const;
     void resetCache();
 
+    void setUseEdgeDetection(bool value);
+    bool useEdgeDetection() const;
+
+    void setEdgeDetectionSize(qreal value);
+    qreal edgeDetectionSize() const;
+
+    void setFuzzyRadius(qreal value);
+    qreal fuzzyRadius() const;
+
+    void setCleanUpAmount(qreal value);
+    qreal cleanUpAmount() const;
+
+
     void testingAddKeyStroke(KisPaintDeviceSP dev, const KoColor &color, bool isTransparent = false);
     void testingRegenerateMask();
     KisPaintDeviceSP testingFilteredSource() const;
diff --git a/libs/image/lazybrush/kis_colorize_stroke_strategy.cpp b/libs/image/lazybrush/kis_colorize_stroke_strategy.cpp
index a3ace2cde95..58e908cfd36 100644
--- a/libs/image/lazybrush/kis_colorize_stroke_strategy.cpp
+++ b/libs/image/lazybrush/kis_colorize_stroke_strategy.cpp
@@ -20,6 +20,7 @@
 
 #include <QBitArray>
 
+#include "krita_utils.h"
 #include "kis_paint_device.h"
 #include "kis_lazy_fill_tools.h"
 #include "kis_gaussian_kernel.h"
@@ -55,6 +56,9 @@ struct KisColorizeStrokeStrategy::Private
 
     QVector<KeyStroke> keyStrokes;
     KisNodeSP dirtyNode;
+
+    // default values: disabled
+    FilteringOptions filteringOptions;
 };
 
 KisColorizeStrokeStrategy::KisColorizeStrokeStrategy(KisPaintDeviceSP src,
@@ -87,6 +91,16 @@ KisColorizeStrokeStrategy::~KisColorizeStrokeStrategy()
 {
 }
 
+void KisColorizeStrokeStrategy::setFilteringOptions(const FilteringOptions &value)
+{
+    m_d->filteringOptions = value;
+}
+
+FilteringOptions KisColorizeStrokeStrategy::filteringOptions() const
+{
+    return m_d->filteringOptions;
+}
+
 void KisColorizeStrokeStrategy::addKeyStroke(KisPaintDeviceSP dev, const KoColor &color)
 {
     KoColor convertedColor(color);
@@ -100,11 +114,36 @@ void KisColorizeStrokeStrategy::initStrokeCallback()
     if (!m_d->filteredSourceValid) {
         KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsAlpha(m_d->src);
 
-        // optional filtering
-        // KisGaussianKernel::applyLoG(filteredMainDev,
-        //                             m_d->boundingRect,
-        //                             1.0,
-        //                             QBitArray(), 0);
+        if (m_d->filteringOptions.useEdgeDetection &&
+            m_d->filteringOptions.edgeDetectionSize > 0.0) {
+
+            KisGaussianKernel::applyLoG(filteredMainDev,
+                                        m_d->boundingRect,
+                                        0.5 * m_d->filteringOptions.edgeDetectionSize,
+                                        -1.0,
+                                        QBitArray(), 0);
+
+            normalizeAlpha8Device(filteredMainDev, m_d->boundingRect);
+
+            KisGaussianKernel::applyGaussian(filteredMainDev,
+                                             m_d->boundingRect,
+                                             m_d->filteringOptions.edgeDetectionSize,
+                                             m_d->filteringOptions.edgeDetectionSize,
+                                             QBitArray(), 0);
+        }
+
+        if (m_d->filteringOptions.fuzzyRadius > 0) {
+            KisPaintDeviceSP filteredMainDevCopy = new KisPaintDevice(*filteredMainDev);
+
+            KisGaussianKernel::applyGaussian(filteredMainDev,
+                                             m_d->boundingRect,
+                                             m_d->filteringOptions.fuzzyRadius,
+                                             m_d->filteringOptions.fuzzyRadius,
+                                             QBitArray(), 0);
+
+            KisPainter gc(filteredMainDev);
+            gc.bitBlt(m_d->boundingRect.topLeft(), filteredMainDevCopy, m_d->boundingRect);
+        }
 
         normalizeAlpha8Device(filteredMainDev, m_d->boundingRect);
 
@@ -117,7 +156,7 @@ void KisColorizeStrokeStrategy::initStrokeCallback()
     Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
         worker.addKeyStroke(new KisPaintDevice(*stroke.dev), stroke.color);
     }
-    worker.run(true);
+    worker.run(m_d->filteringOptions.cleanUpAmount);
 
 
     m_d->dirtyNode->setDirty(m_d->boundingRect);
diff --git a/libs/image/lazybrush/kis_colorize_stroke_strategy.h b/libs/image/lazybrush/kis_colorize_stroke_strategy.h
index dda84c0ba12..9ec382683fc 100644
--- a/libs/image/lazybrush/kis_colorize_stroke_strategy.h
+++ b/libs/image/lazybrush/kis_colorize_stroke_strategy.h
@@ -27,6 +27,10 @@
 
 class KoColor;
 
+namespace KisLazyFillTools {
+struct FilteringOptions;
+}
+
 
 class KisColorizeStrokeStrategy : public QObject, public KisSimpleStrokeStrategy
 {
@@ -41,6 +45,9 @@ public:
     KisColorizeStrokeStrategy(const KisColorizeStrokeStrategy &rhs, int levelOfDetail);
     ~KisColorizeStrokeStrategy() override;
 
+    void setFilteringOptions(const KisLazyFillTools::FilteringOptions &value);
+    KisLazyFillTools::FilteringOptions filteringOptions() const;
+
     void addKeyStroke(KisPaintDeviceSP dev, const KoColor &color);
 
     void initStrokeCallback() override;
diff --git a/libs/image/lazybrush/kis_lazy_fill_tools.cpp b/libs/image/lazybrush/kis_lazy_fill_tools.cpp
index f42b8708fe8..0cce32de852 100644
--- a/libs/image/lazybrush/kis_lazy_fill_tools.cpp
+++ b/libs/image/lazybrush/kis_lazy_fill_tools.cpp
@@ -199,4 +199,20 @@ bool operator==(const KeyStroke& t1, const KeyStroke&t2)
         t1.isTransparent == t2.isTransparent;
 }
 
+FilteringOptions::FilteringOptions(bool _useEdgeDetection, qreal _edgeDetectionSize, qreal _fuzzyRadius, qreal _cleanUpAmount)
+    : useEdgeDetection(_useEdgeDetection),
+      edgeDetectionSize(_edgeDetectionSize),
+      fuzzyRadius(_fuzzyRadius),
+      cleanUpAmount(_cleanUpAmount)
+{
+}
+
+bool operator==(const FilteringOptions &t1, const FilteringOptions &t2)
+{
+    return t1.useEdgeDetection == t2.useEdgeDetection &&
+           qFuzzyCompare(t1.edgeDetectionSize, t2.edgeDetectionSize) &&
+           qFuzzyCompare(t1.fuzzyRadius, t2.fuzzyRadius) &&
+           qFuzzyCompare(t1.cleanUpAmount, t2.cleanUpAmount);
+}
+
 }
diff --git a/libs/image/lazybrush/kis_lazy_fill_tools.h b/libs/image/lazybrush/kis_lazy_fill_tools.h
index 46072f83525..f8fc15eab9a 100644
--- a/libs/image/lazybrush/kis_lazy_fill_tools.h
+++ b/libs/image/lazybrush/kis_lazy_fill_tools.h
@@ -79,6 +79,20 @@ namespace KisLazyFillTools
         KoColor color;
         bool isTransparent;
     };
+
+    struct KRITAIMAGE_EXPORT FilteringOptions : public boost::equality_comparable<FilteringOptions>
+    {
+        FilteringOptions() = default;
+        FilteringOptions(bool _useEdgeDetection, qreal _edgeDetectionSize, qreal _fuzzyRadius, qreal _cleanUpAmount);
+
+        friend bool operator==(const FilteringOptions &t1, const FilteringOptions &t2);
+
+        // default values for filtering: disabled
+        bool useEdgeDetection = false;
+        qreal edgeDetectionSize = 4;
+        qreal fuzzyRadius = 0;
+        qreal cleanUpAmount = 0.0;
+    };
 };
 
 #endif /* __KIS_LAZY_FILL_TOOLS_H */
diff --git a/libs/image/tests/KisWatershedWorkerTest.cpp b/libs/image/tests/KisWatershedWorkerTest.cpp
index 1aca7df005e..7ce11cf2299 100644
--- a/libs/image/tests/KisWatershedWorkerTest.cpp
+++ b/libs/image/tests/KisWatershedWorkerTest.cpp
@@ -59,6 +59,7 @@ void KisWatershedWorkerTest::testWorker()
     KisGaussianKernel::applyLoG(filteredMainDev,
                                 filterRect,
                                 2,
+                                -1.0,
                                 QBitArray(), 0);
 
     KisLazyFillTools::normalizeAlpha8Device(filteredMainDev, filterRect);
@@ -69,7 +70,7 @@ void KisWatershedWorkerTest::testWorker()
     KisWatershedWorker worker(filteredMainDev, resultColoring, filterRect);
     worker.addKeyStroke(aLabelDev, KoColor(Qt::red, mainDev->colorSpace()));
     worker.addKeyStroke(bLabelDev, KoColor(Qt::blue, mainDev->colorSpace()));
-    worker.run(true);
+    worker.run(0.7);
 
 }
 
diff --git a/libs/image/tests/kis_lazy_brush_test.cpp b/libs/image/tests/kis_lazy_brush_test.cpp
index 3103a8f7d01..d7fc5281352 100644
--- a/libs/image/tests/kis_lazy_brush_test.cpp
+++ b/libs/image/tests/kis_lazy_brush_test.cpp
@@ -1229,6 +1229,7 @@ void KisLazyBrushTest::testCutOnGraphDeviceMulti()
     KisGaussianKernel::applyLoG(filteredMainDev,
                                 filterRect,
                                 2,
+                                1.0,
                                 QBitArray(), 0);
 
     KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, filterRect);
@@ -1272,6 +1273,7 @@ void KisLazyBrushTest::testLoG()
     KisGaussianKernel::applyLoG(filteredMainDev,
                                 rect,
                                 4.0,
+                                1.0,
                                 QBitArray(), 0);
 
     KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, rect);
diff --git a/plugins/impex/libkra/kis_kra_loader.cpp b/plugins/impex/libkra/kis_kra_loader.cpp
index 3ad6aba6f28..ae38512c0f1 100644
--- a/plugins/impex/libkra/kis_kra_loader.cpp
+++ b/plugins/impex/libkra/kis_kra_loader.cpp
@@ -1029,11 +1029,22 @@ KisNodeSP KisKraLoader::loadColorizeMask(KisImageSP image, const KoXmlElement& e
 {
     Q_UNUSED(parent);
     KisColorizeMaskSP mask = new KisColorizeMask();
-    bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true;
-    bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true;
+    const bool editKeystrokes = element.attribute(COLORIZE_EDIT_KEYSTROKES, "1") == "0" ? false : true;
+    const bool showColoring = element.attribute(COLORIZE_SHOW_COLORING, "1") == "0" ? false : true;
 
     KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, editKeystrokes, image);
     KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, showColoring, image);
+
+    const bool useEdgeDetection = KisDomUtils::toInt(element.attribute(COLORIZE_USE_EDGE_DETECTION, "0"));
+    const qreal edgeDetectionSize = KisDomUtils::toDouble(element.attribute(COLORIZE_EDGE_DETECTION_SIZE, "4"));
+    const qreal radius = KisDomUtils::toDouble(element.attribute(COLORIZE_FUZZY_RADIUS, "0"));
+    const int cleanUp = KisDomUtils::toInt(element.attribute(COLORIZE_CLEANUP, "0"));
+
+    mask->setUseEdgeDetection(useEdgeDetection);
+    mask->setEdgeDetectionSize(edgeDetectionSize);
+    mask->setFuzzyRadius(radius);
+    mask->setCleanUpAmount(qreal(cleanUp) / 100.0);
+
     delete mask->setColorSpace(colorSpace);
     mask->setImage(image);
 
diff --git a/plugins/impex/libkra/kis_kra_savexml_visitor.cpp b/plugins/impex/libkra/kis_kra_savexml_visitor.cpp
index 23f94435f9c..33716777be9 100644
--- a/plugins/impex/libkra/kis_kra_savexml_visitor.cpp
+++ b/plugins/impex/libkra/kis_kra_savexml_visitor.cpp
@@ -48,6 +48,7 @@
 #include <kis_file_layer.h>
 #include <kis_psd_layer_style.h>
 #include "kis_keyframe_channel.h"
+#include "kis_dom_utils.h"
 
 using namespace KRA;
 
@@ -414,6 +415,16 @@ void KisSaveXmlVisitor::saveMask(QDomElement & el, const QString & maskType, con
         el.setAttribute(COMPOSITE_OP, mask->compositeOpId());
         el.setAttribute(COLORIZE_EDIT_KEYSTROKES, KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool());
         el.setAttribute(COLORIZE_SHOW_COLORING, KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool());
+
+        const KisColorizeMask *colorizeMask = dynamic_cast<const KisColorizeMask*>(mask.data());
+        KIS_SAFE_ASSERT_RECOVER_NOOP(colorizeMask);
+
+        if (colorizeMask) {
+            el.setAttribute(COLORIZE_USE_EDGE_DETECTION, colorizeMask->useEdgeDetection());
+            el.setAttribute(COLORIZE_EDGE_DETECTION_SIZE, KisDomUtils::toString(colorizeMask->edgeDetectionSize()));
+            el.setAttribute(COLORIZE_FUZZY_RADIUS, KisDomUtils::toString(colorizeMask->fuzzyRadius()));
+            el.setAttribute(COLORIZE_CLEANUP, int(100 * colorizeMask->cleanUpAmount()));
+        }
     }
 
     saveNodeKeyframes(mask, filename, el);
diff --git a/plugins/impex/libkra/kis_kra_tags.h b/plugins/impex/libkra/kis_kra_tags.h
index 4335df5547a..4dc80affdf4 100644
--- a/plugins/impex/libkra/kis_kra_tags.h
+++ b/plugins/impex/libkra/kis_kra_tags.h
@@ -100,6 +100,10 @@ const QString COLORIZE_KEYSTROKE_COLOR = "color";
 const QString COLORIZE_KEYSTROKE_IS_TRANSPARENT = "is-transparent";
 const QString COLORIZE_COLORING_DEVICE = "colorize-coloring";
 const QString COLORIZE_KEYSTROKES_SECTION = "keystrokes";
+const QString COLORIZE_USE_EDGE_DETECTION = "use-edge-detection";
+const QString COLORIZE_EDGE_DETECTION_SIZE = "edge-detection-size";
+const QString COLORIZE_FUZZY_RADIUS = "fuzzy-radius";
+const QString COLORIZE_CLEANUP = "cleanup";
 const QString TRANSFORM_MASK = "transformmask";
 const QString UUID = "uuid";
 const QString VISIBLE = "visible";
diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp
index 2cd5dab9c53..d489042fead 100644
--- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp
+++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp
@@ -68,6 +68,39 @@ KisToolLazyBrushOptionsWidget::KisToolLazyBrushOptionsWidget(KisCanvasResourcePr
     m_d->ui->colorView->setAllowModification(false); //people proly shouldn't be able to edit the colorentries themselves.
     m_d->ui->colorView->setCrossedKeyword("transparent");
 
+    connect(m_d->ui->chkUseEdgeDetection, SIGNAL(toggled(bool)), SLOT(slotUseEdgeDetectionChanged(bool)));
+    connect(m_d->ui->intEdgeDetectionSize, SIGNAL(valueChanged(int)), SLOT(slotEdgeDetectionSizeChanged(int)));
+    connect(m_d->ui->intRadius, SIGNAL(valueChanged(int)), SLOT(slotRadiusChanged(int)));
+    connect(m_d->ui->intCleanUp, SIGNAL(valueChanged(int)), SLOT(slotCleanUpChanged(int)));
+
+    m_d->ui->intEdgeDetectionSize->setRange(0, 100);
+    m_d->ui->intEdgeDetectionSize->setExponentRatio(2.0);
+    m_d->ui->intEdgeDetectionSize->setSuffix(i18n(" px"));
+    m_d->ui->intEdgeDetectionSize->setPrefix("Edge detection: ");
+    m_d->ui->intEdgeDetectionSize->setToolTip(
+        i18nc("@info:tooltip",
+              "Activate for images with vast solid areas. "
+              "Set the value to the width of the thinnest "
+              "lines on the image"));
+
+    m_d->ui->intRadius->setRange(0, 1000);
+    m_d->ui->intRadius->setExponentRatio(3.0);
+    m_d->ui->intRadius->setSuffix(i18n(" px"));
+    m_d->ui->intRadius->setPrefix("Gap close hint: ");
+    m_d->ui->intRadius->setToolTip(
+        i18nc("@info:tooltip",
+              "The mask will try to close non-closed contours "
+              "if the gas is smaller than \"Gap close hint\" value"));
+
+    m_d->ui->intCleanUp->setRange(0, 100);
+    m_d->ui->intCleanUp->setSuffix(i18n(" %"));
+    m_d->ui->intCleanUp->setPrefix("Clean up: ");
+    m_d->ui->intCleanUp->setToolTip(
+        i18nc("@info:tooltip",
+              "The mask will try to remove parts of the key strokes "
+              "that are places outside the closed contours. 0% - no effect, 100% - max effect"));
+
+
     connect(m_d->ui->colorView, SIGNAL(indexEntrySelected(QModelIndex)), this, SLOT(entrySelected(QModelIndex)));
     connect(m_d->ui->btnTransparent, SIGNAL(toggled(bool)), this, SLOT(slotMakeTransparent(bool)));
     connect(m_d->ui->btnRemove, SIGNAL(clicked()), this, SLOT(slotRemove()));
@@ -187,10 +220,14 @@ void KisToolLazyBrushOptionsWidget::slotColorLabelsChanged()
 
 void KisToolLazyBrushOptionsWidget::slotUpdateNodeProperties()
 {
-    KisSignalsBlocker b(m_d->ui->chkAutoUpdates,
-                        m_d->ui->btnUpdate,
-                        m_d->ui->chkShowKeyStrokes,
-                        m_d->ui->chkShowOutput);
+    KisSignalsBlocker b1(m_d->ui->chkAutoUpdates,
+                         m_d->ui->btnUpdate,
+                         m_d->ui->chkShowKeyStrokes,
+                         m_d->ui->chkShowOutput);
+    KisSignalsBlocker b2(m_d->ui->chkUseEdgeDetection,
+                         m_d->ui->intEdgeDetectionSize,
+                         m_d->ui->intRadius,
+                         m_d->ui->intCleanUp);
 
     // not implemented yet!
     //m_d->ui->chkAutoUpdates->setEnabled(m_d->activeMask);
@@ -209,6 +246,16 @@ void KisToolLazyBrushOptionsWidget::slotUpdateNodeProperties()
     value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool();
     m_d->ui->chkShowOutput->setEnabled(m_d->activeMask);
     m_d->ui->chkShowOutput->setChecked(value);
+
+    m_d->ui->chkUseEdgeDetection->setEnabled(m_d->activeMask);
+    m_d->ui->chkUseEdgeDetection->setChecked(m_d->activeMask && m_d->activeMask->useEdgeDetection());
+
+    m_d->ui->intEdgeDetectionSize->setEnabled(m_d->activeMask && m_d->ui->chkUseEdgeDetection->isChecked());
+    m_d->ui->intEdgeDetectionSize->setValue(m_d->activeMask ? m_d->activeMask->edgeDetectionSize() : 4.0);
+    m_d->ui->intRadius->setEnabled(m_d->activeMask);
+    m_d->ui->intRadius->setValue(2 * (m_d->activeMask ? m_d->activeMask->fuzzyRadius() : 15));
+    m_d->ui->intCleanUp->setEnabled(m_d->activeMask);
+    m_d->ui->intCleanUp->setValue(100 * (m_d->activeMask ? m_d->activeMask->cleanUpAmount() : 0.7));
 }
 
 void KisToolLazyBrushOptionsWidget::slotCurrentNodeChanged(KisNodeSP node)
@@ -292,3 +339,28 @@ void KisToolLazyBrushOptionsWidget::slotSetShowOutput(bool value)
     KisLayerPropertiesIcons::setNodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, value, m_d->provider->currentImage());
 }
 
+void KisToolLazyBrushOptionsWidget::slotUseEdgeDetectionChanged(bool value)
+{
+    KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
+    m_d->activeMask->setUseEdgeDetection(value);
+    m_d->ui->intEdgeDetectionSize->setEnabled(value);
+}
+
+void KisToolLazyBrushOptionsWidget::slotEdgeDetectionSizeChanged(int value)
+{
+    KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
+    m_d->activeMask->setEdgeDetectionSize(value);
+}
+
+void KisToolLazyBrushOptionsWidget::slotRadiusChanged(int value)
+{
+    KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
+    m_d->activeMask->setFuzzyRadius(0.5 * value);
+}
+
+void KisToolLazyBrushOptionsWidget::slotCleanUpChanged(int value)
+{
+    KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
+    m_d->activeMask->setCleanUpAmount(qreal(value) / 100.0);
+}
+
diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.h b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.h
index 15afeb54c5d..e3b41631491 100644
--- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.h
+++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.h
@@ -50,6 +50,12 @@ private Q_SLOTS:
     void slotSetShowKeyStrokes(bool value);
     void slotSetShowOutput(bool value);
 
+    void slotUseEdgeDetectionChanged(bool value);
+    void slotEdgeDetectionSizeChanged(int value);
+    void slotRadiusChanged(int value);
+    void slotCleanUpChanged(int value);
+
+
     void slotUpdateNodeProperties();
 
 protected:
diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.ui b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.ui
index c09421b1b6b..1e99f1124ea 100644
--- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.ui
+++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.ui
@@ -6,26 +6,11 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>233</width>
-    <height>264</height>
+    <width>342</width>
+    <height>382</height>
    </rect>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,1,1,0,0">
-   <property name="spacing">
-    <number>2</number>
-   </property>
-   <property name="leftMargin">
-    <number>2</number>
-   </property>
-   <property name="topMargin">
-    <number>2</number>
-   </property>
-   <property name="rightMargin">
-    <number>2</number>
-   </property>
-   <property name="bottomMargin">
-    <number>2</number>
-   </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout_2">
      <item>
@@ -77,6 +62,49 @@
      </property>
     </spacer>
    </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <widget class="QCheckBox" name="chkUseEdgeDetection">
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="KisSliderSpinBox" name="intEdgeDetectionSize" native="true">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="KisSliderSpinBox" name="intRadius" native="true"/>
+   </item>
+   <item>
+    <widget class="KisSliderSpinBox" name="intCleanUp" native="true"/>
+   </item>
+   <item>
+    <spacer name="verticalSpacer_2">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Minimum</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>10</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
    <item>
     <widget class="QGroupBox" name="groupBox">
      <property name="title">
@@ -137,6 +165,12 @@
    <extends>QTableView</extends>
    <header location="global">kis_palette_view.h</header>
   </customwidget>
+  <customwidget>
+   <class>KisSliderSpinBox</class>
+   <extends>QWidget</extends>
+   <header location="global">kis_slider_spin_box.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <resources/>
  <connections/>



More information about the kimageshop mailing list