[calligra] krita/plugins/paintops/libbrush: A significant refactoring in the Predefined Brush engine

Dmitry Kazakov dimula73 at gmail.com
Tue Jul 16 11:00:51 UTC 2013


Git commit b9d749eb4e7c6bbc00332c9d1c090c9a2293af74 by Dmitry Kazakov.
Committed on 16/07/2013 at 10:43.
Pushed by dkazakov into branch 'master'.

A significant refactoring in the Predefined Brush engine

This patch ports the predefined brush engine to the new capabilities
of Qt. It removes a huge chunk of hand-written code. Here is a short list
of changes:

1) All the operations are now performed on non-premultiplied RGB values.
   The use of premultiplication resulted in instability of the color,
   which caused artifacts on scaling and rotation of the brushes.

2) Trilinear filtering of the brushes is now substituted with simple
   bilinear filtering from the nearest pyramid level.

   It turned out that trilinear filtering not only impacted the
   performance, but also introduced heavy aliasing artifacts on specific
   zoom levels.

3) After the patch the speed of the dab generation raised 3-5 times. Here
   are the values for 512px brush:
   Before patch:
      Scaling:   34 ms
      Rotation:  19 ms
   After patch:
      Scaling:   6 ms
      Rotation:  9 ms

CCBUG:320651
BUG:320368,319944,300665
CCMAIL:kimageshop at kde.org

M  +1    -2    krita/plugins/paintops/libbrush/CMakeLists.txt
M  +0    -1    krita/plugins/paintops/libbrush/kis_abr_brush.cpp
M  +1    -1    krita/plugins/paintops/libbrush/kis_auto_brush.cpp
M  +70   -778  krita/plugins/paintops/libbrush/kis_brush.cpp
M  +3    -26   krita/plugins/paintops/libbrush/kis_brush.h
M  +2    -3    krita/plugins/paintops/libbrush/kis_gbr_brush.cpp
D  +0    -135  krita/plugins/paintops/libbrush/kis_qimage_mask.cpp
D  +0    -138  krita/plugins/paintops/libbrush/kis_qimage_mask.h
A  +196  -0    krita/plugins/paintops/libbrush/kis_qimage_pyramid.cpp     [License: GPL (v2+)]
A  +52   -0    krita/plugins/paintops/libbrush/kis_qimage_pyramid.h     [License: GPL (v2+)]
D  +0    -64   krita/plugins/paintops/libbrush/kis_scaled_brush.cpp
D  +0    -62   krita/plugins/paintops/libbrush/kis_scaled_brush.h
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_0_sc_0.871853_rot_3.55731_sub_0.137199.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_1_sc_0.861342_rot_3.45867_sub_0.20933.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_2_sc_1.80941_rot_4.97706_sub_0.113862.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_3_sc_0.46124_rot_4.18791_sub_0.167627.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_4_sc_0.963137_rot_2.82314_sub_0.444059.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_5_sc_0.592981_rot_0.439579_sub_0.45074.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_6_sc_1.86775_rot_3.12606_sub_0.365777.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_7_sc_1.13937_rot_3.47854_sub_0.458733.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_8_sc_1.53198_rot_3.68719_sub_0.410264.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_9_sc_1.46566_rot_2.88023_sub_0.474873.png
M  +-    --    krita/plugins/paintops/libbrush/tests/data/result_autobrush_3.png
M  +-    --    krita/plugins/paintops/libbrush/tests/data/result_autobrush_4.png
A  +-    --    krita/plugins/paintops/libbrush/tests/data/testing_brush_512_bars.gbr
M  +24   -28   krita/plugins/paintops/libbrush/tests/kis_auto_brush_test.cpp
M  +91   -17   krita/plugins/paintops/libbrush/tests/kis_brush_test.cpp
M  +7    -2    krita/plugins/paintops/libbrush/tests/kis_brush_test.h
M  +5    -4    krita/plugins/paintops/libbrush/tests/kis_imagepipe_brush_test.cpp

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

diff --git a/krita/plugins/paintops/libbrush/CMakeLists.txt b/krita/plugins/paintops/libbrush/CMakeLists.txt
index ea97648..eff7bb0 100644
--- a/krita/plugins/paintops/libbrush/CMakeLists.txt
+++ b/krita/plugins/paintops/libbrush/CMakeLists.txt
@@ -15,8 +15,7 @@ set(kritalibbrush_LIB_SRCS
     kis_png_brush_factory.cpp
     kis_svg_brush.cpp
     kis_svg_brush_factory.cpp
-    kis_qimage_mask.cpp
-    kis_scaled_brush.cpp
+    kis_qimage_pyramid.cpp
     kis_text_brush.cpp
     kis_auto_brush_factory.cpp
     kis_text_brush_factory.cpp
diff --git a/krita/plugins/paintops/libbrush/kis_abr_brush.cpp b/krita/plugins/paintops/libbrush/kis_abr_brush.cpp
index 874ad8a..baf06c2 100644
--- a/krita/plugins/paintops/libbrush/kis_abr_brush.cpp
+++ b/krita/plugins/paintops/libbrush/kis_abr_brush.cpp
@@ -19,7 +19,6 @@
  */
 #include "kis_abr_brush.h"
 #include "kis_brush.h"
-#include "kis_qimage_mask.h"
 
 #include <QDomElement>
 #include <QFile>
diff --git a/krita/plugins/paintops/libbrush/kis_auto_brush.cpp b/krita/plugins/paintops/libbrush/kis_auto_brush.cpp
index 02daf12..3f15ef7 100644
--- a/krita/plugins/paintops/libbrush/kis_auto_brush.cpp
+++ b/krita/plugins/paintops/libbrush/kis_auto_brush.cpp
@@ -307,7 +307,7 @@ qreal KisAutoBrush::randomness() const
 void KisAutoBrush::setImage(const QImage& image)
 {
     m_image = image;
-    clearScaledBrushes();
+    clearBrushPyramid();
 }
 
 QPainterPath KisAutoBrush::outline() const
diff --git a/krita/plugins/paintops/libbrush/kis_brush.cpp b/krita/plugins/paintops/libbrush/kis_brush.cpp
index 5a91cc9..83e90fc 100644
--- a/krita/plugins/paintops/libbrush/kis_brush.cpp
+++ b/krita/plugins/paintops/libbrush/kis_brush.cpp
@@ -31,6 +31,7 @@
 #include <klocale.h>
 
 #include <KoColor.h>
+#include <KoColorSpaceMaths.h>
 #include <KoColorSpaceRegistry.h>
 
 #include "kis_datamanager.h"
@@ -38,17 +39,13 @@
 #include "kis_global.h"
 #include "kis_boundary.h"
 #include "kis_image.h"
-#include "kis_scaled_brush.h"
-#include "kis_qimage_mask.h"
 #include "kis_iterator_ng.h"
 #include "kis_brush_registry.h"
 #include <kis_paint_information.h>
 #include <kis_fixed_paint_device.h>
+#include <kis_qimage_pyramid.h>
 
 
-const static int MAXIMUM_MIPMAP_SCALE = 10;
-const static int MAXIMUM_MIPMAP_SIZE  = 400;
-
 KisBrush::ColoringInformation::~ColoringInformation()
 {
 }
@@ -105,7 +102,8 @@ struct KisBrush::Private {
         , angle(0)
         , scale(1.0)
         , hasColor(false)
-    , brushType(INVALID)
+        , brushType(INVALID)
+        , brushPyramid(0)
     {}
 
     ~Private() {
@@ -122,9 +120,8 @@ struct KisBrush::Private {
     qint32 height;
     double spacing;
     QPointF hotSpot;
-    mutable QVector<KisScaledBrush> scaledBrushes;
-
 
+    mutable KisQImagePyramid *brushPyramid;
 };
 
 KisBrush::KisBrush()
@@ -150,17 +147,17 @@ KisBrush::KisBrush(const KisBrush& rhs)
     d->height = rhs.d->height;
     d->spacing = rhs.d->spacing;
     d->hotSpot = rhs.d->hotSpot;
-    d->scaledBrushes.clear();
     d->hasColor = rhs.d->hasColor;
     d->angle = rhs.d->angle;
     d->scale = rhs.d->scale;
     setFilename(rhs.filename());
+    clearBrushPyramid();
     // don't copy the boundery, it will be regenerated -- see bug 291910
 }
 
 KisBrush::~KisBrush()
 {
-    clearScaledBrushes();
+    clearBrushPyramid();
     delete d;
 }
 
@@ -255,7 +252,7 @@ void KisBrush::setImage(const QImage& image)
     setWidth(image.width());
     setHeight(image.height());
 
-    clearScaledBrushes();
+    clearBrushPyramid();
 
 }
 
@@ -295,21 +292,8 @@ qint32 KisBrush::maskWidth(double scale, double angle, const KisPaintInformation
     if(angle > 2 * M_PI) angle -= 2 * M_PI;
     scale *= d->scale;
 
-    double width_ = width() * scale;
-    if(angle == 0.0) return (qint32)ceil(width_ + 1);
-
-    double height_ = height() * scale;
-
-    // Add one for sub-pixel shift
-    if (angle >= 0.0 && angle < M_PI_2) {
-        return qAbs(static_cast<qint32>(ceil(width_ * cos(angle) + height_ * sin(angle)) + 1));
-    } else if (angle >= M_PI_2 && angle < M_PI) {
-        return qAbs(static_cast<qint32>(ceil(-width_ * cos(angle) + height_ * sin(angle)) + 1));
-    } else if (angle >= M_PI && angle < (M_PI + M_PI_2)) {
-        return qAbs(static_cast<qint32>(ceil(-width_ * cos(angle) - height_ * sin(angle)) + 1));
-    } else {
-        return qAbs(static_cast<qint32>(ceil(width_ * cos(angle) - height_ * sin(angle)) + 1));
-    }
+    return KisQImagePyramid::imageSize(QSize(width(), height()),
+                                       scale, angle, 0.0, 0.0).width();
 }
 
 qint32 KisBrush::maskHeight(double scale, double angle, const KisPaintInformation& info) const
@@ -322,21 +306,9 @@ qint32 KisBrush::maskHeight(double scale, double angle, const KisPaintInformatio
     if(angle < 0) angle += 2 * M_PI;
     if(angle > 2 * M_PI) angle -= 2 * M_PI;
     scale *= d->scale;
-    double height_ = height() * scale;
-    if(angle == 0.0) return ceil(height_ + 1);
-
-    double width_ = width() * scale;
-
-    // Add one for sub-pixel shift
-    if (angle >= 0.0 && angle < M_PI_2) {
-        return qAbs(static_cast<qint32>(ceil(width_ * sin(angle) + height_ * cos(angle)) + 1));
-    } else if (angle >= M_PI_2 && angle < M_PI) {
-        return qAbs(static_cast<qint32>(ceil(width_ * sin(angle) - height_ * cos(angle)) + 1));
-    } else if (angle >= M_PI && angle < (M_PI + M_PI_2)) {
-        return qAbs(static_cast<qint32>(ceil(-width_ * sin(angle) - height_ * cos(angle)) + 1));
-    } else {
-        return qAbs(static_cast<qint32>(ceil(-width_ * sin(angle) + height_ * cos(angle)) + 1));
-    }
+
+    return KisQImagePyramid::imageSize(QSize(width(), height()),
+                                       scale, angle, 0.0, 0.0).height();
 }
 
 double KisBrush::maskAngle(double angle) const
@@ -380,6 +352,19 @@ double KisBrush::spacing() const
 void KisBrush::notifyCachedDabPainted() {
 }
 
+void KisBrush::prepareBrushPyramid() const
+{
+    if (!d->brushPyramid) {
+        d->brushPyramid = new KisQImagePyramid(image());
+    }
+}
+
+void KisBrush::clearBrushPyramid()
+{
+    delete d->brushPyramid;
+    d->brushPyramid = 0;
+}
+
 void KisBrush::mask(KisFixedPaintDeviceSP dst, double scaleX, double scaleY, double angle, const KisPaintInformation& info , double subPixelX, double subPixelY, qreal softnessFactor) const
 {
     generateMaskAndApplyMaskOrCreateDab(dst, 0, scaleX, scaleY, angle, info, subPixelX, subPixelY, softnessFactor);
@@ -416,41 +401,16 @@ void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
     scaleX *= d->scale;
     scaleY *= d->scale;
 
-    const KoColorSpace* cs = dst->colorSpace();
-    quint32 pixelSize = cs->pixelSize();
-
     double scale = 0.5 * (scaleX + scaleY);
 
-    KisQImagemaskSP outputMask = createMask(scale, subPixelX, subPixelY);
-
-    if (angle != 0) {
-        outputMask->rotation(angle);
-    }
-
-    qint32 maskWidth = outputMask->width();
-    qint32 maskHeight = outputMask->height();
+    prepareBrushPyramid();
+    QImage outputImage = d->brushPyramid->createImage(scale, -angle, subPixelX, subPixelY);
 
-    if (coloringInformation || dst->data() == 0 || dst->bounds().isEmpty()) {
-        // Lazy initialization
-        dst->setRect(QRect(0, 0, maskWidth, maskHeight));
-        dst->initialize();
-    }
+    qint32 maskWidth = outputImage.width();
+    qint32 maskHeight = outputImage.height();
 
-    {
-        QSize dabSize = dst->bounds().size();
-        if (dabSize.width() != maskWidth || dabSize.height() != maskHeight) {
-            qWarning() << "WARNING: KisBrush::generateMaskAndApplyMaskOrCreateDab";
-            qWarning() << "         the sizes of the mask and the supplied dab are not"
-                       << "equal. We shall workaround it now, but please report a bug.";
-            qWarning() << "        " << ppVar(maskWidth) << ppVar(maskHeight);
-            qWarning() << "        " << ppVar(dabSize);
-
-            dst->setRect(QRect(0, 0, maskWidth, maskHeight));
-            dst->initialize();
-        }
-    }
-
-    Q_ASSERT(dst->bounds().size().width() >= maskWidth && dst->bounds().size().height() >= maskHeight);
+    dst->setRect(QRect(0, 0, maskWidth, maskHeight));
+    dst->initialize();
 
     quint8* color = 0;
 
@@ -460,12 +420,15 @@ void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
         }
     }
 
-    quint8* dabPointer = dst->data();
-    quint8* rowPointer = dabPointer;
-    int rowWidth = dst->bounds().width();
+    const KoColorSpace *cs = dst->colorSpace();
+    qint32 pixelSize = cs->pixelSize();
+    quint8 *dabPointer = dst->data();
+    quint8 *rowPointer = dabPointer;
+    quint8 *alphaArray = new quint8[maskWidth];
+    bool hasColor = this->hasColor();
 
     for (int y = 0; y < maskHeight; y++) {
-        quint8* maskPointer = outputMask->scanline(y);
+        quint8* maskPointer = outputImage.scanLine(y);
         if (coloringInformation) {
             for (int x = 0; x < maskWidth; x++) {
                 if (color) {
@@ -477,14 +440,39 @@ void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
                 dabPointer += pixelSize;
             }
         }
-        cs->applyAlphaU8Mask(rowPointer, maskPointer, maskWidth);
-        rowPointer += rowWidth * pixelSize;
+
+        if (hasColor) {
+            quint8 *src = maskPointer;
+            quint8 *dst = alphaArray;
+            for (int x = 0; x < maskWidth; x++) {
+                QRgb *c = reinterpret_cast<QRgb*>(src);
+
+                *dst = KoColorSpaceMaths<quint8>::multiply(255 - qGray(*c), qAlpha(*c));
+                src += 4;
+                dst++;
+            }
+        } else {
+            quint8 *src = maskPointer;
+            quint8 *dst = alphaArray;
+            for (int x = 0; x < maskWidth; x++) {
+                QRgb *c = reinterpret_cast<QRgb*>(src);
+
+                *dst = KoColorSpaceMaths<quint8>::multiply(255 - *src, qAlpha(*c));
+                src += 4;
+                dst++;
+            }
+        }
+
+        cs->applyAlphaU8Mask(rowPointer, alphaArray, maskWidth);
+        rowPointer += maskWidth * pixelSize;
         dabPointer = rowPointer;
 
         if (!color && coloringInformation) {
             coloringInformation->nextRow();
         }
     }
+
+    delete alphaArray;
 }
 
 KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace,
@@ -500,713 +488,17 @@ KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace,
     if(angle < 0) angle += 2 * M_PI;
     if(angle > 2 * M_PI) angle -= 2 * M_PI;
     scale *= d->scale;
-    if (d->scaledBrushes.isEmpty()) {
-        createScaledBrushes();
-    }
-
-    const KisScaledBrush *aboveBrush = 0;
-    const KisScaledBrush *belowBrush = 0;
-
-    findScaledBrushes(scale, &aboveBrush, &belowBrush);
-    Q_ASSERT(aboveBrush != 0);
 
-    QImage outputImage;
+    prepareBrushPyramid();
+    QImage outputImage = d->brushPyramid->createImage(scale, -angle, subPixelX, subPixelY);
 
-    if (belowBrush != 0) {
-        // We're in between two brushes. Interpolate between them.
-
-        QImage scaledAboveImage = scaleImage(aboveBrush, scale, subPixelX, subPixelY);
-
-        QImage scaledBelowImage = scaleImage(belowBrush, scale, subPixelX, subPixelY);
-
-        double t = (scale - belowBrush->scale()) / (aboveBrush->scale() - belowBrush->scale());
-
-        outputImage = interpolate(scaledBelowImage, scaledAboveImage, t);
-
-    } else {
-        if (Eigen::ei_isApprox(scale, aboveBrush->scale())) {
-            // Exact match.
-            outputImage = scaleImage(aboveBrush, scale, subPixelX, subPixelY);
-
-        } else {
-            // We are smaller than the smallest brush, which is always 1x1.
-            double s = scale / aboveBrush->scale();
-            outputImage = scaleSinglePixelImage(s, aboveBrush->image().pixel(0, 0), subPixelX, subPixelY);
-
-        }
-    }
-
-    if (angle != 0.0)
-    {
-        outputImage = outputImage.transformed(QTransform().rotate(-angle * 180 / M_PI), Qt::SmoothTransformation);
-    }
-
-    int outputWidth = outputImage.width();
-    int outputHeight = outputImage.height();
-
-    KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->rgb8());
+    KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(colorSpace);
     Q_CHECK_PTR(dab);
-    dab->setRect(outputImage.rect());
-    dab->initialize();
-    quint8* dabPointer = dab->data();
-    quint32 pixelSize = dab->pixelSize();
-
-    for (int y = 0; y < outputHeight; y++) {
-        const QRgb *scanline = reinterpret_cast<const QRgb *>(outputImage.scanLine(y));
-        for (int x = 0; x < outputWidth; x++) {
-            QRgb pixel = scanline[x];
-
-            int red = qRed(pixel);
-            int green = qGreen(pixel);
-            int blue = qBlue(pixel);
-            int alpha = qAlpha(pixel);
-
-            // Scaled images are in pre-multiplied alpha form so
-            // divide by alpha.
-            // XXX: Is alpha != 0 ever true?
-            // channel order is BGRA
-            if (alpha != 0) {
-                dabPointer[2] = (red * 255) / alpha;
-                dabPointer[1] = (green * 255) / alpha;
-                dabPointer[0] = (blue * 255) / alpha;
-                dabPointer[3] = alpha;
-            } else {
-                dabPointer[2] = red;
-                dabPointer[1] = green;
-                dabPointer[0] = blue;
-                dabPointer[3] = 0;
-            }
-
-            dabPointer += pixelSize;
+    dab->convertFromQImage(outputImage, "");
 
-        }
-    }
-    if (colorSpace != KoColorSpaceRegistry::instance()->rgb8()) {
-        KisFixedPaintDeviceSP dab2 = new KisFixedPaintDevice(colorSpace);
-        dab2->setRect(outputImage.rect());
-        dab2->initialize();
-        dabPointer = dab->data();
-        quint8* dabPointer2 = dab2->data();
-        KoColorSpaceRegistry::instance()->rgb8()->convertPixelsTo(dabPointer, dabPointer2, colorSpace, outputWidth * outputHeight, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags);
-        dab = dab2;
-    }
     return dab;
 }
 
-void KisBrush::clearScaledBrushes()
-{
-    d->scaledBrushes.clear();
-}
-
-void KisBrush::createScaledBrushes() const
-{
-    if (!d->scaledBrushes.isEmpty()) {
-        const_cast<KisBrush*>(this)->clearScaledBrushes();
-    }
-
-    if (image().isNull()) {
-        return;
-    }
-
-    // Construct a series of brushes where each one's dimensions are
-    // half the size of the previous one.
-    // IMPORTANT: and make sure that a brush with a size > MAXIMUM_MIPMAP_SIZE
-    // will not get scaled up anymore or the memory consumption gets too high
-    // also don't scale the brush up more then MAXIMUM_MIPMAP_SCALE times
-    int scale  = qBound(1, MAXIMUM_MIPMAP_SIZE*2 / qMax(image().width(),image().height()), MAXIMUM_MIPMAP_SCALE);
-    int width  = ceil((double)(image().width()  * scale));
-    int height = ceil((double)(image().height() * scale));
-
-    QImage scaledImage;
-    while (true) {
-
-        if (width >= image().width() && height >= image().height()) {
-            scaledImage = scaleImage(image(), width, height);
-        } else {
-            // Scale down the previous image once we're below 1:1.
-            scaledImage = scaleImage(scaledImage, width, height);
-        }
-
-        KisQImagemaskSP scaledMask = KisQImagemaskSP(new KisQImagemask(scaledImage, hasColor()));
-        Q_CHECK_PTR(scaledMask);
-
-        double xScale = static_cast<double>(width) / image().width();
-        double yScale = static_cast<double>(height) / image().height();
-        double scale = xScale;
-
-        d->scaledBrushes.append(KisScaledBrush(scaledMask, hasColor() ? scaledImage : QImage(), scale, xScale, yScale));
-
-        if (width == 1 && height == 1) {
-            break;
-        }
-
-        // Round up so that we never have to scale an image by less than 1/2.
-        width = (width + 1) / 2;
-        height = (height + 1) / 2;
-
-    }
-}
-
-KisQImagemaskSP KisBrush::createMask(double scale, double subPixelX, double subPixelY) const
-{
-    if (d->scaledBrushes.isEmpty()) {
-        createScaledBrushes();
-    }
-
-    const KisScaledBrush *aboveBrush = 0;
-    const KisScaledBrush *belowBrush = 0;
-
-    findScaledBrushes(scale, &aboveBrush,  &belowBrush);
-    Q_ASSERT(aboveBrush != 0);
-
-    // get the right mask
-    KisQImagemaskSP outputMask = KisQImagemaskSP(0);
-
-    if (belowBrush != 0) {
-        double t = (scale - belowBrush->scale()) / (aboveBrush->scale() - belowBrush->scale());
-
-        outputMask = scaleMask( (t >= 0.5) ? aboveBrush : belowBrush, scale, subPixelX, subPixelY);
-    } else {
-        if (Eigen::ei_isApprox(scale, aboveBrush->scale())) {
-            // Exact match.
-            outputMask = scaleMask(aboveBrush, scale, subPixelX, subPixelY);
-        } else {
-            // We are smaller than the smallest mask, which is always 1x1.
-            double s = scale / aboveBrush->scale();
-            outputMask = scaleSinglePixelMask(s, aboveBrush->mask()->alphaAt(0, 0), subPixelX, subPixelY);
-        }
-    }
-
-    return outputMask;
-}
-
-KisQImagemaskSP KisBrush::scaleMask(const KisScaledBrush *srcBrush, double scale, double subPixelX, double subPixelY) const
-{
-    // Add one pixel for sub-pixel shifting
-    int dstWidth = static_cast<int>(ceil(scale * width())) + 1;
-    int dstHeight = static_cast<int>(ceil(scale * height())) + 1;
-
-    KisQImagemaskSP dstMask = KisQImagemaskSP(new KisQImagemask(dstWidth, dstHeight, false));
-    Q_CHECK_PTR(dstMask);
-
-    KisQImagemaskSP srcMask = srcBrush->mask();
-
-    // Compute scales to map the scaled brush onto the required scale.
-    double xScale = srcBrush->xScale() / scale;
-    double yScale = srcBrush->yScale() / scale;
-
-    int srcWidth = srcMask->width();
-    int srcHeight = srcMask->height();
-
-    for (int dstY = 0; dstY < dstHeight; dstY++) {
-        for (int dstX = 0; dstX < dstWidth; dstX++) {
-
-            double srcX = (dstX - subPixelX + 0.5) * xScale;
-            double srcY = (dstY - subPixelY + 0.5) * yScale;
-
-            srcX -= 0.5;
-            srcY -= 0.5;
-
-            int leftX = static_cast<int>(srcX);
-
-            if (srcX < 0) {
-                leftX--;
-            }
-
-            double xInterp = srcX - leftX;
-
-            int topY = static_cast<int>(srcY);
-
-            if (srcY < 0) {
-                topY--;
-            }
-
-            double yInterp = srcY - topY;
-
-            quint8 topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX, topY) : OPACITY_TRANSPARENT_U8;
-            quint8 topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcMask->alphaAt(leftX + 1, topY) : OPACITY_TRANSPARENT_U8;
-            quint8 bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcMask->alphaAt(leftX, topY + 1) : OPACITY_TRANSPARENT_U8;
-            quint8 bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcMask->alphaAt(leftX + 1, topY + 1) : OPACITY_TRANSPARENT_U8;
-
-            double a = 1 - xInterp;
-            double b = 1 - yInterp;
-
-            // Bi-linear interpolation
-            int d = static_cast<int>(a * b * topLeft
-                                     + a * (1 - b) * bottomLeft
-                                     + (1 - a) * b * topRight
-                                     + (1 - a) * (1 - b) * bottomRight + 0.5);
-
-            if (d < OPACITY_TRANSPARENT_U8) {
-                d = OPACITY_TRANSPARENT_U8;
-            } else if (d > OPACITY_OPAQUE_U8) {
-                d = OPACITY_OPAQUE_U8;
-            }
-
-            dstMask->setAlphaAt(dstX, dstY, static_cast<quint8>(d));
-        }
-    }
-
-    return dstMask;
-}
-
-QImage KisBrush::scaleImage(const KisScaledBrush *srcBrush, double scale, double subPixelX, double subPixelY) const
-{
-    // Add one pixel for sub-pixel shifting
-    int dstWidth = static_cast<int>(ceil(scale * width())) + 1;
-    int dstHeight = static_cast<int>(ceil(scale * height())) + 1;
-
-    QImage dstImage(dstWidth, dstHeight, QImage::Format_ARGB32);
-
-    QImage srcImage = srcBrush->image();
-    if (srcImage.format() != QImage::Format_ARGB32)
-    {
-        srcImage = srcImage.convertToFormat(QImage::Format_ARGB32);
-    }
-
-    // Compute scales to map the scaled brush onto the required scale.
-    double xScale = srcBrush->xScale() / scale;
-    double yScale = srcBrush->yScale() / scale;
-
-    int srcWidth = srcImage.width();
-    int srcHeight = srcImage.height();
-
-    for (int dstY = 0; dstY < dstHeight; dstY++) {
-
-        double srcY = (dstY - subPixelY + 0.5) * yScale;
-        srcY -= 0.5;
-        int topY = static_cast<int>(srcY);
-        if (srcY < 0) {
-            topY--;
-        }
-
-        QRgb *dstPixel = reinterpret_cast<QRgb *>(dstImage.scanLine(dstY));
-        QRgb *srcPixel = reinterpret_cast<QRgb *>(srcImage.scanLine(topY));
-        QRgb *srcPixelPlusOne = reinterpret_cast<QRgb *>(srcImage.scanLine(topY + 1));
-
-        for (int dstX = 0; dstX < dstWidth; dstX++) {
-
-            double srcX = (dstX - subPixelX + 0.5) * xScale;
-
-
-            srcX -= 0.5;
-
-
-            int leftX = static_cast<int>(srcX);
-
-            if (srcX < 0) {
-                leftX--;
-            }
-
-            double xInterp = srcX - leftX;
-
-            double yInterp = srcY - topY;
-
-            QRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcPixel[leftX] : qRgba(0, 0, 0, 0);
-            QRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcPixelPlusOne[leftX] : qRgba(0, 0, 0, 0);
-            QRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcPixel[leftX + 1] : qRgba(0, 0, 0, 0);
-            QRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcPixelPlusOne[leftX + 1] : qRgba(0, 0, 0, 0);
-
-            double a = 1 - xInterp;
-            double b = 1 - yInterp;
-
-            // Bi-linear interpolation. Image is pre-multiplied by alpha.
-            int red = static_cast<int>(a * b * qRed(topLeft)
-                                       + a * (1 - b) * qRed(bottomLeft)
-                                       + (1 - a) * b * qRed(topRight)
-                                       + (1 - a) * (1 - b) * qRed(bottomRight) + 0.5);
-            int green = static_cast<int>(a * b * qGreen(topLeft)
-                                         + a * (1 - b) * qGreen(bottomLeft)
-                                         + (1 - a) * b * qGreen(topRight)
-                                         + (1 - a) * (1 - b) * qGreen(bottomRight) + 0.5);
-            int blue = static_cast<int>(a * b * qBlue(topLeft)
-                                        + a * (1 - b) * qBlue(bottomLeft)
-                                        + (1 - a) * b * qBlue(topRight)
-                                        + (1 - a) * (1 - b) * qBlue(bottomRight) + 0.5);
-            int alpha = static_cast<int>(a * b * qAlpha(topLeft)
-                                         + a * (1 - b) * qAlpha(bottomLeft)
-                                         + (1 - a) * b * qAlpha(topRight)
-                                         + (1 - a) * (1 - b) * qAlpha(bottomRight) + 0.5);
-
-            if (red < 0) {
-                red = 0;
-            } else if (red > 255) {
-                red = 255;
-            }
-
-            if (green < 0) {
-                green = 0;
-            } else if (green > 255) {
-                green = 255;
-            }
-
-            if (blue < 0) {
-                blue = 0;
-            } else if (blue > 255) {
-                blue = 255;
-            }
-
-            if (alpha < 0) {
-                alpha = 0;
-            } else if (alpha > 255) {
-                alpha = 255;
-            }
-
-            dstPixel[dstX] = qRgba(red, green, blue, alpha);
-        }
-    }
-
-    return dstImage;
-}
-
-QImage KisBrush::scaleImage(const QImage& _srcImage, int width, int height)
-{
-    QImage scaledImage;
-    QImage srcImage = _srcImage; // detaches!
-    //QString filename;
-    if (srcImage.format() != QImage::Format_ARGB32)
-    {
-        srcImage = srcImage.convertToFormat(QImage::Format_ARGB32);
-    }
-
-    int srcWidth = srcImage.width();
-    int srcHeight = srcImage.height();
-
-    double xScale = static_cast<double>(srcWidth) / width;
-    double yScale = static_cast<double>(srcHeight) / height;
-
-    if (xScale > 2 || yScale > 2 || xScale < 1 || yScale < 1) {
-        // smoothScale gives better results when scaling an image up
-        // or scaling it to less than half size.
-        scaledImage = srcImage.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
-
-        //filename = QString("smoothScale_%1x%2.png").arg(width).arg(height);
-    } else {
-        scaledImage = QImage(width, height, srcImage.format());
-
-        for (int dstY = 0; dstY < height; dstY++) {
-
-            double srcY = (dstY + 0.5) * yScale;
-            srcY -= 0.5;
-            int topY = static_cast<int>(srcY);
-
-            if (srcY < 0) {
-                topY--;
-            }
-
-            QRgb *dstPixel = reinterpret_cast<QRgb *>(scaledImage.scanLine(dstY));
-            QRgb *srcPixel = reinterpret_cast<QRgb *>(srcImage.scanLine(topY));
-            QRgb *srcPixelPlusOne = reinterpret_cast<QRgb *>(srcImage.scanLine(topY + 1));
-
-            for (int dstX = 0; dstX < width; dstX++) {
-
-                double srcX = (dstX + 0.5) * xScale;
-
-                srcX -= 0.5;
-
-                int leftX = static_cast<int>(srcX);
-
-                if (srcX < 0) {
-                    leftX--;
-                }
-
-                double xInterp = srcX - leftX;
-
-                double yInterp = srcY - topY;
-
-                QRgb topLeft = (leftX >= 0 && leftX < srcWidth && topY >= 0 && topY < srcHeight) ? srcPixel[leftX] : qRgba(0, 0, 0, 0);
-                QRgb bottomLeft = (leftX >= 0 && leftX < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcPixelPlusOne[leftX] : qRgba(0, 0, 0, 0);
-                QRgb topRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY >= 0 && topY < srcHeight) ? srcPixel[leftX] : qRgba(0, 0, 0, 0);
-                QRgb bottomRight = (leftX + 1 >= 0 && leftX + 1 < srcWidth && topY + 1 >= 0 && topY + 1 < srcHeight) ? srcPixelPlusOne[leftX + 1] : qRgba(0, 0, 0, 0);
-
-                double a = 1 - xInterp;
-                double b = 1 - yInterp;
-
-                int red;
-                int green;
-                int blue;
-                int alpha;
-
-                if (srcImage.hasAlphaChannel()) {
-                    red = static_cast<int>(a * b * qRed(topLeft)         * qAlpha(topLeft)
-                                           + a * (1 - b) * qRed(bottomLeft)             * qAlpha(bottomLeft)
-                                           + (1 - a) * b * qRed(topRight)               * qAlpha(topRight)
-                                           + (1 - a) * (1 - b) * qRed(bottomRight)      * qAlpha(bottomRight) + 0.5);
-                    green = static_cast<int>(a * b * qGreen(topLeft)     * qAlpha(topLeft)
-                                             + a * (1 - b) * qGreen(bottomLeft)           * qAlpha(bottomLeft)
-                                             + (1 - a) * b * qGreen(topRight)             * qAlpha(topRight)
-                                             + (1 - a) * (1 - b) * qGreen(bottomRight)    * qAlpha(bottomRight) + 0.5);
-                    blue = static_cast<int>(a * b * qBlue(topLeft)       * qAlpha(topLeft)
-                                            + a * (1 - b) * qBlue(bottomLeft)            * qAlpha(bottomLeft)
-                                            + (1 - a) * b * qBlue(topRight)              * qAlpha(topRight)
-                                            + (1 - a) * (1 - b) * qBlue(bottomRight)     * qAlpha(bottomRight) + 0.5);
-                    alpha = static_cast<int>(a * b * qAlpha(topLeft)
-                                             + a * (1 - b) * qAlpha(bottomLeft)
-                                             + (1 - a) * b * qAlpha(topRight)
-                                             + (1 - a) * (1 - b) * qAlpha(bottomRight) + 0.5);
-
-                    if (alpha != 0) {
-                        red /= alpha;
-                        green /= alpha;
-                        blue /= alpha;
-                    }
-                } else {
-                    red = static_cast<int>(a * b * qRed(topLeft)
-                                           + a * (1 - b) * qRed(bottomLeft)
-                                           + (1 - a) * b * qRed(topRight)
-                                           + (1 - a) * (1 - b) * qRed(bottomRight) + 0.5);
-                    green = static_cast<int>(a * b * qGreen(topLeft)
-                                             + a * (1 - b) * qGreen(bottomLeft)
-                                             + (1 - a) * b * qGreen(topRight)
-                                             + (1 - a) * (1 - b) * qGreen(bottomRight) + 0.5);
-                    blue = static_cast<int>(a * b * qBlue(topLeft)
-                                            + a * (1 - b) * qBlue(bottomLeft)
-                                            + (1 - a) * b * qBlue(topRight)
-                                            + (1 - a) * (1 - b) * qBlue(bottomRight) + 0.5);
-                    alpha = 255;
-                }
-
-                if (red < 0) {
-                    red = 0;
-                } else if (red > 255) {
-                    red = 255;
-                }
-
-                if (green < 0) {
-                    green = 0;
-                } else if (green > 255) {
-                    green = 255;
-                }
-
-                if (blue < 0) {
-                    blue = 0;
-                } else if (blue > 255) {
-                    blue = 255;
-                }
-
-                if (alpha < 0) {
-                    alpha = 0;
-                } else if (alpha > 255) {
-                    alpha = 255;
-                }
-
-                dstPixel[dstX] = qRgba(red, green, blue, alpha);
-            }
-        }
-
-        //filename = QString("bilinear_%1x%2.png").arg(width).arg(height);
-    }
-
-    //scaledImage.save(filename, "PNG");
-
-    return scaledImage;
-}
-
-void KisBrush::findScaledBrushes(double scale, const KisScaledBrush **aboveBrush, const KisScaledBrush **belowBrush) const
-{
-    int current = 0;
-
-    while (true) {
-        *aboveBrush = &(d->scaledBrushes[current]);
-
-        if (Eigen::ei_isApprox(scale, (*aboveBrush)->scale())) {
-            // Scale matches exactly
-            break;
-        }
-
-        if (current == d->scaledBrushes.count() - 1) {
-            // This is the last one
-            break;
-        }
-
-        if (scale > d->scaledBrushes[current + 1].scale()) {
-            // We fit in between the two.
-            *belowBrush = &(d->scaledBrushes[current + 1]);
-            break;
-        }
-
-        current++;
-    }
-}
-
-KisQImagemaskSP KisBrush::scaleSinglePixelMask(double scale, quint8 maskValue, double subPixelX, double subPixelY)
-{
-    int srcWidth = 1;
-    int srcHeight = 1;
-    int dstWidth = 2;
-    int dstHeight = 2;
-    KisQImagemaskSP outputMask = KisQImagemaskSP(new KisQImagemask(dstWidth, dstHeight));
-    Q_CHECK_PTR(outputMask);
-
-    double a = subPixelX;
-    double b = subPixelY;
-
-    for (int y = 0; y < dstHeight; y++) {
-
-        for (int x = 0; x < dstWidth; x++) {
-
-            quint8 topLeft = (x > 0 && y > 0) ? maskValue : OPACITY_TRANSPARENT_U8;
-            quint8 bottomLeft = (x > 0 && y < srcHeight) ? maskValue : OPACITY_TRANSPARENT_U8;
-            quint8 topRight = (x < srcWidth && y > 0) ? maskValue : OPACITY_TRANSPARENT_U8;
-            quint8 bottomRight = (x < srcWidth && y < srcHeight) ? maskValue : OPACITY_TRANSPARENT_U8;
-
-            // Bi-linear interpolation
-            int d = static_cast<int>(a * b * topLeft
-                                     + a * (1 - b) * bottomLeft
-                                     + (1 - a) * b * topRight
-                                     + (1 - a) * (1 - b) * bottomRight + 0.5);
-
-            // Multiply by the square of the scale because a 0.5x0.5 pixel
-            // has 0.25 the value of the 1x1.
-            d = static_cast<int>(d * scale * scale + 0.5);
-
-            if (d < OPACITY_TRANSPARENT_U8) {
-                d = OPACITY_TRANSPARENT_U8;
-            } else if (d > OPACITY_OPAQUE_U8) {
-                d = OPACITY_OPAQUE_U8;
-            }
-
-            outputMask->setAlphaAt(x, y, static_cast<quint8>(d));
-        }
-    }
-
-    return outputMask;
-}
-
-QImage KisBrush::scaleSinglePixelImage(double scale, QRgb pixel, double subPixelX, double subPixelY)
-{
-    int srcWidth = 1;
-    int srcHeight = 1;
-    int dstWidth = 2;
-    int dstHeight = 2;
-
-    QImage outputImage(dstWidth, dstHeight, QImage::Format_ARGB32);
-
-    double a = subPixelX;
-    double b = subPixelY;
-
-    for (int y = 0; y < dstHeight; y++) {
-
-        QRgb *dstPixel = reinterpret_cast<QRgb *>(outputImage.scanLine(y));
-
-        for (int x = 0; x < dstWidth; x++) {
-
-            QRgb topLeft = (x > 0 && y > 0) ? pixel : qRgba(0, 0, 0, 0);
-            QRgb bottomLeft = (x > 0 && y < srcHeight) ? pixel : qRgba(0, 0, 0, 0);
-            QRgb topRight = (x < srcWidth && y > 0) ? pixel : qRgba(0, 0, 0, 0);
-            QRgb bottomRight = (x < srcWidth && y < srcHeight) ? pixel : qRgba(0, 0, 0, 0);
-
-            // Bi-linear interpolation. Images are in pre-multiplied form.
-            int red = static_cast<int>(a * b * qRed(topLeft)
-                                       + a * (1 - b) * qRed(bottomLeft)
-                                       + (1 - a) * b * qRed(topRight)
-                                       + (1 - a) * (1 - b) * qRed(bottomRight) + 0.5);
-            int green = static_cast<int>(a * b * qGreen(topLeft)
-                                         + a * (1 - b) * qGreen(bottomLeft)
-                                         + (1 - a) * b * qGreen(topRight)
-                                         + (1 - a) * (1 - b) * qGreen(bottomRight) + 0.5);
-            int blue = static_cast<int>(a * b * qBlue(topLeft)
-                                        + a * (1 - b) * qBlue(bottomLeft)
-                                        + (1 - a) * b * qBlue(topRight)
-                                        + (1 - a) * (1 - b) * qBlue(bottomRight) + 0.5);
-            int alpha = static_cast<int>(a * b * qAlpha(topLeft)
-                                         + a * (1 - b) * qAlpha(bottomLeft)
-                                         + (1 - a) * b * qAlpha(topRight)
-                                         + (1 - a) * (1 - b) * qAlpha(bottomRight) + 0.5);
-
-            // Multiply by the square of the scale because a 0.5x0.5 pixel
-            // has 0.25 the value of the 1x1.
-            alpha = static_cast<int>(alpha * scale * scale + 0.5);
-
-            // Apply to the color channels too since we are
-            // storing pre-multiplied by alpha.
-            red = static_cast<int>(red * scale * scale + 0.5);
-            green = static_cast<int>(green * scale * scale + 0.5);
-            blue = static_cast<int>(blue * scale * scale + 0.5);
-
-            if (red < 0) {
-                red = 0;
-            } else if (red > 255) {
-                red = 255;
-            }
-
-            if (green < 0) {
-                green = 0;
-            } else if (green > 255) {
-                green = 255;
-            }
-
-            if (blue < 0) {
-                blue = 0;
-            } else if (blue > 255) {
-                blue = 255;
-            }
-
-            if (alpha < 0) {
-                alpha = 0;
-            } else if (alpha > 255) {
-                alpha = 255;
-            }
-
-            dstPixel[x] = qRgba(red, green, blue, alpha);
-        }
-    }
-
-    return outputImage;
-}
-
-QImage KisBrush::interpolate(const QImage& image1, const QImage& image2, double t)
-{
-    Q_ASSERT((image1.width() == image2.width()) && (image1.height() == image2.height()));
-
-    int width = image1.width();
-    int height = image1.height();
-
-    QImage outputImage(width, height, QImage::Format_ARGB32);
-
-    for (int y = 0; y < height; y++) {
-        QRgb *dstPixel = reinterpret_cast<QRgb *>(outputImage.scanLine(y));
-        for (int x = 0; x < width; x++) {
-            QRgb image1pixel = image1.pixel(x, y);
-            QRgb image2pixel = image2.pixel(x, y);
-
-            // Images are in pre-multiplied alpha format.
-            int red = static_cast<int>((1 - t) * qRed(image1pixel) + t * qRed(image2pixel) + 0.5);
-            int green = static_cast<int>((1 - t) * qGreen(image1pixel) + t * qGreen(image2pixel) + 0.5);
-            int blue = static_cast<int>((1 - t) * qBlue(image1pixel) + t * qBlue(image2pixel) + 0.5);
-            int alpha = static_cast<int>((1 - t) * qAlpha(image1pixel) + t * qAlpha(image2pixel) + 0.5);
-
-            if (red < 0) {
-                red = 0;
-            } else if (red > 255) {
-                red = 255;
-            }
-
-            if (green < 0) {
-                green = 0;
-            } else if (green > 255) {
-                green = 255;
-            }
-
-            if (blue < 0) {
-                blue = 0;
-            } else if (blue > 255) {
-                blue = 255;
-            }
-
-            if (alpha < 0) {
-                alpha = 0;
-            } else if (alpha > 255) {
-                alpha = 255;
-            }
-
-            dstPixel[x] = qRgba(red, green, blue, alpha);
-        }
-    }
-
-    return outputImage;
-}
-
 void KisBrush::resetBoundary()
 {
     delete d->boundary;
diff --git a/krita/plugins/paintops/libbrush/kis_brush.h b/krita/plugins/paintops/libbrush/kis_brush.h
index eb56136..0464701 100644
--- a/krita/plugins/paintops/libbrush/kis_brush.h
+++ b/krita/plugins/paintops/libbrush/kis_brush.h
@@ -313,9 +313,9 @@ protected:
      */
     virtual void setBrushType(enumBrushType type);
 
-    void clearScaledBrushes();
-
-    void createScaledBrushes() const;
+    friend class KisBrushTest;
+    void prepareBrushPyramid() const;
+    void clearBrushPyramid();
 
     virtual void setHasColor(bool hasColor);
 
@@ -328,29 +328,6 @@ protected:
 private:
     friend class KisImagePipeBrushTest;
 
-    KisQImagemaskSP createMask(double scale, double subPixelX, double subPixelY) const;
-
-    KisQImagemaskSP scaleMask(const KisScaledBrush *srcBrush,
-                              double scale, double subPixelX, double subPixelY) const;
-
-    QImage scaleImage(const KisScaledBrush *srcBrush,
-                      double scale, double subPixelX, double subPixelY) const;
-
-    static QImage scaleImage(const QImage& srcImage, int width, int height);
-
-    static QImage interpolate(const QImage& image1, const QImage& image2, double t);
-
-    static KisQImagemaskSP scaleSinglePixelMask(double scale, quint8 maskValue,
-                                                double subPixelX, double subPixelY);
-
-    static QImage scaleSinglePixelImage(double scale, QRgb pixel,
-                                        double subPixelX, double subPixelY);
-
-    // Find the scaled brush(es) nearest to the given scale.
-    void findScaledBrushes(double scale,
-                           const KisScaledBrush **aboveBrush,
-                           const KisScaledBrush **belowBrush) const;
-
     // Initialize our boundary
     void generateBoundary() const;
 
diff --git a/krita/plugins/paintops/libbrush/kis_gbr_brush.cpp b/krita/plugins/paintops/libbrush/kis_gbr_brush.cpp
index 4305a4c..b68a6d4 100644
--- a/krita/plugins/paintops/libbrush/kis_gbr_brush.cpp
+++ b/krita/plugins/paintops/libbrush/kis_gbr_brush.cpp
@@ -27,7 +27,6 @@
 #include "kis_gbr_brush.h"
 
 #include "kis_brush.h"
-#include "kis_qimage_mask.h"
 
 #include <QDomElement>
 #include <QFile>
@@ -464,7 +463,7 @@ void KisGbrBrush::makeMaskImage()
     setHasColor(false);
     setUseColorAsMask(false);
     resetBoundary();
-    clearScaledBrushes();
+    clearBrushPyramid();
 }
 
 KisGbrBrush* KisGbrBrush::clone() const
@@ -487,7 +486,7 @@ void KisGbrBrush::setUseColorAsMask(bool useColorAsMask)
 {
     d->useColorAsMask = useColorAsMask;
     resetBoundary();
-    clearScaledBrushes();
+    clearBrushPyramid();
 }
 bool KisGbrBrush::useColorAsMask() const
 {
diff --git a/krita/plugins/paintops/libbrush/kis_qimage_mask.cpp b/krita/plugins/paintops/libbrush/kis_qimage_mask.cpp
deleted file mode 100644
index e08012a..0000000
--- a/krita/plugins/paintops/libbrush/kis_qimage_mask.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- *  Copyright (c) 2004-2008 Boudewijn Rempt <boud at valdyas.org>
- *  Copyright (c) 2010 Lukáš Tvrdý <lukast.dev 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_qimage_mask.h"
-
-#include <cfloat>
-#include <QImage>
-
-#include <KoColorSpace.h>
-
-#include <kis_debug.h>
-#include "kis_global.h"
-
-KisQImagemask::KisQImagemask(const QImage& image, bool hasColor)
-{
-    init(image.width(), image.height());
-    
-    if (hasColor) {
-        computeAlphaMaskFromRGBA(image);
-    } else {
-        computeAlphaMaskFromGrayScale(image);
-    }
-}
-
-KisQImagemask::KisQImagemask(const QImage& image)
-{
-    init(image.width(), image.height());
-    
-    if (!image.allGray()) {
-        computeAlphaMaskFromRGBA(image);
-    } else {
-        computeAlphaMaskFromGrayScale(image);
-    }
-}
-
-KisQImagemask::KisQImagemask(qint32 width, qint32 height, bool initialize)
-{
-    init(width,height);
-    if (initialize){
-        m_data.fill(0);
-    }
-}
-
-inline void KisQImagemask::init(int width, int height)
-{
-    m_width = width;
-    m_height = height;
-    m_data = QImage(m_width,m_height,QImage::Format_Indexed8);
-    m_dataPtr = m_data.bits();
-    m_bytesPerLine = m_data.bytesPerLine();
-    
-}
-
-
-KisQImagemask::~KisQImagemask()
-{
-}
-
-void KisQImagemask::computeAlphaMaskFromRGBA(const QImage& image)
-{
-    uchar * data;
-    int height = image.height();
-    int width = image.width();
-    for (int y = 0; y < height; y++) {
-        const QRgb *scanline = reinterpret_cast<const QRgb *>(image.scanLine(y));
-        data = m_data.scanLine(y);
-        for (int x = 0; x < width; x++) {
-            QRgb c = scanline[x];
-            quint8 a = ((255 - qGray(c)) * qAlpha(c)) / 255;
-            data[x] = a;
-        }
-    }
-}
-
-void KisQImagemask::computeAlphaMaskFromGrayScale(const QImage& image)
-{
-    // The brushes are mostly grayscale on a white background,
-    // although some do have a colors. The alpha channel is seldom
-    // used, so we take the average gray value of this pixel of
-    // the brush as the setting for the opacitiy. We need to
-    // invert it, because 255, 255, 255 is white, which is
-    // completely transparent, but 255 corresponds to
-    // OPACITY_OPAQUE.
-    int height = image.height();
-    int width = image.width();
-    uchar * data;
-    for (int y = 0; y < height; y++) {
-        const QRgb *scanline = reinterpret_cast<const QRgb *>(image.scanLine(y));
-        data = m_data.scanLine(y);
-        for (int x = 0; x < width; x++) {
-            data[x] = (255 - qRed(scanline[x]));
-        }
-    }
-}
-
-void KisQImagemask::rotation(double angle)
-{
-    // For some reason rotating an Indexed8 image is broken so convert to RGB32
-    // Would probably be faster to have a native own implementation
-    QVector<QRgb> table;
-    for (int i = 0; i < 256; ++i) {
-        table.append(qRgb(i, i, i));
-    }
-    m_data.setColorTable(table);
-    
-    QImage tmp = m_data.convertToFormat(QImage::Format_RGB32);
-    tmp = tmp.transformed(QTransform().rotate(-angle * 180 / M_PI), Qt::SmoothTransformation);
-    
-    init(tmp.width(), tmp.height());
-
-    // Do not use convertToFormat to go back to Indexed8, since it is quiet
-    // a slow general operation, while we know that we are outputting a grayscale image
-    for (int y = 0; y < tmp.height(); ++y) {
-        for (int x = 0; x < tmp.width(); ++x) {
-            m_data.scanLine(y)[x] = tmp.scanLine(y)[4 * x];
-        }
-    }
-    
-}
diff --git a/krita/plugins/paintops/libbrush/kis_qimage_mask.h b/krita/plugins/paintops/libbrush/kis_qimage_mask.h
deleted file mode 100644
index f29616c..0000000
--- a/krita/plugins/paintops/libbrush/kis_qimage_mask.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- *  Copyright (c) 2004-2008 Boudewijn Rempt <boud at valdyas.org>
- *  Copyright (c) 2010 Lukáš Tvrdý <lukast.dev 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_QIMAGE_MASK_H_
-#define KIS_QIMAGE_MASK_H_
-
-#include "kis_gbr_brush.h"
-
-#include <QImage>
-
-#include <KoColorSpace.h>
-
-#include "kis_global.h"
-#include "kis_types.h"
-#include <kis_shared.h>
-
-/**
- * KisQImagemask is intended to create alpha values from a QImage for use
- * in brush creation. It is not a generic alpha mask that can be used with
- * KisPaintDevices: use a KisSelection for that.
- */
-class KisQImagemask : public KisShared
-{
-
-public:
-    /**
-       Create an alpha mask based on the specified QImage. If the image is
-       not a grayscale, the mask value is calculated from the effective grey
-       level and alpha value.
-    */
-    KisQImagemask(const QImage& image);
-
-    /**
-       As above except quicker as the image does not need to be scanned
-       to see if it has any color pixels.
-    */
-    KisQImagemask(const QImage& image, bool hasColor);
-
-    /**
-       Create a transparent mask.
-    */
-    KisQImagemask(qint32 width, qint32 height, bool initialize = true);
-
-    virtual ~KisQImagemask();
-
-
-    
-    /**
-     * @return the number of lines in the mask.
-     */
-    qint32 height() const {
-        return m_height;
-    }
-
-    /**
-     * @return the number of alpha values in a scanline.
-     */
-    qint32 width() const{
-        return m_width;
-    }
-
-    /**
-     * Return the alpha mask bytes
-     */
-    inline quint8 * scanline(int y) {
-        return m_data.scanLine(y);
-    }
-
-    /**
-       @return the alpha value at the specified position.
-
-       Returns quint8 OPACITY_TRANSPARENT if the value is
-       outside the bounds of the mask.
-       
-       Now it is much faster, we cache the pointer to data
-       in storage (QImage) instead of calling scanlines which
-       was slow
-
-       XXX: this is, of course, not the best way of masking.
-       Better would be to let KisQImagemask fill a chunk of memory
-       with the alpha values at the right position, something like
-       void applyMask(quint8 *pixeldata, qint32 pixelWidth,
-       qint32 alphaPos). That would be fastest, or we could
-       provide an iterator over the mask, that would be nice, too.
-     */
-    inline quint8 alphaAt(qint32 x, qint32 y) const {
-        if (y >= 0 && y < m_height && x >= 0 && x < m_width) {
-            return m_dataPtr[(y * m_bytesPerLine + x)];
-        } else {
-            return OPACITY_TRANSPARENT_U8;
-        }
-    }
-
-    inline void setAlphaAt(qint32 x, qint32 y, quint8 alpha) {
-        if (y >= 0 && y < m_height && x >= 0 && x < m_width) {
-            m_dataPtr[(y * m_bytesPerLine + x)] = alpha;
-        }
-    }
-
-    /**
-     * Apply rotation to the mask
-     */
-    void rotation(double angle);
-
-
-private:
-    /// init the internal storage (QImage)
-    void init(int width, int height);
-    void computeAlphaMaskFromGrayScale(const QImage& image);
-    void computeAlphaMaskFromRGBA(const QImage& image);
-
-private:    
-    int m_width;
-    int m_height;
-    QImage m_data;
-    
-    int m_bytesPerLine;
-    uchar * m_dataPtr;
-
-};
-
-#endif // KIS_ALPHA_MASK_
-
diff --git a/krita/plugins/paintops/libbrush/kis_qimage_pyramid.cpp b/krita/plugins/paintops/libbrush/kis_qimage_pyramid.cpp
new file mode 100644
index 0000000..d0acbb6
--- /dev/null
+++ b/krita/plugins/paintops/libbrush/kis_qimage_pyramid.cpp
@@ -0,0 +1,196 @@
+/*
+ *  Copyright (c) 2013 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_qimage_pyramid.h"
+
+#include <QPainter>
+#include <kis_debug.h>
+
+#define MIPMAP_SIZE_THRESHOLD 512
+#define MAX_MIPMAP_SCALE 8.0
+
+KisQImagePyramid::KisQImagePyramid(const QImage &baseImage)
+{
+    Q_ASSERT(!baseImage.isNull());
+
+    m_baseSize = baseImage.size();
+
+
+    qreal scale = MAX_MIPMAP_SCALE;
+
+    while (scale > 1.0) {
+        QSize scaledSize = m_baseSize * scale;
+
+        if (scaledSize.width() <= MIPMAP_SIZE_THRESHOLD ||
+            scaledSize.height() <= MIPMAP_SIZE_THRESHOLD) {
+
+            if (m_levels.isEmpty()) {
+                m_baseScale = scale;
+            }
+
+            m_levels.append(baseImage.scaled(scaledSize,  Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+        }
+
+        scale *= 0.5;
+    }
+
+    if (m_levels.isEmpty()) {
+        m_baseScale = 1.0;
+    }
+    m_levels.append(baseImage);
+
+    scale = 0.5;
+    while (true) {
+        QSize scaledSize = m_baseSize * scale;
+
+        if (scaledSize.width() == 0 ||
+            scaledSize.height() == 0) break;
+
+        m_levels.append(baseImage.scaled(scaledSize,  Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+
+        scale *= 0.5;
+    }
+}
+
+KisQImagePyramid::~KisQImagePyramid()
+{
+}
+
+int KisQImagePyramid::findNearestLevel(qreal scale, qreal *baseScale)
+{
+    qreal levelScale = m_baseScale;
+    int level = 0;
+    int lastLevel = m_levels.size() - 1;
+
+    while (0.5 * levelScale > scale && level < lastLevel) {
+        levelScale *= 0.5;
+        level++;
+    }
+
+    *baseScale = levelScale;
+    return level;
+}
+
+inline QRect roundRect(const QRectF &rc) {
+    /**
+     * This is an analog of toAlignedRect() with the only difference
+     * that it rounds corner values instead of doing floor/ceil.
+     *
+     * Warning: be *very* careful with using bottom()/right() values
+     *          of a pure QRect (we don't use it here for the dangers
+     *          it can lead to).
+     */
+
+    int left = qRound(rc.left());
+    int right = qRound(rc.right());
+
+    int top = qRound(rc.top());
+    int bottom = qRound(rc.bottom());
+
+    return QRect(left, top, right - left, bottom - top);
+}
+
+void KisQImagePyramid::calculateParams(qreal scale, qreal rotation,
+                                       qreal subPixelX, qreal subPixelY,
+                                       qreal baseScale, const QSize &baseSize,
+                                       QTransform *outputTransform, QSize *outputSize)
+{
+    QTransform transform;
+    QRectF baseBounds = QRectF(QPointF(), baseSize);
+
+    qreal scaleX = scale / baseScale;
+    qreal scaleY = scale / baseScale;
+
+    if (!qFuzzyCompare(rotation, 0)) {
+        QTransform rotationTransform;
+        rotationTransform.rotateRadians(rotation);
+
+        QRectF rotatedBounds = rotationTransform.mapRect(baseBounds);
+        transform = QTransform().rotateRadians(rotation) *
+            QTransform::fromTranslate(-rotatedBounds.x(), -rotatedBounds.y());
+    } else {
+        QRectF scaledRect = QTransform::fromScale(scaleX, scaleY).mapRect(baseBounds).toAlignedRect();
+        scaleX = scaledRect.width() / baseBounds.width();
+        scaleY = scaledRect.height() / baseBounds.height();
+    }
+
+
+    transform *= QTransform::fromScale(scaleX, scaleY) *
+        QTransform::fromTranslate(subPixelX, subPixelY);
+
+    QRect dstRect = roundRect(transform.mapRect(baseBounds));
+    Q_ASSERT(dstRect.x() >= 0);
+    Q_ASSERT(dstRect.y() >= 0);
+
+    int width = dstRect.x() + dstRect.width();
+    int height = dstRect.y() + dstRect.height();
+
+    // we should not return invalid image, so adjust the image to be
+    // at least 1 px in size.
+    width = qMax(1, width);
+    height = qMax(1, height);
+
+    *outputTransform = transform;
+    *outputSize = QSize(width, height);
+}
+
+QSize KisQImagePyramid::imageSize(const QSize &baseSize,
+                                  qreal scale, qreal rotation,
+                                  qreal subPixelX, qreal subPixelY)
+{
+    QTransform transform;
+    QSize dstSize;
+
+    calculateParams(scale, rotation, subPixelX, subPixelY,
+                    1.0, baseSize,
+                    &transform, &dstSize);
+
+    return dstSize;
+}
+
+QImage KisQImagePyramid::createImage(qreal scale, qreal rotation,
+                                     qreal subPixelX, qreal subPixelY)
+{
+    qreal baseScale = -1.0;
+    int level = findNearestLevel(scale, &baseScale);
+
+    const QImage &srcImage = m_levels[level];
+
+    QTransform transform;
+    QSize dstSize;
+
+    calculateParams(scale, rotation, subPixelX, subPixelY,
+                    baseScale, srcImage.size(),
+                    &transform, &dstSize);
+
+    if (transform.isIdentity()) {
+        return srcImage;
+    }
+
+    QImage dstImage(dstSize, QImage::Format_ARGB32);
+    dstImage.fill(0);
+
+    QPainter gc(&dstImage);
+    gc.setTransform(transform);
+    gc.setRenderHints(QPainter::SmoothPixmapTransform);
+    gc.drawImage(QPointF(), srcImage);
+    gc.end();
+
+    return dstImage;
+}
+
diff --git a/krita/plugins/paintops/libbrush/kis_qimage_pyramid.h b/krita/plugins/paintops/libbrush/kis_qimage_pyramid.h
new file mode 100644
index 0000000..1f0367c
--- /dev/null
+++ b/krita/plugins/paintops/libbrush/kis_qimage_pyramid.h
@@ -0,0 +1,52 @@
+/*
+ *  Copyright (c) 2013 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_QIMAGE_PYRAMID_H
+#define __KIS_QIMAGE_PYRAMID_H
+
+#include <QImage>
+#include <QVector>
+
+
+class KisQImagePyramid
+{
+public:
+    KisQImagePyramid(const QImage &baseImage);
+    ~KisQImagePyramid();
+
+    static QSize imageSize(const QSize &baseSize,
+                           qreal scale, qreal rotation,
+                           qreal subPixelX, qreal subPixelY);
+
+    QImage createImage(qreal scale, qreal rotation,
+                       qreal subPixelX, qreal subPixelY);
+
+private:
+    int findNearestLevel(qreal scale, qreal *baseScale);
+    static void calculateParams(qreal scale, qreal rotation,
+                                qreal subPixelX, qreal subPixelY,
+                                qreal baseScale, const QSize &baseSize,
+                                QTransform *outputTransform, QSize *outputSize);
+
+private:
+    QSize m_baseSize;
+    qreal m_baseScale;
+    QVector<QImage> m_levels;
+};
+
+#endif /* __KIS_QIMAGE_PYRAMID_H */
diff --git a/krita/plugins/paintops/libbrush/kis_scaled_brush.cpp b/krita/plugins/paintops/libbrush/kis_scaled_brush.cpp
deleted file mode 100644
index b604eaf..0000000
--- a/krita/plugins/paintops/libbrush/kis_scaled_brush.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- *  Copyright (c) 2008 Boudewijn Rempt <boud at valdyas.org>
- *
- *  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_scaled_brush.h"
-
-KisScaledBrush::KisScaledBrush()
-{
-    m_mask = 0;
-    m_image = QImage();
-    m_scale = 1;
-    m_xScale = 1;
-    m_yScale = 1;
-}
-
-KisScaledBrush::KisScaledBrush(KisQImagemaskSP scaledMask, const QImage& scaledImage, double scale, double xScale, double yScale)
-{
-    m_mask = scaledMask;
-    m_image = scaledImage;
-    m_scale = scale;
-    m_xScale = xScale;
-    m_yScale = yScale;
-
-    if (!m_image.isNull()) {
-        // Convert image to pre-multiplied by alpha.
-
-        m_image.detach();
-        for (int y = 0; y < m_image.height(); y++) {
-
-            QRgb *imagePixelPtr = reinterpret_cast<QRgb *>(m_image.scanLine(y));
-            const QRgb *scanline = reinterpret_cast<const QRgb *>(scaledImage.scanLine(y));
-
-            for (int x = 0; x < m_image.width(); x++) {
-                QRgb pixel = scanline[x];
-                int red = qRed(pixel);
-                int green = qGreen(pixel);
-                int blue = qBlue(pixel);
-                int alpha = qAlpha(pixel);
-
-                red = (red * alpha) / 255;
-                green = (green * alpha) / 255;
-                blue = (blue * alpha) / 255;
-
-                *imagePixelPtr = qRgba(red, green, blue, alpha);
-                ++imagePixelPtr;
-            }
-        }
-
-    }
-}
diff --git a/krita/plugins/paintops/libbrush/kis_scaled_brush.h b/krita/plugins/paintops/libbrush/kis_scaled_brush.h
deleted file mode 100644
index 15e3f6f..0000000
--- a/krita/plugins/paintops/libbrush/kis_scaled_brush.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- *  Copyright (c) 2008 Boudewijn Rempt <boud at valdyas.org>
- *
- *  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_SCALED_BRUSH_H
-#define KIS_SCALED_BRUSH_H
-
-#include <QImage>
-
-#include <kis_types.h>
-
-#include "kis_qimage_mask.h"
-
-class KisScaledBrush
-{
-
-public:
-
-    KisScaledBrush();
-
-    KisScaledBrush(KisQImagemaskSP scaledMask,
-                   const QImage& scaledImage,
-                   double scale, double xScale, double yScale);
-
-    double scale() const {
-        return m_scale;
-    }
-    double xScale() const {
-        return m_xScale;
-    }
-    double yScale() const {
-        return m_yScale;
-    }
-    KisQImagemaskSP mask() const {
-        return m_mask;
-    }
-    QImage image() const {
-        return m_image;
-    }
-
-private:
-    KisQImagemaskSP m_mask;
-    QImage m_image;
-    double m_scale;
-    double m_xScale;
-    double m_yScale;
-};
-
-#endif
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_0_sc_0.871853_rot_3.55731_sub_0.137199.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_0_sc_0.871853_rot_3.55731_sub_0.137199.png
new file mode 100644
index 0000000..8749911
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_0_sc_0.871853_rot_3.55731_sub_0.137199.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_1_sc_0.861342_rot_3.45867_sub_0.20933.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_1_sc_0.861342_rot_3.45867_sub_0.20933.png
new file mode 100644
index 0000000..a854e35
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_1_sc_0.861342_rot_3.45867_sub_0.20933.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_2_sc_1.80941_rot_4.97706_sub_0.113862.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_2_sc_1.80941_rot_4.97706_sub_0.113862.png
new file mode 100644
index 0000000..5b0a2ea
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_2_sc_1.80941_rot_4.97706_sub_0.113862.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_3_sc_0.46124_rot_4.18791_sub_0.167627.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_3_sc_0.46124_rot_4.18791_sub_0.167627.png
new file mode 100644
index 0000000..d826f28
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_3_sc_0.46124_rot_4.18791_sub_0.167627.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_4_sc_0.963137_rot_2.82314_sub_0.444059.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_4_sc_0.963137_rot_2.82314_sub_0.444059.png
new file mode 100644
index 0000000..e17d4c5
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_4_sc_0.963137_rot_2.82314_sub_0.444059.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_5_sc_0.592981_rot_0.439579_sub_0.45074.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_5_sc_0.592981_rot_0.439579_sub_0.45074.png
new file mode 100644
index 0000000..eb3f53f
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_5_sc_0.592981_rot_0.439579_sub_0.45074.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_6_sc_1.86775_rot_3.12606_sub_0.365777.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_6_sc_1.86775_rot_3.12606_sub_0.365777.png
new file mode 100644
index 0000000..0c3f572
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_6_sc_1.86775_rot_3.12606_sub_0.365777.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_7_sc_1.13937_rot_3.47854_sub_0.458733.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_7_sc_1.13937_rot_3.47854_sub_0.458733.png
new file mode 100644
index 0000000..82a4af3
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_7_sc_1.13937_rot_3.47854_sub_0.458733.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_8_sc_1.53198_rot_3.68719_sub_0.410264.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_8_sc_1.53198_rot_3.68719_sub_0.410264.png
new file mode 100644
index 0000000..c909d6d
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_8_sc_1.53198_rot_3.68719_sub_0.410264.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_9_sc_1.46566_rot_2.88023_sub_0.474873.png b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_9_sc_1.46566_rot_2.88023_sub_0.474873.png
new file mode 100644
index 0000000..eb8490f
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/brush_masks/_brush_9_sc_1.46566_rot_2.88023_sub_0.474873.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/result_autobrush_3.png b/krita/plugins/paintops/libbrush/tests/data/result_autobrush_3.png
index f588410..d54aa58 100644
Binary files a/krita/plugins/paintops/libbrush/tests/data/result_autobrush_3.png and b/krita/plugins/paintops/libbrush/tests/data/result_autobrush_3.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/result_autobrush_4.png b/krita/plugins/paintops/libbrush/tests/data/result_autobrush_4.png
index 846c920..a8dbd8d 100644
Binary files a/krita/plugins/paintops/libbrush/tests/data/result_autobrush_4.png and b/krita/plugins/paintops/libbrush/tests/data/result_autobrush_4.png differ
diff --git a/krita/plugins/paintops/libbrush/tests/data/testing_brush_512_bars.gbr b/krita/plugins/paintops/libbrush/tests/data/testing_brush_512_bars.gbr
new file mode 100644
index 0000000..eaa892f
Binary files /dev/null and b/krita/plugins/paintops/libbrush/tests/data/testing_brush_512_bars.gbr differ
diff --git a/krita/plugins/paintops/libbrush/tests/kis_auto_brush_test.cpp b/krita/plugins/paintops/libbrush/tests/kis_auto_brush_test.cpp
index 568fbb2..45e00c2 100644
--- a/krita/plugins/paintops/libbrush/tests/kis_auto_brush_test.cpp
+++ b/krita/plugins/paintops/libbrush/tests/kis_auto_brush_test.cpp
@@ -98,24 +98,24 @@ void KisAutoBrushTest::testSizeRotation()
         KisBrushSP a = new KisAutoBrush(circle, 0.0, 0.0);
         QCOMPARE(a->width(), 10);
         QCOMPARE(a->height(), 5);
-        QCOMPARE(a->maskWidth(1.0,0.0, KisPaintInformation()), 11);
-        QCOMPARE(a->maskHeight(1.0,0.0, KisPaintInformation()), 6);
-        QCOMPARE(a->maskWidth(2.0,0.0, KisPaintInformation()), 21);
-        QCOMPARE(a->maskHeight(2.0,0.0, KisPaintInformation()), 11);
-        QCOMPARE(a->maskWidth(0.5,0.0, KisPaintInformation()), 6);
-        QCOMPARE(a->maskHeight(0.5,0.0, KisPaintInformation()), 4);
-        QCOMPARE(a->maskWidth(1.0,M_PI, KisPaintInformation()), 11);
-        QCOMPARE(a->maskHeight(1.0,M_PI, KisPaintInformation()), 6);
-        QCOMPARE(a->maskWidth(1.0,M_PI_2, KisPaintInformation()), 6);
-        QCOMPARE(a->maskHeight(1.0,M_PI_2, KisPaintInformation()), 11);
-        QCOMPARE(a->maskWidth(1.0,-M_PI_2, KisPaintInformation()), 6);
-        QCOMPARE(a->maskHeight(1.0,-M_PI_2, KisPaintInformation()), 11);
-        QCOMPARE(a->maskWidth(1.0,0.25*M_PI, KisPaintInformation()), 12);
-        QCOMPARE(a->maskHeight(1.0,0.25*M_PI, KisPaintInformation()), 12);
-        QCOMPARE(a->maskWidth(2.0,0.25*M_PI, KisPaintInformation()), 23);
-        QCOMPARE(a->maskHeight(2.0,0.25*M_PI, KisPaintInformation()), 23);
-        QCOMPARE(a->maskWidth(0.5,0.25*M_PI, KisPaintInformation()), 7);
-        QCOMPARE(a->maskHeight(0.5,0.25*M_PI, KisPaintInformation()), 7);
+        QCOMPARE(a->maskWidth(1.0,0.0, KisPaintInformation()), 10);
+        QCOMPARE(a->maskHeight(1.0,0.0, KisPaintInformation()), 5);
+        QCOMPARE(a->maskWidth(2.0,0.0, KisPaintInformation()), 20);
+        QCOMPARE(a->maskHeight(2.0,0.0, KisPaintInformation()), 10);
+        QCOMPARE(a->maskWidth(0.5,0.0, KisPaintInformation()), 5);
+        QCOMPARE(a->maskHeight(0.5,0.0, KisPaintInformation()), 3);
+        QCOMPARE(a->maskWidth(1.0,M_PI, KisPaintInformation()), 10);
+        QCOMPARE(a->maskHeight(1.0,M_PI, KisPaintInformation()), 5);
+        QCOMPARE(a->maskWidth(1.0,M_PI_2, KisPaintInformation()), 5);
+        QCOMPARE(a->maskHeight(1.0,M_PI_2, KisPaintInformation()), 10);
+        QCOMPARE(a->maskWidth(1.0,-M_PI_2, KisPaintInformation()), 5);
+        QCOMPARE(a->maskHeight(1.0,-M_PI_2, KisPaintInformation()), 10);
+        QCOMPARE(a->maskWidth(1.0,0.25*M_PI, KisPaintInformation()), 11);
+        QCOMPARE(a->maskHeight(1.0,0.25*M_PI, KisPaintInformation()), 11);
+        QCOMPARE(a->maskWidth(2.0,0.25*M_PI, KisPaintInformation()), 21);
+        QCOMPARE(a->maskHeight(2.0,0.25*M_PI, KisPaintInformation()), 21);
+        QCOMPARE(a->maskWidth(0.5,0.25*M_PI, KisPaintInformation()), 5);
+        QCOMPARE(a->maskHeight(0.5,0.25*M_PI, KisPaintInformation()), 5);
     }
 }
 
@@ -124,9 +124,10 @@ void KisAutoBrushTest::testCopyMasking()
 {
     int w = 64;
     int h = 64;
-
     int x = 0;
     int y = 0;
+    QRect rc(x,y,w,h);
+
     const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
 
     KoColor black(Qt::black, cs);
@@ -134,7 +135,7 @@ void KisAutoBrushTest::testCopyMasking()
 
 
     KisPaintDeviceSP tempDev = new KisPaintDevice(cs);
-    tempDev->fill(0, 0, w+1, h+1, red.data()); // see the TODO
+    tempDev->fill(0, 0, w, h, red.data());
 #ifdef SAVE_OUTPUT_IMAGES
     tempDev->convertToQImage(0).save("tempDev.png");
 #endif
@@ -143,20 +144,15 @@ void KisAutoBrushTest::testCopyMasking()
     KisAutoBrush brush(mask,0,0);
 
     KisFixedPaintDeviceSP maskDab = new KisFixedPaintDevice(cs);
-    brush.mask(maskDab,black,1,1,0,KisPaintInformation()); // grows to w+1, h+1
+    brush.mask(maskDab,black,1,1,0,KisPaintInformation());
     maskDab->convertTo(KoColorSpaceRegistry::instance()->alpha8());
 
 #ifdef SAVE_OUTPUT_IMAGES
     maskDab->convertToQImage(0,0,0,64,64).save("maskDab.png");
 #endif
 
-    QRect rc = tempDev->exactBounds();
-    //QRect maskRc = maskDab->bounds();
-
-    //TODO: if rc != maskRc, bitBltWithFixedSelection works wrong
-    //qDebug() << rc;
-    //qDebug() << maskRc;
-
+    QCOMPARE(tempDev->exactBounds(), rc);
+    QCOMPARE(maskDab->bounds(), rc);
 
     KisFixedPaintDeviceSP dev2fixed = new KisFixedPaintDevice(cs);
     dev2fixed->setRect(rc);
diff --git a/krita/plugins/paintops/libbrush/tests/kis_brush_test.cpp b/krita/plugins/paintops/libbrush/tests/kis_brush_test.cpp
index 29c64c6..92d607f 100644
--- a/krita/plugins/paintops/libbrush/tests/kis_brush_test.cpp
+++ b/krita/plugins/paintops/libbrush/tests/kis_brush_test.cpp
@@ -32,18 +32,6 @@
 #include "kis_vec.h"
 #include <kis_fixed_paint_device.h>
 
-void KisBrushTest::testCreation()
-{
-    const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
-    KisPaintDeviceSP dev = new KisPaintDevice(cs);
-    QImage image(512, 512, QImage::Format_ARGB32);
-
-    KisGbrBrush a(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr");
-    KisGbrBrush b(dev, 0, 0, 10, 10);
-    KisGbrBrush c(image, "bla");
-    KisGbrBrush d(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gih");
-}
-
 void KisBrushTest::testMaskGenerationNoColor()
 {
     KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr");
@@ -176,18 +164,104 @@ void KisBrushTest::testMaskGenerationDefaultColor()
 
 void KisBrushTest::testImageGeneration()
 {
-    KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr");
+    KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
     brush->load();
-    Q_ASSERT(brush->valid());
+    QVERIFY(!brush->image().isNull());
+    brush->prepareBrushPyramid();
+    qsrand(1);
+
     const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
     KisVector2D v2d = KisVector2D::Zero();
     KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, v2d, 0, 0);
+    KisFixedPaintDeviceSP dab;
+
+    for (int i = 0; i < 200; i++) {
+        qreal scale = qreal(qrand()) / RAND_MAX * 2.0;
+        qreal rotation = qreal(qrand()) / RAND_MAX * 2 * M_PI;
+        qreal subPixelX = qreal(qrand()) / RAND_MAX * 0.5;
+        QString testName =
+            QString("brush_%1_sc_%2_rot_%3_sub_%4")
+            .arg(i).arg(scale).arg(rotation).arg(subPixelX);
+
+        dab = brush->paintDevice(cs, scale, rotation, info, subPixelX);
+
+        /**
+         * Compare first 10 images. Others are tested for asserts only
+         */
+        if (i < 10) {
+            QImage result = dab->convertToQImage(0);
+            TestUtil::checkQImage(result, "brush_masks", "", testName);
+        }
+    }
+}
+
+void KisBrushTest::benchmarkPyramidCreation()
+{
+    KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
+    brush->load();
+    QVERIFY(!brush->image().isNull());
 
-    KisFixedPaintDeviceSP fdev = brush->paintDevice(cs, 1.0, 0.0, info);
+    QBENCHMARK {
+        brush->prepareBrushPyramid();
+        brush->clearBrushPyramid();
+    }
+}
 
-    fdev->convertToQImage(0).save("bla.png");
+void KisBrushTest::benchmarkScaling()
+{
+    KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
+    brush->load();
+    QVERIFY(!brush->image().isNull());
+    brush->prepareBrushPyramid();
+    qsrand(1);
 
-    delete brush;
+    const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
+    KisVector2D v2d = KisVector2D::Zero();
+    KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, v2d, 0, 0);
+    KisFixedPaintDeviceSP dab;
+
+    QBENCHMARK {
+        dab = brush->paintDevice(cs, qreal(qrand()) / RAND_MAX * 2.0, 0.0, info);
+        //dab->convertToQImage(0).save(QString("dab_%1_new_smooth.png").arg(i++));
+    }
+}
+
+void KisBrushTest::benchmarkRotation()
+{
+    KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
+    brush->load();
+    QVERIFY(!brush->image().isNull());
+    brush->prepareBrushPyramid();
+    qsrand(1);
+
+    const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
+    KisVector2D v2d = KisVector2D::Zero();
+    KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, v2d, 0, 0);
+    KisFixedPaintDeviceSP dab;
+
+    QBENCHMARK {
+        dab = brush->paintDevice(cs, 1.0, qreal(qrand()) / RAND_MAX * 2 * M_PI, info);
+    }
+}
+
+void KisBrushTest::benchmarkMaskScaling()
+{
+    KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
+    brush->load();
+    QVERIFY(!brush->image().isNull());
+    brush->prepareBrushPyramid();
+    qsrand(1);
+
+    const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
+    KisVector2D v2d = KisVector2D::Zero();
+    KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, v2d, 0, 0);
+    KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(cs);
+
+    QBENCHMARK {
+        KoColor c(Qt::black, cs);
+        qreal scale = qreal(qrand()) / RAND_MAX * 2.0;
+        brush->mask(dab, c, scale, scale, 0.0, info, 0.0, 0.0, 1.0);
+    }
 }
 
 QTEST_KDEMAIN(KisBrushTest, GUI)
diff --git a/krita/plugins/paintops/libbrush/tests/kis_brush_test.h b/krita/plugins/paintops/libbrush/tests/kis_brush_test.h
index d8efde0..f066da6 100644
--- a/krita/plugins/paintops/libbrush/tests/kis_brush_test.h
+++ b/krita/plugins/paintops/libbrush/tests/kis_brush_test.h
@@ -30,11 +30,16 @@ class KisBrushTest : public QObject
     void testMaskGenerationSingleColor();
     void testMaskGenerationDevColor();
     void testMaskGenerationDefaultColor();
-    void testImageGeneration();
 
 private slots:
 
-    void testCreation();
+    void testImageGeneration();
+
+    void benchmarkPyramidCreation();
+    void benchmarkScaling();
+    void benchmarkRotation();
+
+    void benchmarkMaskScaling();
 };
 
 #endif
diff --git a/krita/plugins/paintops/libbrush/tests/kis_imagepipe_brush_test.cpp b/krita/plugins/paintops/libbrush/tests/kis_imagepipe_brush_test.cpp
index 699e348..eaa1183 100644
--- a/krita/plugins/paintops/libbrush/tests/kis_imagepipe_brush_test.cpp
+++ b/krita/plugins/paintops/libbrush/tests/kis_imagepipe_brush_test.cpp
@@ -28,7 +28,6 @@
 #include <kis_paint_information.h>
 
 #include "kis_imagepipe_brush.h"
-#include "kis_qimage_mask.h"
 #include <kis_paint_device.h>
 #include <kis_painter.h>
 
@@ -82,10 +81,12 @@ inline void KisImagePipeBrushTest::checkConsistency(KisImagePipeBrush *brush)
 
     int maskWidth = brush->maskWidth(realScale, realAngle, info);
     int maskHeight = brush->maskHeight(realScale, realAngle, info);
-    KisQImagemaskSP outputMask = brush->testingGetCurrentBrush(info)->createMask(realScale, subPixelX, subPixelY);
 
-    QCOMPARE(maskWidth, outputMask->width());
-    QCOMPARE(maskHeight, outputMask->height());
+    const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
+    KisFixedPaintDeviceSP dev = brush->testingGetCurrentBrush(info)->paintDevice(cs, realScale, realAngle, info, subPixelX, subPixelY);
+
+    QCOMPARE(maskWidth, dev->bounds().width());
+    QCOMPARE(maskHeight, dev->bounds().height());
 
     KisBrush *newBrush = brush->testingGetCurrentBrush(info);
     QCOMPARE(oldBrush, newBrush);


More information about the kimageshop mailing list