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

List:       kde-kimageshop
Subject:    [calligra/krita-chili-kazakov] krita: Fixed antialiasing of auto-brush based brushes
From:       Dmitry Kazakov <dimula73 () gmail ! com>
Date:       2014-08-20 16:49:44
Message-ID: E1XK94y-0006De-P7 () scm ! kde ! org
[Download RAW message or body]

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@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@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@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@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 */
_______________________________________________
Krita mailing list
kimageshop@kde.org
https://mail.kde.org/mailman/listinfo/kimageshop


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

Configure | About | News | Add a list | Sponsored by KoreLogic