[calligra/krita-chili-kazakov] krita: Fixed antialiasing of auto-brush based brushes

Dmitry Kazakov dimula73 at gmail.com
Wed Aug 20 16:49:44 UTC 2014


Git commit df2f8e03520860308fb0856208eca00acc3d49f6 by Dmitry Kazakov.
Committed on 20/08/2014 at 16:47.
Pushed by dkazakov into branch 'krita-chili-kazakov'.

Fixed antialiasing of auto-brush based brushes

This patch does two changes:

1) Add 1 px fading to every auto brush
2) Implement "Auto" spacing mode. In this mode the spacing is calculated
   using a different formula. Instead of usual sp = k * size, we use
   sp = k * sqrt(size). This formula gives an excellent line quality
   and shows quite nice performance
3) Use qreal's for calculation of the spacing instad of using dab's size.
   Dab's size is rather unstable on sizes around 1.0 - 5.0.

TODO:

1) Port Vc implementation of the auto brush to use a new formula
2) Activate "Auto" spacing mode for all the default presets in Krita
3) Port the other brushes

CCMAIL:kimageshop at kde.org

A  +156  -0    krita/image/kis_antialiasing_fade_maker.h     [License: GPL (v2+)]
M  +9    -30   krita/image/kis_circle_mask_generator.cpp
M  +35   -14   krita/image/kis_curve_circle_mask_generator.cpp
M  +46   -25   krita/image/kis_curve_rect_mask_generator.cpp
M  +27   -4    krita/image/kis_gauss_circle_mask_generator.cpp
M  +27   -3    krita/image/kis_gauss_rect_mask_generator.cpp
M  +3    -2    krita/image/kis_rect_mask_generator.cpp
M  +2    -0    krita/libbrush/kis_auto_brush.cpp
M  +11   -0    krita/libbrush/kis_auto_brush_factory.cpp
M  +25   -0    krita/libbrush/kis_brush.cpp
M  +6    -0    krita/libbrush/kis_brush.h
M  +12   -0    krita/libbrush/kis_qimage_pyramid.cpp
M  +3    -0    krita/libbrush/kis_qimage_pyramid.h
M  +1    -1    krita/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp
M  +1    -2    krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
M  +1    -1    krita/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp
M  +1    -1    krita/plugins/paintops/filterop/kis_filterop.cpp
M  +1    -1    krita/plugins/paintops/hatching/kis_hatching_paintop.cpp
M  +1    -0    krita/plugins/paintops/libpaintop/CMakeLists.txt
M  +61   -64   krita/plugins/paintops/libpaintop/forms/wdgautobrush.ui
M  +13   -15   krita/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp
M  +2    -1    krita/plugins/paintops/libpaintop/kis_auto_brush_widget.h
M  +32   -8    krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp
M  +5    -3    krita/plugins/paintops/libpaintop/kis_brush_based_paintop.h
A  +127  -0    krita/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp     [License: GPL (v2+)]
A  +51   -0    krita/plugins/paintops/libpaintop/kis_spacing_selection_widget.h     [License: GPL (v2+)]

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

diff --git a/krita/image/kis_antialiasing_fade_maker.h b/krita/image/kis_antialiasing_fade_maker.h
new file mode 100644
index 0000000..a248688
--- /dev/null
+++ b/krita/image/kis_antialiasing_fade_maker.h
@@ -0,0 +1,156 @@
+/*
+ *  Copyright (c) 2014 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_ANTIALIASING_FADE_MAKER_H
+#define __KIS_ANTIALIASING_FADE_MAKER_H
+
+
+template <class BaseFade>
+class KisAntialiasingFadeMaker1D
+{
+public:
+    KisAntialiasingFadeMaker1D(const BaseFade &baseFade)
+        : m_radius(0.0),
+          m_fadeStartValue(0),
+          m_antialiasingFadeStart(0),
+          m_antialiasingFadeCoeff(0),
+          m_baseFade(baseFade)
+    {
+    }
+
+    void setSquareNormCoeffs(qreal xcoeff, qreal ycoeff) {
+        m_radius = 1.0;
+
+        qreal xf = qMax(0.0, ((1.0 / xcoeff) - 1.0) * xcoeff);
+        qreal yf = qMax(0.0, ((1.0 / ycoeff) - 1.0) * ycoeff);
+
+        m_antialiasingFadeStart = pow2(0.5 * (xf + yf));
+
+        m_fadeStartValue = m_baseFade.value(m_antialiasingFadeStart);
+        m_antialiasingFadeCoeff = qMax(0.0, 255.0 - m_fadeStartValue) / (m_radius - m_antialiasingFadeStart);
+    }
+
+    void setRadius(qreal radius) {
+        m_radius = radius;
+        m_antialiasingFadeStart = qMax(0.0, m_radius - 1.0);
+
+        m_fadeStartValue = m_baseFade.value(m_antialiasingFadeStart);
+        m_antialiasingFadeCoeff = qMax(0.0, 255.0 - m_fadeStartValue) / (m_radius - m_antialiasingFadeStart);
+    }
+
+    inline bool needFade(qreal dist, quint8 *value) {
+        if (dist > m_radius) {
+            *value = 255;
+            return true;
+        }
+
+        if (dist > m_antialiasingFadeStart) {
+            *value = m_fadeStartValue + (dist - m_antialiasingFadeStart) * m_antialiasingFadeCoeff;
+            return true;
+        }
+
+        return false;
+    }
+
+private:
+    qreal m_radius;
+    quint8 m_fadeStartValue;
+    qreal m_antialiasingFadeStart;
+    qreal m_antialiasingFadeCoeff;
+    const BaseFade &m_baseFade;
+};
+
+template <class BaseFade>
+class KisAntialiasingFadeMaker2D
+{
+public:
+    KisAntialiasingFadeMaker2D(const BaseFade &baseFade)
+        : m_xLimit(0),
+          m_yLimit(0),
+          m_xFadeLimitStart(0),
+          m_yFadeLimitStart(0),
+          m_xFadeCoeff(0),
+          m_yFadeCoeff(0),
+          m_baseFade(baseFade)
+    {
+    }
+
+    void setLimits(qreal halfWidth, qreal halfHeight) {
+        m_xLimit = halfWidth;
+        m_yLimit = halfHeight;
+
+        m_xFadeLimitStart = m_xLimit - 1.0;
+        m_yFadeLimitStart = m_yLimit - 1.0;
+
+        m_xFadeCoeff = 1.0 / (m_xLimit - m_xFadeLimitStart);
+        m_yFadeCoeff = 1.0 / (m_yLimit - m_yFadeLimitStart);
+    }
+
+    inline bool needFade(qreal x, qreal y, quint8 *value) {
+        x = qAbs(x);
+        y = qAbs(y);
+
+        if (x > m_xLimit) {
+            *value = 255;
+            return true;
+        }
+
+        if (y > m_yLimit) {
+            *value = 255;
+            return true;
+        }
+
+        if (x > m_xFadeLimitStart) {
+            quint8 baseValue = m_baseFade.value(x, y);
+            *value = baseValue + (255.0 - baseValue) * (x - m_xFadeLimitStart) * m_xFadeCoeff;
+
+            if (y > m_yFadeLimitStart && *value < 255) {
+                *value += (255.0 - *value) * (y - m_yFadeLimitStart) * m_yFadeCoeff;
+            }
+
+            return true;
+        }
+
+        if (y > m_yFadeLimitStart) {
+            quint8 baseValue = m_baseFade.value(x, y);
+            *value = baseValue + (255.0 - baseValue) * (y - m_yFadeLimitStart) * m_yFadeCoeff;
+
+            if (x > m_xFadeLimitStart && *value < 255) {
+                *value += (255.0 - *value) * (x - m_xFadeLimitStart) * m_xFadeCoeff;
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+private:
+    qreal m_xLimit;
+    qreal m_yLimit;
+
+    qreal m_xFadeLimitStart;
+    qreal m_yFadeLimitStart;
+
+    qreal m_xFadeCoeff;
+    qreal m_yFadeCoeff;
+
+    const BaseFade &m_baseFade;
+};
+
+#endif /* __KIS_ANTIALIASING_FADE_MAKER_H */
diff --git a/krita/image/kis_circle_mask_generator.cpp b/krita/image/kis_circle_mask_generator.cpp
index d670f8a..9e09e47 100644
--- a/krita/image/kis_circle_mask_generator.cpp
+++ b/krita/image/kis_circle_mask_generator.cpp
@@ -93,36 +93,15 @@ quint8 KisCircleMaskGenerator::valueAt(qreal x, qreal y) const
         }
     }
 
-    double n = norme(xr * d->xcoef, yr * d->ycoef);
-
-    if (n > 1) {
-        return 255;
-    } else {
-        double normeFade = norme(xr * d->transformedFadeX, yr * d->transformedFadeY);
-        if (normeFade > 1) {
-            // xle stands for x-coordinate limit exterior
-            // yle stands for y-coordinate limit exterior
-            // we are computing the coordinate on the external ellipse in order to compute
-            // the fade value
-            // xle = xr / sqrt(norme(xr * d->xcoef, yr * d->ycoef))
-            // yle = yr / sqrt(norme(xr * d->xcoef, yr * d->ycoef))
-
-            // On the internal limit of the fade area, normeFade is equal to 1
-
-            // normeFadeLimitE = norme(xle * transformedFadeX, yle * transformedFadeY)
-            // return (uchar)(255 *(normeFade - 1) / (normeFadeLimitE - 1));
-            return (uchar)(255 * n * (normeFade - 1) / (normeFade - n));
-            // if n == 0, the conversion of NaN to uchar will correctly result in zero
-        } else {
-            n = 1 - n;
-            if( width() < 2 || height() < 2 || n > d->xcoef * 0.5 || n > d->ycoef * 0.5)
-            {
-              return 0;
-            } else {
-              return 255 *  ( 1 - 4 * n * n  / (d->xcoef * d->ycoef) );
-            }
-        }
-    }
+    qreal n = norme(xr * d->xcoef, yr * d->ycoef);
+    if (n > 1.0) return 255;
+
+    // we add +1.0 to ensure correct antialising on the border
+    qreal nf = norme((qAbs(xr) + 1.0) * d->transformedFadeX,
+                     (qAbs(yr) + 1.0) * d->transformedFadeY);
+
+    if (nf < 1.0) return 0;
+    return 255 * n * (nf - 1.0) / (nf - n);
 }
 
 void KisCircleMaskGenerator::toXML(QDomDocument& d, QDomElement& e) const
diff --git a/krita/image/kis_curve_circle_mask_generator.cpp b/krita/image/kis_curve_circle_mask_generator.cpp
index 66ac4ec..a32ec75 100644
--- a/krita/image/kis_curve_circle_mask_generator.cpp
+++ b/krita/image/kis_curve_circle_mask_generator.cpp
@@ -29,13 +29,25 @@
 #include "kis_base_mask_generator.h"
 #include "kis_curve_circle_mask_generator.h"
 #include "kis_cubic_curve.h"
+#include "kis_antialiasing_fade_maker.h"
+
+
+
+struct KisCurveCircleMaskGenerator::Private
+{
+    Private()
+        : fadeMaker(*this)
+    {
+    }
 
-struct KisCurveCircleMaskGenerator::Private {
     qreal xcoef, ycoef;
     qreal curveResolution;
     QVector<qreal> curveData;
     QList<QPointF> curvePoints;
     bool dirty;
+
+    KisAntialiasingFadeMaker1D<Private> fadeMaker;
+    inline quint8 value(qreal dist) const;
 };
 
 KisCurveCircleMaskGenerator::KisCurveCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, const KisCubicCurve &curve)
@@ -48,6 +60,8 @@ KisCurveCircleMaskGenerator::KisCurveCircleMaskGenerator(qreal diameter, qreal r
     d->curvePoints = curve.points();
     d->dirty = false;
     setCurveString(curve.toString());
+
+    d->fadeMaker.setSquareNormCoeffs(d->xcoef, d->ycoef);
 }
 
 KisCurveCircleMaskGenerator::~KisCurveCircleMaskGenerator()
@@ -55,6 +69,19 @@ KisCurveCircleMaskGenerator::~KisCurveCircleMaskGenerator()
     delete d;
 }
 
+inline quint8 KisCurveCircleMaskGenerator::Private::value(qreal dist) const
+{
+    qreal distance = dist * curveResolution;
+
+    quint16 alphaValue = distance;
+    qreal alphaValueF = distance - alphaValue;
+
+    qreal alpha = (
+        (1.0 - alphaValueF) * curveData.at(alphaValue) +
+        alphaValueF * curveData.at(alphaValue+1));
+    return (1.0 - alpha) * 255;
+}
+
 quint8 KisCurveCircleMaskGenerator::valueAt(qreal x, qreal y) const
 {
     qreal xr = x;
@@ -73,19 +100,13 @@ quint8 KisCurveCircleMaskGenerator::valueAt(qreal x, qreal y) const
     }
 
     qreal dist = norme(xr * d->xcoef, yr * d->ycoef);
-    if (dist <= 1.0){
-        qreal distance = dist * d->curveResolution;
-    
-        quint16 alphaValue = distance;
-        qreal alphaValueF = distance - alphaValue;
-        
-        
-        qreal alpha = (
-            (1.0 - alphaValueF) * d->curveData.at(alphaValue) + 
-                    alphaValueF * d->curveData.at(alphaValue+1));
-        return (1.0 - alpha) * 255;
-    }            
-    return 255;
+
+    quint8 value;
+    if (d->fadeMaker.needFade(dist, &value)) {
+        return value;
+    }
+
+    return d->value(dist);
 }
 
 void KisCurveCircleMaskGenerator::toXML(QDomDocument& doc, QDomElement& e) const
diff --git a/krita/image/kis_curve_rect_mask_generator.cpp b/krita/image/kis_curve_rect_mask_generator.cpp
index c0b7821..c2ba736 100644
--- a/krita/image/kis_curve_rect_mask_generator.cpp
+++ b/krita/image/kis_curve_rect_mask_generator.cpp
@@ -25,26 +25,45 @@
 #include <kis_fast_math.h>
 #include "kis_curve_rect_mask_generator.h"
 #include "kis_cubic_curve.h"
+#include "kis_antialiasing_fade_maker.h"
+
+
+struct KisCurveRectangleMaskGenerator::Private
+{
+    Private()
+        : fadeMaker(*this)
+    {
+    }
 
-struct KisCurveRectangleMaskGenerator::Private {
     QVector<qreal> curveData;
     QList<QPointF> curvePoints;
     int curveResolution;
     bool dirty;
-    qreal m_halfWidth, m_halfHeight;
+
+    qreal xcoeff;
+    qreal ycoeff;
+
+    KisAntialiasingFadeMaker2D<Private> fadeMaker;
+
+    quint8 value(qreal xr, qreal yr) const;
 };
 
 KisCurveRectangleMaskGenerator::KisCurveRectangleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, const KisCubicCurve &curve)
         : KisMaskGenerator(diameter, ratio, fh, fv, spikes, RECTANGLE, SoftId), d(new Private)
 {
     d->curveResolution = qRound( qMax(width(),height()) * OVERSAMPLING);
-    d->curveData = curve.floatTransfer( d->curveResolution + 1); 
+    d->curveData = curve.floatTransfer( d->curveResolution + 1);
     d->curvePoints = curve.points();
     setCurveString(curve.toString());
     d->dirty = false;
-    d->m_halfWidth = KisMaskGenerator::d->diameter * 0.5;
-    d->m_halfHeight = d->m_halfWidth * KisMaskGenerator::d->ratio;
 
+    qreal halfWidth = 0.5 * width();
+    qreal halfHeight = 0.5 * height();
+
+    d->xcoeff = 1.0 / halfWidth;
+    d->ycoeff = 1.0 / halfHeight;
+
+    d->fadeMaker.setLimits(halfWidth, halfHeight);
 }
 
 KisCurveRectangleMaskGenerator::~KisCurveRectangleMaskGenerator()
@@ -52,6 +71,23 @@ KisCurveRectangleMaskGenerator::~KisCurveRectangleMaskGenerator()
     delete d;
 }
 
+quint8 KisCurveRectangleMaskGenerator::Private::value(qreal xr, qreal yr) const
+{
+    xr = qAbs(xr) * xcoeff;
+    yr = qAbs(yr) * ycoeff;
+
+    int sIndex = qRound(xr * (curveResolution));
+    int tIndex = qRound(yr * (curveResolution));
+
+    int sIndexInverted = curveResolution - sIndex;
+    int tIndexInverted = curveResolution - tIndex;
+
+    qreal blend = (curveData.at(sIndex) * (1.0 - curveData.at(sIndexInverted)) *
+                   curveData.at(tIndex) * (1.0 - curveData.at(tIndexInverted)));
+
+    return (1.0 - blend) * 255;
+}
+
 quint8 KisCurveRectangleMaskGenerator::valueAt(qreal x, qreal y) const
 {
 
@@ -76,27 +112,12 @@ quint8 KisCurveRectangleMaskGenerator::valueAt(qreal x, qreal y) const
         }
     }
 
-    if(xr > d->m_halfWidth || xr < -d->m_halfWidth || yr > d->m_halfHeight || yr < -d->m_halfHeight) {
-        return 255;
-    }
-    
-    xr = qAbs(xr) / width();
-    yr = qAbs(yr) / height();
-    
-    if (xr > 1.0 || yr > 1.0){
-        return 255;
+    quint8 value;
+    if (d->fadeMaker.needFade(xr, yr, &value)) {
+        return value;
     }
-    
-    int sIndex = qRound(xr * (d->curveResolution));
-    int tIndex = qRound(yr * (d->curveResolution));
-    
-    int sIndexInverted = d->curveResolution - sIndex;
-    int tIndexInverted = d->curveResolution - tIndex;
-    
-    qreal blend = (d->curveData.at(sIndex) * (1.0 - d->curveData.at(sIndexInverted)) *
-                  d->curveData.at(tIndex) * (1.0 - d->curveData.at(tIndexInverted)));
-    
-    return (1.0 - blend) * 255;
+
+    return d->value(xr, yr);
 }
 
 void KisCurveRectangleMaskGenerator::toXML(QDomDocument& doc, QDomElement& e) const
diff --git a/krita/image/kis_gauss_circle_mask_generator.cpp b/krita/image/kis_gauss_circle_mask_generator.cpp
index 54d0778..3895cce 100644
--- a/krita/image/kis_gauss_circle_mask_generator.cpp
+++ b/krita/image/kis_gauss_circle_mask_generator.cpp
@@ -29,6 +29,7 @@
 
 #include "kis_base_mask_generator.h"
 #include "kis_gauss_circle_mask_generator.h"
+#include "kis_antialiasing_fade_maker.h"
 
 #define M_SQRT_2 1.41421356237309504880
 
@@ -39,9 +40,18 @@
 #endif
 
 
-struct KisGaussCircleMaskGenerator::Private {
+struct KisGaussCircleMaskGenerator::Private
+{
+    Private()
+        : fadeMaker(*this)
+    {
+    }
+
     qreal ycoef;
     qreal center, distfactor, alphafactor;
+    KisAntialiasingFadeMaker1D<Private> fadeMaker;
+
+    inline quint8 value(qreal dist) const;
 };
 
 KisGaussCircleMaskGenerator::KisGaussCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes)
@@ -54,6 +64,8 @@ KisGaussCircleMaskGenerator::KisGaussCircleMaskGenerator(qreal diameter, qreal r
     d->center = (2.5 * (6761.0*fade-10000.0))/(M_SQRT_2*6761.0*fade);
     d->alphafactor = 255.0 / (2.0 * erf(d->center));
     d->distfactor = M_SQRT_2 * 12500.0 / (6761.0 * fade * diameter / 2.0);
+
+    d->fadeMaker.setRadius(0.5 * diameter);
 }
 
 KisGaussCircleMaskGenerator::~KisGaussCircleMaskGenerator()
@@ -61,6 +73,13 @@ KisGaussCircleMaskGenerator::~KisGaussCircleMaskGenerator()
     delete d;
 }
 
+inline quint8 KisGaussCircleMaskGenerator::Private::value(qreal dist) const
+{
+    dist *= distfactor;
+    quint8 ret = alphafactor * (erf(dist + center) - erf(dist - center));
+    return (quint8) 255 - ret;
+}
+
 quint8 KisGaussCircleMaskGenerator::valueAt(qreal x, qreal y) const
 {
     qreal xr = x;
@@ -79,9 +98,13 @@ quint8 KisGaussCircleMaskGenerator::valueAt(qreal x, qreal y) const
     }
 
     qreal dist = sqrt(norme(xr, yr * d->ycoef));
-    dist *= d->distfactor;
-    quint8 ret = d->alphafactor * (erf(dist + d->center) - erf(dist - d->center));
-    return (quint8) 255 - ret;
+
+    quint8 value;
+    if (d->fadeMaker.needFade(dist, &value)) {
+        return value;
+    }
+
+    return d->value(dist);
 }
 
 void KisGaussCircleMaskGenerator::toXML(QDomDocument& doc, QDomElement& e) const
diff --git a/krita/image/kis_gauss_rect_mask_generator.cpp b/krita/image/kis_gauss_rect_mask_generator.cpp
index 8de01d1..2358ffe 100644
--- a/krita/image/kis_gauss_rect_mask_generator.cpp
+++ b/krita/image/kis_gauss_rect_mask_generator.cpp
@@ -30,6 +30,7 @@
 
 #include "kis_base_mask_generator.h"
 #include "kis_gauss_rect_mask_generator.h"
+#include "kis_antialiasing_fade_maker.h"
 
 #define M_SQRT_2 1.41421356237309504880
 
@@ -39,10 +40,20 @@
 #define erf(x) boost::math::erf(x)
 #endif
 
-struct KisGaussRectangleMaskGenerator::Private {
+struct KisGaussRectangleMaskGenerator::Private
+{
+    Private()
+        : fadeMaker(*this)
+    {
+    }
+
     qreal xfade, yfade;
     qreal halfWidth, halfHeight;
     qreal alphafactor;
+
+    KisAntialiasingFadeMaker2D <Private> fadeMaker;
+
+    inline quint8 value(qreal x, qreal y) const;
 };
 
 KisGaussRectangleMaskGenerator::KisGaussRectangleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes)
@@ -55,6 +66,8 @@ KisGaussRectangleMaskGenerator::KisGaussRectangleMaskGenerator(qreal diameter, q
     d->halfWidth = width() * 0.5 - 2.5 * xfade;
     d->halfHeight = height() * 0.5 - 2.5 * yfade;
     d->alphafactor = 255.0 / (4.0 * erf(d->halfWidth * d->xfade) * erf(d->halfHeight * d->yfade));
+
+    d->fadeMaker.setLimits(0.5 * width(), 0.5 * height());
 }
 
 KisGaussRectangleMaskGenerator::~KisGaussRectangleMaskGenerator()
@@ -62,6 +75,12 @@ KisGaussRectangleMaskGenerator::~KisGaussRectangleMaskGenerator()
     delete d;
 }
 
+inline quint8 KisGaussRectangleMaskGenerator::Private::value(qreal xr, qreal yr) const
+{
+    return (quint8) 255 - (quint8) (alphafactor * (erf((halfWidth + xr) * xfade) + erf((halfWidth - xr) * xfade))
+                                    * (erf((halfHeight + yr) * yfade) + erf((halfHeight - yr) * yfade)));
+}
+
 quint8 KisGaussRectangleMaskGenerator::valueAt(qreal x, qreal y) const
 {
     qreal xr = x;
@@ -78,8 +97,13 @@ quint8 KisGaussRectangleMaskGenerator::valueAt(qreal x, qreal y) const
             angle -= 2 * KisMaskGenerator::d->cachedSpikesAngle;
         }
     }
-    return (quint8) 255 - (quint8) (d->alphafactor * (erf((d->halfWidth + xr) * d->xfade) + erf((d->halfWidth - xr) * d->xfade))
-                                                  * (erf((d->halfHeight + yr) * d->yfade) + erf((d->halfHeight - yr) * d->yfade)));
+
+    quint8 value;
+    if (d->fadeMaker.needFade(xr, yr, &value)) {
+        return value;
+    }
+
+    return d->value(xr, yr);
 }
 
 void KisGaussRectangleMaskGenerator::toXML(QDomDocument& doc, QDomElement& e) const
diff --git a/krita/image/kis_rect_mask_generator.cpp b/krita/image/kis_rect_mask_generator.cpp
index 6f191be..2596d9f 100644
--- a/krita/image/kis_rect_mask_generator.cpp
+++ b/krita/image/kis_rect_mask_generator.cpp
@@ -88,8 +88,9 @@ quint8 KisRectangleMaskGenerator::valueAt(qreal x, qreal y) const
     xr /= width();
     yr /= height();
 
-    qreal fhTransformed = KisMaskGenerator::d->fh * softness();
-    qreal fvTransformed = KisMaskGenerator::d->fv * softness();
+    // add -1.0 to ensure the last pixel is antialiased
+    qreal fhTransformed = qMax(0.0, KisMaskGenerator::d->fh * softness() - 1.0 / width());
+    qreal fvTransformed = qMax(0.0, KisMaskGenerator::d->fv * softness() - 1.0 / height());
 
     if( xr > fhTransformed )
     {
diff --git a/krita/libbrush/kis_auto_brush.cpp b/krita/libbrush/kis_auto_brush.cpp
index 81637b9..5f70748 100644
--- a/krita/libbrush/kis_auto_brush.cpp
+++ b/krita/libbrush/kis_auto_brush.cpp
@@ -277,6 +277,8 @@ void KisAutoBrush::toXML(QDomDocument& doc, QDomElement& e) const
     e.appendChild(shapeElt);
     e.setAttribute("type", "auto_brush");
     e.setAttribute("spacing", QString::number(spacing()));
+    e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive()));
+    e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff()));
     e.setAttribute("angle", QString::number(KisBrush::angle()));
     e.setAttribute("randomness", QString::number(d->randomness));
     e.setAttribute("density", QString::number(d->density));
diff --git a/krita/libbrush/kis_auto_brush_factory.cpp b/krita/libbrush/kis_auto_brush_factory.cpp
index ffa0f31..2f544f1 100644
--- a/krita/libbrush/kis_auto_brush_factory.cpp
+++ b/krita/libbrush/kis_auto_brush_factory.cpp
@@ -50,7 +50,18 @@ KisBrushSP KisAutoBrushFactory::getOrCreateBrush(const QDomElement& brushDefinit
         spacing = c.toDouble(brushDefinition.attribute("spacing"));
     }
 
+    bool useAutoSpacing = brushDefinition.attribute("useAutoSpacing", "0").toInt(&result);
+    if (!result) {
+        useAutoSpacing = c.toInt(brushDefinition.attribute("useAutoSpacing"));
+    }
+
+    qreal autoSpacingCoeff = brushDefinition.attribute("autoSpacingCoeff", "1.0").toDouble(&result);
+    if (!result) {
+        autoSpacingCoeff = c.toDouble(brushDefinition.attribute("autoSpacingCoeff"));
+    }
+
     KisBrushSP brush = new KisAutoBrush(mask, angle, randomness, density);
     brush->setSpacing(spacing);
+    brush->setAutoSpacing(useAutoSpacing, autoSpacingCoeff);
     return brush;
 }
diff --git a/krita/libbrush/kis_brush.cpp b/krita/libbrush/kis_brush.cpp
index 5a8050c..7677565 100644
--- a/krita/libbrush/kis_brush.cpp
+++ b/krita/libbrush/kis_brush.cpp
@@ -107,6 +107,8 @@ struct KisBrush::Private {
         , hasColor(false)
         , brushType(INVALID)
         , brushPyramid(0)
+        , autoSpacingActive(false)
+        , autoSpacingCoeff(1.0)
     {}
 
     ~Private() {
@@ -127,6 +129,9 @@ struct KisBrush::Private {
     mutable KisQImagePyramid *brushPyramid;
 
     QImage brushTipImage;
+
+    bool autoSpacingActive;
+    qreal autoSpacingCoeff;
 };
 
 KisBrush::KisBrush()
@@ -155,6 +160,8 @@ KisBrush::KisBrush(const KisBrush& rhs)
     d->hasColor = rhs.d->hasColor;
     d->angle = rhs.d->angle;
     d->scale = rhs.d->scale;
+    d->autoSpacingActive = d->autoSpacingActive;
+    d->autoSpacingCoeff = d->autoSpacingCoeff;
     setFilename(rhs.filename());
     clearBrushPyramid();
     // don't copy the boundary, it will be regenerated -- see bug 291910
@@ -286,6 +293,8 @@ void KisBrush::predefinedBrushToXML(const QString &type, QDomElement& e) const
     e.setAttribute("type", type);
     e.setAttribute("filename", shortFilename());
     e.setAttribute("spacing", QString::number(spacing()));
+    e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive()));
+    e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff()));
     e.setAttribute("angle", QString::number(angle()));
     e.setAttribute("scale", QString::number(scale()));
 }
@@ -380,6 +389,22 @@ double KisBrush::spacing() const
     return d->spacing;
 }
 
+void KisBrush::setAutoSpacing(bool active, qreal coeff)
+{
+    d->autoSpacingCoeff = coeff;
+    d->autoSpacingActive = active;
+}
+
+bool KisBrush::autoSpacingActive() const
+{
+    return d->autoSpacingActive;
+}
+
+qreal KisBrush::autoSpacingCoeff() const
+{
+    return d->autoSpacingCoeff;
+}
+
 void KisBrush::notifyCachedDabPainted()
 {
 }
diff --git a/krita/libbrush/kis_brush.h b/krita/libbrush/kis_brush.h
index 8c07b68..88ee73a 100644
--- a/krita/libbrush/kis_brush.h
+++ b/krita/libbrush/kis_brush.h
@@ -159,6 +159,12 @@ public:
      */
     double spacing() const;
 
+    void setAutoSpacing(bool active, qreal coeff);
+
+    bool autoSpacingActive() const;
+    qreal autoSpacingCoeff() const;
+
+
     /**
      * @return the width (for scale == 1.0)
      */
diff --git a/krita/libbrush/kis_qimage_pyramid.cpp b/krita/libbrush/kis_qimage_pyramid.cpp
index de28116..2811ff4 100644
--- a/krita/libbrush/kis_qimage_pyramid.cpp
+++ b/krita/libbrush/kis_qimage_pyramid.cpp
@@ -218,6 +218,18 @@ QSize KisQImagePyramid::imageSize(const QSize &originalSize,
     return dstSize;
 }
 
+QSizeF KisQImagePyramid::characteristicSize(const QSize &originalSize,
+                                            qreal scale, qreal rotation)
+{
+    QRectF originalRect(QPointF(), originalSize);
+    QTransform transform = baseBrushTransform(scale, scale,
+                                              rotation,
+                                              0.0, 0.0,
+                                              originalRect);
+
+    return transform.mapRect(originalRect).size();
+}
+
 void KisQImagePyramid::appendPyramidLevel(const QImage &image)
 {
     /**
diff --git a/krita/libbrush/kis_qimage_pyramid.h b/krita/libbrush/kis_qimage_pyramid.h
index e730624..1835102 100644
--- a/krita/libbrush/kis_qimage_pyramid.h
+++ b/krita/libbrush/kis_qimage_pyramid.h
@@ -34,6 +34,9 @@ public:
                            qreal scale, qreal rotation,
                            qreal subPixelX, qreal subPixelY);
 
+    static QSizeF characteristicSize(const QSize &originalSize,
+                                     qreal scale, qreal rotation);
+
     QImage createImage(qreal scale, qreal rotation,
                        qreal subPixelX, qreal subPixelY);
 
diff --git a/krita/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp b/krita/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp
index be5d767..67735a0 100644
--- a/krita/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp
+++ b/krita/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp
@@ -174,7 +174,7 @@ KisSpacingInformation KisColorSmudgeOp::paintAt(const KisPaintInformation& info)
     m_lastPaintPos = QRectF(m_dstDabRect).center();
 
     KisSpacingInformation spacingInfo =
-        effectiveSpacing(m_dstDabRect.width(), m_dstDabRect.height(),
+        effectiveSpacing(scale, rotation,
                          m_spacingOption, info);
 
     if (m_firstRun) {
diff --git a/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
index b5b0c07..15ffeea 100644
--- a/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
+++ b/krita/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp
@@ -157,8 +157,7 @@ KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info)
     painter()->setOpacity(origOpacity);
     painter()->setFlow(origFlow);
 
-    return effectiveSpacing(dab->bounds().width(),
-                            dab->bounds().height(),
+    return effectiveSpacing(scale, rotation,
                             m_spacingOption, info);
 }
 
diff --git a/krita/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp b/krita/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp
index 1b2c410..9e85f37 100644
--- a/krita/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp
+++ b/krita/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp
@@ -291,5 +291,5 @@ KisSpacingInformation KisDuplicateOp::paintAt(const KisPaintInformation& info)
     painter()->renderMirrorMaskSafe(dstRect, m_srcdev, 0, 0, dab,
                                     !m_dabCache->needSeparateOriginal());
 
-    return effectiveSpacing(dstRect.width(), dstRect.height());
+    return effectiveSpacing(scale, 0.0);
 }
diff --git a/krita/plugins/paintops/filterop/kis_filterop.cpp b/krita/plugins/paintops/filterop/kis_filterop.cpp
index 02447a8..250a56f 100644
--- a/krita/plugins/paintops/filterop/kis_filterop.cpp
+++ b/krita/plugins/paintops/filterop/kis_filterop.cpp
@@ -139,5 +139,5 @@ KisSpacingInformation KisFilterOp::paintAt(const KisPaintInformation& info)
     painter()->renderMirrorMaskSafe(dstRect, m_tmpDevice, 0, 0, dab,
                                     !m_dabCache->needSeparateOriginal());
 
-    return effectiveSpacing(dabRect.width(), dabRect.height());
+    return effectiveSpacing(scale, rotation);
 }
diff --git a/krita/plugins/paintops/hatching/kis_hatching_paintop.cpp b/krita/plugins/paintops/hatching/kis_hatching_paintop.cpp
index 9de909f..d174bb0 100644
--- a/krita/plugins/paintops/hatching/kis_hatching_paintop.cpp
+++ b/krita/plugins/paintops/hatching/kis_hatching_paintop.cpp
@@ -175,7 +175,7 @@ KisSpacingInformation KisHatchingPaintOp::paintAt(const KisPaintInformation& inf
                                     !m_dabCache->needSeparateOriginal());
     painter()->setOpacity(origOpacity);
 
-    return effectiveSpacing(sw, sh);
+    return effectiveSpacing(scale, 0.0);
 }
 
 double KisHatchingPaintOp::spinAngle(double spin)
diff --git a/krita/plugins/paintops/libpaintop/CMakeLists.txt b/krita/plugins/paintops/libpaintop/CMakeLists.txt
index 2ac296d..7c66392 100644
--- a/krita/plugins/paintops/libpaintop/CMakeLists.txt
+++ b/krita/plugins/paintops/libpaintop/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(kritalibpaintop_LIB_SRCS
     kis_airbrush_option.cpp
     kis_auto_brush_widget.cpp
+    kis_spacing_selection_widget.cpp
     kis_bidirectional_mixing_option.cpp
     kis_bidirectional_mixing_option_widget.cpp
     kis_brush_based_paintop.cpp
diff --git a/krita/plugins/paintops/libpaintop/forms/wdgautobrush.ui b/krita/plugins/paintops/libpaintop/forms/wdgautobrush.ui
index 1bf4a05..8fb63d1 100644
--- a/krita/plugins/paintops/libpaintop/forms/wdgautobrush.ui
+++ b/krita/plugins/paintops/libpaintop/forms/wdgautobrush.ui
@@ -17,16 +17,7 @@
    </size>
   </property>
   <layout class="QGridLayout" name="gridLayout_3" columnstretch="0,1">
-   <property name="leftMargin">
-    <number>0</number>
-   </property>
-   <property name="topMargin">
-    <number>0</number>
-   </property>
-   <property name="rightMargin">
-    <number>0</number>
-   </property>
-   <property name="bottomMargin">
+   <property name="margin">
     <number>0</number>
    </property>
    <item row="0" column="0">
@@ -114,18 +105,24 @@
      </item>
     </layout>
    </item>
+   <item row="1" column="1">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Expanding</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>17</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
    <item row="0" column="1">
     <layout class="QGridLayout" name="gridLayout_2" columnstretch="0,0,1">
-     <item row="0" column="0">
-      <widget class="QLabel" name="label_4">
-       <property name="text">
-        <string>Diameter:</string>
-       </property>
-       <property name="alignment">
-        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-       </property>
-      </widget>
-     </item>
      <item row="0" column="1" colspan="2">
       <widget class="KisMultipliersDoubleSliderSpinBox" name="inputRadius" native="true">
        <property name="minimumSize">
@@ -136,6 +133,26 @@
        </property>
       </widget>
      </item>
+     <item row="3" column="0">
+      <widget class="QLabel" name="label_3">
+       <property name="text">
+        <string>Angle:</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0">
+      <widget class="QLabel" name="label_4">
+       <property name="text">
+        <string>Diameter:</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+      </widget>
+     </item>
      <item row="1" column="0">
       <widget class="QLabel" name="label_5">
        <property name="text">
@@ -236,26 +253,16 @@
        </widget>
       </widget>
      </item>
-     <item row="3" column="0">
-      <widget class="QLabel" name="label_3">
+     <item row="5" column="0">
+      <widget class="QLabel" name="label_randomness">
        <property name="text">
-        <string>Angle:</string>
+        <string>Randomness:</string>
        </property>
        <property name="alignment">
         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
        </property>
       </widget>
      </item>
-     <item row="3" column="1" colspan="2">
-      <widget class="KisSliderSpinBox" name="inputAngle" native="true">
-       <property name="minimumSize">
-        <size>
-         <width>200</width>
-         <height>0</height>
-        </size>
-       </property>
-      </widget>
-     </item>
      <item row="4" column="0">
       <widget class="QLabel" name="label">
        <property name="text">
@@ -266,18 +273,21 @@
        </property>
       </widget>
      </item>
+     <item row="3" column="1" colspan="2">
+      <widget class="KisSliderSpinBox" name="inputAngle" native="true">
+       <property name="minimumSize">
+        <size>
+         <width>200</width>
+         <height>0</height>
+        </size>
+       </property>
+      </widget>
+     </item>
      <item row="4" column="1" colspan="2">
       <widget class="KisSliderSpinBox" name="inputSpikes" native="true"/>
      </item>
-     <item row="5" column="0">
-      <widget class="QLabel" name="label_randomness">
-       <property name="text">
-        <string>Randomness:</string>
-       </property>
-       <property name="alignment">
-        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-       </property>
-      </widget>
+     <item row="6" column="1" colspan="2">
+      <widget class="KisDoubleSliderSpinBox" name="density" native="true"/>
      </item>
      <item row="5" column="1" colspan="2">
       <widget class="KisDoubleSliderSpinBox" name="inputRandomness" native="true"/>
@@ -292,11 +302,11 @@
        </property>
       </widget>
      </item>
-     <item row="6" column="1" colspan="2">
-      <widget class="KisDoubleSliderSpinBox" name="density" native="true"/>
+     <item row="7" column="1" colspan="2">
+      <widget class="KisSpacingSelectionWidget" name="spacingWidget" native="true"/>
      </item>
      <item row="7" column="0">
-      <widget class="QLabel" name="label_2">
+      <widget class="QLabel" name="label_11">
        <property name="text">
         <string>Spacing:</string>
        </property>
@@ -305,27 +315,8 @@
        </property>
       </widget>
      </item>
-     <item row="7" column="1" colspan="2">
-      <widget class="KisDoubleSliderSpinBox" name="inputSpacing" native="true"/>
-     </item>
     </layout>
    </item>
-   <item row="1" column="1">
-    <spacer name="verticalSpacer">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeType">
-      <enum>QSizePolicy::Expanding</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>17</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
   </layout>
  </widget>
  <customwidgets>
@@ -359,6 +350,12 @@
    <header>kis_multipliers_double_slider_spinbox.h</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>KisSpacingSelectionWidget</class>
+   <extends>QWidget</extends>
+   <header>kis_spacing_selection_widget.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  <resources/>
  <connections/>
diff --git a/krita/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp b/krita/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp
index 4a1a232..418bf5b 100644
--- a/krita/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp
+++ b/krita/plugins/paintops/libpaintop/kis_auto_brush_widget.cpp
@@ -83,10 +83,7 @@ KisAutoBrushWidget::KisAutoBrushWidget(QWidget *parent, const char* name)
     inputAngle->setValue(0);
     connect(inputAngle, SIGNAL(valueChanged(int)), this, SLOT(spinBoxAngleChanged(int)));
 
-    inputSpacing->setRange(0.0, 10.0, 2);
-    inputSpacing->setSingleStep(0.1);
-    inputSpacing->setValue(0.1);
-    connect(inputSpacing, SIGNAL(valueChanged(qreal)), this, SLOT(spinBoxSpacingChanged(qreal)));
+    connect(spacingWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged()));
 
     density->setRange(0, 100, 0);
     density->setSingleStep(1);
@@ -160,7 +157,8 @@ void KisAutoBrushWidget::paramChanged()
     Q_CHECK_PTR(kas);
 
     m_autoBrush = new KisAutoBrush(kas, inputAngle->value() / 180.0 * M_PI, inputRandomness->value() / 100.0, density->value() / 100.0);
-    m_autoBrush->setSpacing(inputSpacing->value());
+    m_autoBrush->setSpacing(spacingWidget->spacing());
+    m_autoBrush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff());
     m_brush = m_autoBrush->image();
 
     QImage pi(m_brush);
@@ -261,14 +259,6 @@ void KisAutoBrushWidget::spinBoxAngleChanged(int a)
     paramChanged();
 }
 
-void KisAutoBrushWidget::spinBoxSpacingChanged(qreal a)
-{
-    inputSpacing->blockSignals(true);
-    inputSpacing->setValue(a);
-    inputSpacing->blockSignals(false);
-    paramChanged();
-}
-
 void KisAutoBrushWidget::spinBoxDensityChanged(qreal a)
 {
     density->blockSignals(true);
@@ -277,6 +267,11 @@ void KisAutoBrushWidget::spinBoxDensityChanged(qreal a)
     paramChanged();
 }
 
+void KisAutoBrushWidget::slotSpacingChanged()
+{
+    paramChanged();
+}
+
 void KisAutoBrushWidget::linkFadeToggled(bool b)
 {
     m_linkFade = b;
@@ -321,8 +316,11 @@ void KisAutoBrushWidget::setBrush(KisBrushSP brush)
 
     inputAngle->setValue(aBrush->angle() * 180 / M_PI);
     inputSpikes->setValue(aBrush->maskGenerator()->spikes());
-    inputSpacing->setValue(aBrush->spacing());
-    inputSpacing->setExponentRatio(3.0);
+
+    spacingWidget->setSpacing(aBrush->autoSpacingActive(),
+                              aBrush->autoSpacingActive() ?
+                              aBrush->autoSpacingCoeff() : aBrush->spacing());
+
     inputRandomness->setValue(aBrush->randomness() * 100);
     density->setValue(aBrush->density() * 100);
 
diff --git a/krita/plugins/paintops/libpaintop/kis_auto_brush_widget.h b/krita/plugins/paintops/libpaintop/kis_auto_brush_widget.h
index 2acafb7..d1f48bb 100644
--- a/krita/plugins/paintops/libpaintop/kis_auto_brush_widget.h
+++ b/krita/plugins/paintops/libpaintop/kis_auto_brush_widget.h
@@ -63,10 +63,11 @@ private slots:
     void spinBoxRandomnessChanged(qreal);
     void spinBoxRadiusChanged(qreal);
     void spinBoxSpikesChanged(int);
-    void spinBoxSpacingChanged(qreal);
     void spinBoxAngleChanged(int);
     void spinBoxDensityChanged(qreal);
 
+    void slotSpacingChanged();
+
 signals:
 
     void sigBrushChanged();
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp
index 1458def..ab5b013 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp
+++ b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.cpp
@@ -20,7 +20,7 @@
 #include "kis_properties_configuration.h"
 #include "kis_brush_option.h"
 #include <kis_pressure_spacing_option.h>
-
+#include "kis_qimage_pyramid.h"
 
 #include <QImage>
 #include <QPainter>
@@ -106,34 +106,58 @@ bool KisBrushBasedPaintOp::checkSizeTooSmall(qreal scale)
            scale * m_brush->height() < 0.01;
 }
 
-KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(int dabWidth, int dabHeight) const
+KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale, qreal rotation) const
 {
-    return effectiveSpacing(dabWidth, dabHeight, 1.0, false);
+    return effectiveSpacing(scale, rotation, 1.0, false);
 }
 
-KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(int dabWidth, int dabHeight, const KisPressureSpacingOption &spacingOption, const KisPaintInformation &pi) const
+KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal scale, qreal rotation, const KisPressureSpacingOption &spacingOption, const KisPaintInformation &pi) const
 {
     qreal extraSpacingScale = 1.0;
     if (spacingOption.isChecked()) {
         extraSpacingScale = spacingOption.apply(pi);
     }
 
-    return effectiveSpacing(dabWidth, dabHeight, extraSpacingScale, spacingOption.isotropicSpacing());
+    QSizeF metric =
+        KisQImagePyramid::characteristicSize(QSize(m_brush->width(), m_brush->height()),
+                                             scale, rotation);
+
+    return effectiveSpacing(metric.width(), metric.height(), extraSpacingScale, spacingOption.isotropicSpacing());
+}
+
+inline qreal calcAutoSpacing(qreal value)
+{
+    return value < 1.0 ? value : sqrt(value);
+}
+
+inline QPointF calcAutoSpacing(const QPointF &pt)
+{
+    return QPointF(calcAutoSpacing(pt.x()), calcAutoSpacing(pt.y()));
 }
 
-KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(int dabWidth, int dabHeight, qreal extraScale, bool isotropicSpacing) const
+KisSpacingInformation KisBrushBasedPaintOp::effectiveSpacing(qreal dabWidth, qreal dabHeight, qreal extraScale, bool isotropicSpacing) const
 {
     QPointF spacing;
 
     if (!isotropicSpacing) {
-        spacing = QPointF(dabWidth, dabHeight);
+        if (m_brush->autoSpacingActive()) {
+            spacing = calcAutoSpacing(QPointF(dabWidth, dabHeight));
+        } else {
+            spacing = QPointF(dabWidth, dabHeight);
+            spacing *= m_brush->spacing();
+        }
     }
     else {
         qreal significantDimension = qMax(dabWidth, dabHeight);
+        if (m_brush->autoSpacingActive()) {
+            significantDimension = calcAutoSpacing(significantDimension);
+        } else {
+            significantDimension *= m_brush->spacing();
+        }
         spacing = QPointF(significantDimension, significantDimension);
     }
 
-    spacing *= extraScale * m_brush->spacing();
+    spacing *= extraScale;
 
     return spacing;
 }
diff --git a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.h b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.h
index a809d67..38077b7 100644
--- a/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.h
+++ b/krita/plugins/paintops/libpaintop/kis_brush_based_paintop.h
@@ -46,9 +46,8 @@ public:
 
     bool checkSizeTooSmall(qreal scale);
 
-    KisSpacingInformation effectiveSpacing(int dabWidth, int dabHeight) const;
-    KisSpacingInformation effectiveSpacing(int dabWidth, int dabHeight, const KisPressureSpacingOption &spacingOption, const KisPaintInformation &pi) const;
-    KisSpacingInformation effectiveSpacing(int dabWidth, int dabHeight, qreal extraScale, bool isotropicSpacing) const;
+    KisSpacingInformation effectiveSpacing(qreal scale, qreal rotation) const;
+    KisSpacingInformation effectiveSpacing(qreal scale, qreal rotation, const KisPressureSpacingOption &spacingOption, const KisPaintInformation &pi) const;
 
     ///Reimplemented, false if brush is 0
     virtual bool canPaint() const;
@@ -58,6 +57,9 @@ public:
     static void preinitializeOpStatically(const KisPaintOpSettingsSP settings);
 #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
 
+private:
+    KisSpacingInformation effectiveSpacing(qreal dabWidth, qreal dabHeight, qreal extraScale, bool isotropicSpacing) const;
+
 protected: // XXX: make private!
 
     KisBrushSP m_brush;
diff --git a/krita/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp b/krita/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp
new file mode 100644
index 0000000..a08fd2b
--- /dev/null
+++ b/krita/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp
@@ -0,0 +1,127 @@
+/*
+ *  Copyright (c) 2014 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_spacing_selection_widget.h"
+
+#include <QPushButton>
+#include <QHBoxLayout>
+
+#include "klocale.h"
+
+#include "kis_signals_blocker.h"
+#include "kis_slider_spin_box.h"
+
+
+
+struct KisSpacingSelectionWidget::Private
+{
+    Private(KisSpacingSelectionWidget *_q)
+        : q(_q), oldSliderValue(0.1)
+    {
+    }
+
+    KisSpacingSelectionWidget *q;
+
+    KisDoubleSliderSpinBox *slider;
+    QPushButton *autoButton;
+
+    qreal oldSliderValue;
+
+    void slotSpacingChanged(qreal value);
+    void slotAutoSpacing(bool value);
+};
+
+
+KisSpacingSelectionWidget::KisSpacingSelectionWidget(QWidget *parent)
+    : QWidget(parent),
+      m_d(new Private(this))
+{
+    m_d->slider = new KisDoubleSliderSpinBox(this);
+    m_d->slider->setRange(0.0, 10.0, 2);
+    m_d->slider->setSingleStep(0.01);
+    m_d->slider->setValue(0.1);
+    m_d->slider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed));
+
+    m_d->autoButton = new QPushButton(this);
+    m_d->autoButton->setText(i18nc("@action:button", "Auto"));
+    m_d->autoButton->setToolTip(i18nc("@info:tooltip", "In auto mode the spacing of the brush will be calculated automatically depending on its size"));
+    m_d->autoButton->setCheckable(true);
+    m_d->autoButton->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed));
+
+    QHBoxLayout *layout = new QHBoxLayout(this);
+    layout->addWidget(m_d->autoButton);
+    layout->addWidget(m_d->slider);
+
+    connect(m_d->slider, SIGNAL(valueChanged(qreal)), SLOT(slotSpacingChanged(qreal)));
+    connect(m_d->autoButton, SIGNAL(toggled(bool)), SLOT(slotAutoSpacing(bool)));
+}
+
+KisSpacingSelectionWidget::~KisSpacingSelectionWidget()
+{
+}
+
+qreal KisSpacingSelectionWidget::spacing() const
+{
+    return autoSpacingActive() ? 0.1 : m_d->slider->value();
+}
+
+bool KisSpacingSelectionWidget::autoSpacingActive() const
+{
+    return m_d->autoButton->isChecked();
+}
+
+qreal KisSpacingSelectionWidget::autoSpacingCoeff() const
+{
+    return autoSpacingActive() ? m_d->slider->value() : 1.0;
+}
+
+void KisSpacingSelectionWidget::setSpacing(bool isAuto, qreal spacing)
+{
+    KisSignalsBlocker b1(m_d->autoButton);
+    KisSignalsBlocker b2(m_d->slider);
+
+    m_d->autoButton->setChecked(isAuto);
+    m_d->slider->setValue(spacing);
+}
+
+void KisSpacingSelectionWidget::Private::slotSpacingChanged(qreal value)
+{
+    Q_UNUSED(value);
+    emit q->sigSpacingChanged();
+}
+
+void KisSpacingSelectionWidget::Private::slotAutoSpacing(bool value)
+{
+    qreal newSliderValue = 0.0;
+
+    if (value) {
+        newSliderValue = 1.0;
+        oldSliderValue = slider->value();
+    } else {
+        newSliderValue = oldSliderValue;
+    }
+
+    {
+        KisSignalsBlocker b(slider);
+        slider->setValue(newSliderValue);
+    }
+
+    emit q->sigSpacingChanged();
+}
+
+#include "kis_spacing_selection_widget.moc"
diff --git a/krita/plugins/paintops/libpaintop/kis_spacing_selection_widget.h b/krita/plugins/paintops/libpaintop/kis_spacing_selection_widget.h
new file mode 100644
index 0000000..3b32919
--- /dev/null
+++ b/krita/plugins/paintops/libpaintop/kis_spacing_selection_widget.h
@@ -0,0 +1,51 @@
+/*
+ *  Copyright (c) 2014 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_SPACING_SELECTION_WIDGET_H
+#define __KIS_SPACING_SELECTION_WIDGET_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+
+class KisSpacingSelectionWidget : public QWidget
+{
+    Q_OBJECT
+public:
+    KisSpacingSelectionWidget(QWidget *parent);
+    ~KisSpacingSelectionWidget();
+
+    void setSpacing(bool isAuto, qreal spacing);
+
+    qreal spacing() const;
+    bool autoSpacingActive() const;
+    qreal autoSpacingCoeff() const;
+
+signals:
+    void sigSpacingChanged();
+
+private:
+    Q_PRIVATE_SLOT(m_d, void slotSpacingChanged(qreal value));
+    Q_PRIVATE_SLOT(m_d, void slotAutoSpacing(bool value));
+
+private:
+    struct Private;
+    const QScopedPointer<Private> m_d;
+};
+
+#endif /* __KIS_SPACING_SELECTION_WIDGET_H */


More information about the kimageshop mailing list