[calligra/krita-chili-kazakov] krita: Implement proper bilinear interpolation for the Warp Transform

Dmitry Kazakov dimula73 at gmail.com
Fri Sep 12 21:36:38 UTC 2014


Git commit b0ff2a6ebe3e6aaa0892e381d19a07d6fd2e9aa8 by Dmitry Kazakov.
Committed on 12/09/2014 at 21:35.
Pushed by dkazakov into branch 'krita-chili-kazakov'.

Implement proper bilinear interpolation for the Warp Transform

The real problem of the warp transform was the implementation
of the bilinear interpolation. Now there is a proper solution
with quadratic equation and unittests. Please tests :)

CCMAIL:kimageshop at kde.org
BUG:314746

A  +118  -0    krita/image/kis_four_point_interpolator_backward.h     [License: GPL (v2+)]
A  +92   -0    krita/image/kis_four_point_interpolator_forward.h     [License: GPL (v2+)]
M  +265  -1342 krita/image/kis_warptransform_worker.cc
M  +8    -46   krita/image/kis_warptransform_worker.h
M  +236  -1    krita/image/tests/kis_warp_transform_worker_test.cpp
M  +8    -0    krita/image/tests/kis_warp_transform_worker_test.h
M  +7    -1    krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp

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

diff --git a/krita/image/kis_four_point_interpolator_backward.h b/krita/image/kis_four_point_interpolator_backward.h
new file mode 100644
index 0000000..04df325
--- /dev/null
+++ b/krita/image/kis_four_point_interpolator_backward.h
@@ -0,0 +1,118 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_FOUR_POINT_INTERPOLATOR_BACKWARD_H
+#define __KIS_FOUR_POINT_INTERPOLATOR_BACKWARD_H
+
+#include <QPolygon>
+#include <QPointF>
+
+
+
+/**
+ *    A-----B         The polygons must be initialized in this order:
+ *    |     |
+ *    |     |         polygon << A << B << D << C;
+ *    C-----D
+ */
+
+class KisFourPointInterpolatorBackward
+{
+public:
+    KisFourPointInterpolatorBackward(const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
+        m_a = dstPolygon[1] - dstPolygon[0]; // AB
+        m_b = dstPolygon[2] - dstPolygon[1]; // BD
+        m_c = dstPolygon[3] - dstPolygon[0]; // AC
+        m_d = m_b - m_c; // BD - AC
+
+        m_qA = m_c.x() * m_d.y() - m_c.y() * m_d.x();
+
+        m_srcBase = srcPolygon[0];
+        m_dstBase = dstPolygon[0];
+        m_xCoeff = srcPolygon[1].x() - srcPolygon[0].x(); // AB_src
+        m_yCoeff = srcPolygon[3].y() - srcPolygon[0].y(); // AC_src
+
+        m_qB_const = m_c.x() * m_a.y() - m_c.y() * m_a.x();
+
+        m_qD_div = 1.0 / (2 * m_qA);
+
+        //m_qB_varX = 0.0;
+        //m_qB_varY = 0.0;
+    }
+
+    inline QPointF map(const QPointF &pt) {
+        setX(pt.x());
+        setY(pt.y());
+        return getValue();
+    }
+
+    inline void setX(qreal x) {
+        x -= m_dstBase.x();
+
+        m_qB_varX = - x * m_d.y();
+        m_qC_varX = - x * m_a.y();
+        m_px = x;
+    }
+
+    inline void setY(qreal y) {
+        y -= m_dstBase.y();
+
+        m_qB_varY = y * m_d.x();
+        m_qC_varY = y * m_a.x();
+    }
+
+    inline QPointF getValue() const {
+        qreal qB = m_qB_const + m_qB_varX + m_qB_varY;
+        qreal qC = m_qC_varX + m_qC_varY;
+
+        qreal nu = 0.0;
+
+        if (qFuzzyCompare(m_qA, 0.0)) {
+            nu = -qC / qB;
+        } else {
+            qreal D = pow2(qB) - 4 * m_qA * qC;
+            nu = D > 0.0 ? (-qB - std::sqrt(D)) * m_qD_div : 0.0;
+        }
+
+        qreal mu = (m_px - nu * m_c.x()) / (m_a.x() + nu * m_d.x());
+
+        return m_srcBase + QPointF(mu * m_xCoeff, nu * m_yCoeff);
+    }
+
+private:
+    QPointF m_a; // AB
+    QPointF m_b; // BD
+    QPointF m_c; // AC
+    QPointF m_d; // m_b - m_c
+
+    qreal m_qA; // quadratic equation A coeff
+    qreal m_qB_const; // quadratic equation B coeff, const part
+    qreal m_qB_varX; // quadratic equation B coeff, X-dep part
+    qreal m_qB_varY; // quadratic equation B coeff, Y-dep part
+    qreal m_qC_varX; // quadratic equation C coeff, X-dep part
+    qreal m_qC_varY; // quadratic equation C coeff, Y-dep part
+    qreal m_qD_div; // inverted divisor of the quadratic equation solution
+    qreal m_px; // saved relative X coordinate
+
+    QPointF m_srcBase;
+    QPointF m_dstBase;
+    qreal m_xCoeff;
+    qreal m_yCoeff;
+};
+
+#endif /* __KIS_FOUR_POINT_INTERPOLATOR_BACKWARD_H */
diff --git a/krita/image/kis_four_point_interpolator_forward.h b/krita/image/kis_four_point_interpolator_forward.h
new file mode 100644
index 0000000..51f4b90
--- /dev/null
+++ b/krita/image/kis_four_point_interpolator_forward.h
@@ -0,0 +1,92 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73 at gmail.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_FOUR_POINT_INTERPOLATOR_FORWARD_H
+#define __KIS_FOUR_POINT_INTERPOLATOR_FORWARD_H
+
+#include <QPolygon>
+#include <QPointF>
+
+
+
+/**
+ *    A-----B         The polygons must be initialized in this order:
+ *    |     |
+ *    |     |         polygon << A << B << D << C;
+ *    C-----D
+ */
+
+class KisFourPointInterpolatorForward
+{
+public:
+    KisFourPointInterpolatorForward(const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
+        m_srcBase = srcPolygon[0];
+        m_dstBase = dstPolygon[0];
+
+        m_h0 = dstPolygon[1] - dstPolygon[0];
+        m_h1 = dstPolygon[2] - dstPolygon[3];
+
+        m_v0 = dstPolygon[3] - dstPolygon[0];
+
+        m_forwardCoeffX = 1.0 / (srcPolygon[1].x() - srcPolygon[0].x());
+        m_forwardCoeffY = 1.0 / (srcPolygon[3].y() - srcPolygon[0].y());
+
+        m_xProp = 0;
+        m_yProp = 0;
+    }
+
+    inline QPointF map(const QPointF &pt) {
+        setX(pt.x());
+        setY(pt.y());
+        return getValue();
+    }
+
+    inline void setX(qreal x) {
+        qreal diff = x - m_srcBase.x();
+        m_xProp = diff * m_forwardCoeffX;
+    }
+
+    inline void setY(qreal y) {
+        qreal diff = y - m_srcBase.y();
+        m_yProp = diff * m_forwardCoeffY;
+    }
+
+    inline QPointF getValue() const {
+        QPointF dstPoint = m_dstBase +
+            m_yProp * m_v0 +
+            m_xProp * (m_yProp * m_h1 + (1.0 - m_yProp) * m_h0);
+
+        return dstPoint;
+    }
+
+private:
+    QPointF m_srcBase;
+    QPointF m_dstBase;
+
+    QPointF m_h0;
+    QPointF m_h1;
+    QPointF m_v0;
+
+    qreal m_forwardCoeffX;
+    qreal m_forwardCoeffY;
+
+    qreal m_xProp;
+    qreal m_yProp;
+};
+
+#endif /* __KIS_FOUR_POINT_INTERPOLATOR_FORWARD_H */
diff --git a/krita/image/kis_warptransform_worker.cc b/krita/image/kis_warptransform_worker.cc
index fa0b412..c980a91 100644
--- a/krita/image/kis_warptransform_worker.cc
+++ b/krita/image/kis_warptransform_worker.cc
@@ -23,6 +23,8 @@
 #include "kis_iterator_ng.h"
 #include "kis_datamanager.h"
 
+#include <algorithm>
+
 #include <QTransform>
 #include <QVector2D>
 #include <QPainter>
@@ -32,873 +34,10 @@
 #include <KoColorSpace.h>
 #include <KoColor.h>
 
-#include <limits.h>
+#include <limits>
 #include <math.h>
 
-typedef struct KisWarpTransformWorker::s_Fraction {
-    int dividend;
-    int divisor;
-    //dividend and divisor must be positive
-    int sign;
-} Fraction;
-
-class KisWarpTransformWorker::Side {
-public:
-    int ymax, x_ymin;
-    Fraction invm;
-    int e;
-    Side *next;
-
-    inline bool operator<(Side& C2);
-};
-
-inline bool KisWarpTransformWorker::Side::operator<(Side& C2)
-{
-    Side C1 = *this;
-    if (C1.x_ymin < C2.x_ymin)
-        return true;
-    else if (C1.x_ymin>C2.x_ymin)
-        return false;
-    else
-        return (C1.invm.sign * C1.invm.dividend * C2.invm.divisor < C2.invm.sign * C2.invm.dividend * C1.invm.divisor);
-}
-
-struct KisWarpTransformWorker::s_ExtendedSide {
-    QPoint *P, *S;
-    Side *Side_;
-    struct s_ExtendedSide *next;
-} ExtendedSide;
-
-inline KisWarpTransformWorker::Side KisWarpTransformWorker::CalcSide(QPoint P1, QPoint P2, Side *next)
-{
-    Side C;
-
-    if (P1.y() <= P2.y()) {
-        C.ymax = P2.y();
-        C.x_ymin = P1.x();
-        C.invm.dividend = (P2.x() - P1.x());
-        C.invm.divisor = (P2.y() - P1.y());
-    } else {
-        C.ymax = P1.y();
-        C.x_ymin = P2.x();
-        C.invm.dividend = (P1.x() - P2.x());
-        C.invm.divisor = (P1.y() - P2.y());
-    }
-
-    if (C.invm.dividend < 0)
-        C.invm.sign = -1;
-    else
-        C.invm.sign = 1;
-
-    C.invm.dividend = abs(C.invm.dividend);
-    C.next = next;
-    C.e = 0;
-
-    return C;
-}
-
-inline KisWarpTransformWorker::Side KisWarpTransformWorker::CalcSide(QPoint *P1, QPoint *P2, Side *next)
-{
-    return CalcSide(*P1, *P2, next);
-}
-
-inline void KisWarpTransformWorker::AddExtSide(ExtendedSide **S, QPoint P0, QPoint P1)
-{
-    (*S)->next = new ExtendedSide;
-    (*S) = (*S)->next;
-    (*S)->P = new QPoint(P0);
-    (*S)->S = new QPoint(P1);
-    (*S)->Side_ = new Side;
-    *((*S)->Side_) = CalcSide(P0, P1, NULL);
-    (*S)->next = NULL;
-}
-
-void KisWarpTransformWorker::CreateExtSides(ExtendedSide **sides, QPolygon polygon)
-{
-    ExtendedSide *CurrExtSide;
-    ExtendedSide *Senti;
-    QPoint FirstPoint, CurrPoint, NextPoint;
-
-    if (polygon.size() <= 1)
-        return;
-
-    Senti = new ExtendedSide;
-    Senti->next = NULL;
-    CurrExtSide = Senti;
-    FirstPoint = polygon.at(0);
-    CurrPoint = FirstPoint;
-
-    for (int i = 1; i < polygon.size(); ++i) {
-        NextPoint = polygon.at(i);
-        AddExtSide(&CurrExtSide, CurrPoint, NextPoint);
-        CurrPoint = NextPoint;
-    }
-
-    AddExtSide(&CurrExtSide, CurrPoint, FirstPoint);
-    *sides = Senti->next;
-    delete Senti;
-}
-
-inline void KisWarpTransformWorker::Insert(Side **L, Side *NewSide)
-{
-    NewSide->next = *L;
-    *L = NewSide;
-}
-
-inline void KisWarpTransformWorker::InsertInSortedList(Side **L, Side *NewSide)
-{
-    Side *CurrSide = *L;
-
-    if (*L == NULL || *NewSide < **L) {
-        NewSide->next = *L;
-        *L = NewSide;
-        return;
-    }
-
-    while (CurrSide->next != NULL && *(CurrSide->next) < *NewSide)
-        CurrSide = CurrSide->next;
-
-    NewSide->next = CurrSide->next;
-    CurrSide->next = NewSide;
-}
-
-void KisWarpTransformWorker::FreeExtSide(ExtendedSide *S)
-{
-    if (S == NULL)
-        return;
-
-    delete S->P;
-    delete S->S;
-    delete S->Side_;
-    delete S;
-}
-
-void KisWarpTransformWorker::FreeExtSides(ExtendedSide **S)
-{
-    ExtendedSide *NextExtSide;
-    ExtendedSide *CurrentExtSide = *S;
-
-    while (CurrentExtSide != NULL) {
-        NextExtSide = CurrentExtSide->next;
-        FreeExtSide(CurrentExtSide);
-        CurrentExtSide = NextExtSide;
-    }
-
-    *S = NULL;
-}
-
-//requires dest allocated
-//create a copie of the side represented by P, S, C and adds it to dest
-inline void KisWarpTransformWorker::AddExtSide(ExtendedSide **dest, QPoint P, QPoint S, Side C)
-{
-    (*dest)->next = new ExtendedSide;
-    *dest = (*dest)->next;
-    (*dest)->P = new QPoint(P);
-    (*dest)->S = new QPoint(S);
-    (*dest)->Side_ = new Side;
-    *((*dest)->Side_) = C;
-    (*dest)->next = NULL;
-}
-
-inline void KisWarpTransformWorker::setRegion(bool reg[4], int x0, int y0, QRect clipRect)
-{
-    for (int i = 0; i < 4; ++i)
-        reg[i] = false;
-
-    if (x0 < clipRect.topLeft().x())
-        reg[LEFTSIDE] = true;
-    else if (x0 > clipRect.bottomRight().x())
-        reg[RIGHTSIDE] = true;
-
-    if (y0 < clipRect.topLeft().y())
-        reg[UPSIDE] = true;
-    else if (y0 > clipRect.bottomRight().y())
-        reg[DOWNSIDE] = true;
-}
-
-void KisWarpTransformWorker::Sutherland_Hodgman(ExtendedSide **Dest, ExtendedSide *ExtSide, QRect clipRect, ClipperSide CS, bool &PreviousPointOut)
-{
-    bool reg[2][4];
-    int dx, dy, xTmp, tmp, eTmp, sign;
-    Side C;
-    QPoint NewPoint;
-
-    setRegion(reg[0], ExtSide->P->x(), ExtSide->P->y(), clipRect);
-    setRegion(reg[1], ExtSide->S->x(), ExtSide->S->y(), clipRect);
-    dx = ExtSide->Side_->invm.dividend;
-    dy = ExtSide->Side_->invm.divisor;
-    sign = ExtSide->Side_->invm.sign;
-    switch (CS) {
-    case LEFTSIDE:
-        if ( (!reg[0][LEFTSIDE]) && (!reg[1][LEFTSIDE]) ) {
-            if (PreviousPointOut) {
-                C = CalcSide((*Dest)->S, ExtSide->P, NULL);
-                AddExtSide(Dest, *((*Dest)->S), *(ExtSide->P), C);
-            }
-            AddExtSide(Dest, *(ExtSide->P), *(ExtSide->S), *(ExtSide->Side_));
-            PreviousPointOut = false;
-        } else if ( (!reg[0][LEFTSIDE]) && reg[1][LEFTSIDE] ) {
-            if (dy == 0) {
-                tmp = ExtSide->Side_->ymax;
-            } else {
-                if (ExtSide->S->y() == ExtSide->Side_->ymax) {
-                    tmp = ExtSide->P->y();
-                    eTmp = ExtSide->Side_->e;
-                    xTmp = ExtSide->Side_->x_ymin;
-                    while (xTmp >= clipRect.topLeft().x()) {
-                        eTmp += dx;
-                        while (2 * eTmp > dy) {
-                            --xTmp;
-                            eTmp -= dy;
-                        }
-                        ++tmp;
-                    }
-                    ExtSide->Side_->ymax = tmp;
-                } else {
-                    tmp = ExtSide->S->y();
-                    while (ExtSide->Side_->x_ymin < clipRect.topLeft().x()) {
-                        ExtSide->Side_->e += dx;
-                        while (2 * ExtSide->Side_->e > dy) {
-                            ++ExtSide->Side_->x_ymin;
-                            ExtSide->Side_->e -= dy;
-                        }
-                        ++tmp;
-                    }
-                }
-            }
-
-            NewPoint = QPoint(clipRect.topLeft().x(), tmp);
-            if (PreviousPointOut) {
-                C = CalcSide((*Dest)->S, ExtSide->P, NULL);
-                AddExtSide(Dest, *((*Dest)->S), *(ExtSide->P), C);
-            }
-            AddExtSide(Dest, *(ExtSide->P), NewPoint, *(ExtSide->Side_));
-            PreviousPointOut = true;
-        } else if ( reg[0][LEFTSIDE] && (!reg[1][LEFTSIDE]) ) {
-            if (dy == 0) {
-                tmp = ExtSide->Side_->ymax;
-            } else {
-                if (ExtSide->P->y() == ExtSide->Side_->ymax) {
-                    tmp = ExtSide->S->y();
-                    eTmp = ExtSide->Side_->e;
-                    xTmp = ExtSide->Side_->x_ymin;
-                    while (xTmp >= clipRect.topLeft().x()) {
-                        eTmp += dx;
-                        while (2 * eTmp > dy) {
-                            --xTmp;
-                            eTmp -= dy;
-                        }
-                        ++tmp;
-                    }
-                    ExtSide->Side_->ymax = tmp;
-                } else {
-                    tmp = ExtSide->P->y();
-                    while (ExtSide->Side_->x_ymin < clipRect.topLeft().x()) {
-                        ExtSide->Side_->e += dx;
-                        while (2 * ExtSide->Side_->e > dy) {
-                            ++ExtSide->Side_->x_ymin;
-                            ExtSide->Side_->e -= dy;
-                        }
-                        ++tmp;
-                    }
-                }
-            }
-
-            NewPoint = QPoint(clipRect.topLeft().x(), tmp);
-            C = CalcSide(*((*Dest)->S), NewPoint, NULL);
-            AddExtSide(Dest, *((*Dest)->S), NewPoint, C);
-            AddExtSide(Dest, NewPoint, *(ExtSide->S), *(ExtSide->Side_));
-            PreviousPointOut = false;
-        } else {
-            PreviousPointOut = true;
-        }
-
-        break;
-    case RIGHTSIDE:
-        if ( (!reg[0][RIGHTSIDE]) && (!reg[1][RIGHTSIDE]) ) {
-            if (PreviousPointOut) {
-                C = CalcSide((*Dest)->S, ExtSide->P, NULL);
-                AddExtSide(Dest, *((*Dest)->S), *(ExtSide->P), C);
-            }
-            AddExtSide(Dest, *(ExtSide->P), *(ExtSide->S), *(ExtSide->Side_));
-            PreviousPointOut = false;
-        } else if ( (!reg[0][RIGHTSIDE]) && reg[1][RIGHTSIDE] ) {
-            if (dy == 0) {
-                tmp = ExtSide->Side_->ymax;
-            } else {
-                if (ExtSide->S->y() == ExtSide->Side_->ymax) {
-                    tmp = ExtSide->P->y();
-                    eTmp = ExtSide->Side_->e;
-                    xTmp = ExtSide->Side_->x_ymin;
-                    while (xTmp <= clipRect.bottomRight().x()) {
-                        eTmp += dx;
-                        while (2 * eTmp > dy) {
-                            ++xTmp;
-                            eTmp -= dy;
-                        }
-                        ++tmp;
-                    }
-                    ExtSide->Side_->ymax = tmp;
-                } else {
-                    tmp = ExtSide->S->y();
-                    while (ExtSide->Side_->x_ymin > clipRect.bottomRight().x()) {
-                        ExtSide->Side_->e += dx;
-                        while (2 * ExtSide->Side_->e > dy) {
-                            --ExtSide->Side_->x_ymin;
-                            ExtSide->Side_->e -= dy;
-                        }
-                        ++tmp;
-                    }
-                }
-            }
-
-            NewPoint = QPoint(clipRect.bottomRight().x(), tmp);
-            if (PreviousPointOut) {
-                C = CalcSide((*Dest)->S, ExtSide->P, NULL);
-                AddExtSide(Dest, *((*Dest)->S), *(ExtSide->P), C);
-            }
-            AddExtSide(Dest, *(ExtSide->P), NewPoint, *(ExtSide->Side_));
-            PreviousPointOut = true;
-        } else if ( reg[0][RIGHTSIDE] && (!reg[1][RIGHTSIDE]) ) {
-            if (dy == 0) {
-                tmp = ExtSide->Side_->ymax;
-            } else {
-                if (ExtSide->P->y() == ExtSide->Side_->ymax) {
-                    tmp = ExtSide->S->y();
-                    eTmp = ExtSide->Side_->e;
-                    xTmp = ExtSide->Side_->x_ymin;
-                    while (xTmp <= clipRect.bottomRight().x()) {
-                        eTmp += dx;
-                        while (2 * eTmp > dy) {
-                            ++xTmp;
-                            eTmp -= dy;
-                        }
-                        ++tmp;
-                    }
-                    ExtSide->Side_->ymax = tmp;
-                } else {
-                    tmp = ExtSide->P->y();
-                    while (ExtSide->Side_->x_ymin > clipRect.bottomRight().x()) {
-                        ExtSide->Side_->e += dx;
-                        while (2 * ExtSide->Side_->e > dy) {
-                            --ExtSide->Side_->x_ymin;
-                            ExtSide->Side_->e -= dy;
-                        }
-                        ++tmp;
-                    }
-                }
-            }
-
-            NewPoint = QPoint(clipRect.bottomRight().x(), tmp);
-            C = CalcSide(*((*Dest)->S), NewPoint, NULL);
-            AddExtSide(Dest, *((*Dest)->S), NewPoint, C);
-            AddExtSide(Dest, NewPoint, *(ExtSide->S),*(ExtSide->Side_));
-            PreviousPointOut = false;
-        } else {
-            PreviousPointOut = true;
-        }
-
-    break;
-    case UPSIDE:
-        if ( (!reg[0][UPSIDE]) && (!reg[1][UPSIDE]) ) {
-            if (PreviousPointOut) {
-                C = CalcSide((*Dest)->S, ExtSide->P, NULL);
-                AddExtSide(Dest, *((*Dest)->S), *(ExtSide->P), C);
-            }
-            AddExtSide(Dest, *(ExtSide->P), *(ExtSide->S), *(ExtSide->Side_));
-            PreviousPointOut = false;
-        } else if ( (!reg[0][UPSIDE]) && reg[1][UPSIDE] ) {
-            ExtSide->Side_->e += dx * (clipRect.topLeft().y() - ExtSide->S->y());
-            ExtSide->Side_->x_ymin += sign * ExtSide->Side_->e / dy;
-
-            if (ExtSide->Side_->e > 0) {
-                ExtSide->Side_->e = ExtSide->Side_->e % dy;
-                while (2 * ExtSide->Side_->e > dy) {
-                    ExtSide->Side_->x_ymin += sign;
-                    ExtSide->Side_->e -= dy;
-                }
-            }
-
-            NewPoint = QPoint(ExtSide->Side_->x_ymin, clipRect.topLeft().y());
-            if (PreviousPointOut) {
-                C = CalcSide((*Dest)->S, ExtSide->P, NULL);
-                AddExtSide(Dest, *((*Dest)->S), *(ExtSide->P), C);
-            }
-            AddExtSide(Dest, *(ExtSide->P), NewPoint, *(ExtSide->Side_));
-            PreviousPointOut = true;
-        } else if ( reg[0][UPSIDE] && (!reg[1][UPSIDE]) ) {
-            ExtSide->Side_->e += dx * (clipRect.topLeft().y() - ExtSide->P->y());
-            ExtSide->Side_->x_ymin += sign *ExtSide->Side_->e / dy;
-
-            if (ExtSide->Side_->e > 0) {
-                ExtSide->Side_->e = ExtSide->Side_->e % dy;
-                while (2 * ExtSide->Side_->e > dy) {
-                    ExtSide->Side_->x_ymin += sign;
-                    ExtSide->Side_->e -= dy;
-                }
-            }
-            NewPoint = QPoint(ExtSide->Side_->x_ymin, clipRect.topLeft().y());
-            C = CalcSide(*((*Dest)->S), NewPoint, NULL);
-            AddExtSide(Dest, *((*Dest)->S), NewPoint, C);
-            AddExtSide(Dest, NewPoint, *(ExtSide->S), *(ExtSide->Side_));
-            PreviousPointOut = false;
-        } else {
-            PreviousPointOut = true;
-        }
-        break;
-    case DOWNSIDE:
-        if ( (!reg[0][DOWNSIDE]) && (!reg[1][DOWNSIDE]) ) {
-            if (PreviousPointOut) {
-                C = CalcSide((*Dest)->S, ExtSide->P, NULL);
-                AddExtSide(Dest, *((*Dest)->S), *(ExtSide->P), C);
-            }
-            AddExtSide(Dest, *(ExtSide->P), *(ExtSide->S), *(ExtSide->Side_));
-            PreviousPointOut = false;
-        } else if ( (!reg[0][DOWNSIDE]) && reg[1][DOWNSIDE] ) {
-            tmp = (sign * dx * (clipRect.bottomRight().y() - ExtSide->P->y()) + ExtSide->P->x() * dy) / dy;
-            NewPoint = QPoint(tmp, clipRect.bottomRight().y());
-            if (PreviousPointOut) {
-                C = CalcSide((*Dest)->S, ExtSide->P, NULL);
-                AddExtSide(Dest, *((*Dest)->S), *(ExtSide->P), C);
-            }
-            ExtSide->Side_->ymax = clipRect.bottomRight().y();
-            AddExtSide(Dest, *(ExtSide->P), NewPoint, *(ExtSide->Side_));
-            PreviousPointOut = true;
-        } else if ( reg[0][DOWNSIDE] && (!reg[1][DOWNSIDE]) ) {
-            tmp = (sign * dx * (clipRect.bottomRight().y() - ExtSide->P->y()) + ExtSide->P->x() * dy) / dy;
-            NewPoint = QPoint(tmp, clipRect.bottomRight().y());
-            C = CalcSide(*((*Dest)->S), NewPoint, NULL);
-            AddExtSide(Dest, *((*Dest)->S), NewPoint, C);
-            ExtSide->Side_->ymax = clipRect.bottomRight().y();
-            AddExtSide(Dest, NewPoint, *(ExtSide->S), *(ExtSide->Side_));
-            PreviousPointOut = false;
-        } else {
-            PreviousPointOut = true;
-        }
-        break;
-    }	
-}
-
-void KisWarpTransformWorker::ClipPolygone(ExtendedSide **ExtSides, QRect *clipper)
-{
-    ExtendedSide *Senti;
-    ExtendedSide *NewExtSides;
-    ExtendedSide *CurrExtSide;
-    bool PreviousPointOut = false;
-    QRect clipRect = clipper->adjusted(0, 0, 1, 1);
-
-    Senti = new ExtendedSide;
-    Senti->P = new QPoint(0, 0);
-    Senti->S = new QPoint(0, 0);
-    Senti->Side_ = new Side;
-    Senti->Side_->next = NULL;
-    Senti->next = NULL;
-
-    for (int i = 0; i < 4; ++i) {
-        if (*ExtSides == NULL) {
-            FreeExtSide(Senti);
-            return;
-        } else if ((*ExtSides)->next == NULL) {
-            //only one side : no interior => nothing to draw
-            FreeExtSide(Senti);
-            FreeExtSides(ExtSides);
-            return;
-        }
-
-        CurrExtSide = *ExtSides;
-        NewExtSides = Senti;
-        Senti->next = NULL;
-        PreviousPointOut = false;
-        while (CurrExtSide != NULL) {
-            Sutherland_Hodgman(&NewExtSides, CurrExtSide, clipRect, (ClipperSide)i, PreviousPointOut);
-            CurrExtSide = CurrExtSide->next;
-        }
-        if (PreviousPointOut && Senti->next != NULL) {
-            *(Senti->next->P) = *(NewExtSides->S);
-            *(Senti->next->Side_) = CalcSide(Senti->next->P, Senti->next->S, NULL);
-        }
-        FreeExtSides(ExtSides);
-        *ExtSides = Senti->next;
-    }
-
-    FreeExtSide(Senti);
-}
-
-inline bool KisWarpTransformWorker::equals(qreal a, qreal b, qreal tolerance)
-{
-    return ( (a == b) || ((a <= (b + tolerance)) && (a >= (b - tolerance))) );
-}
-
-inline bool KisWarpTransformWorker::valInRange(qreal val, qreal range_min, qreal range_max, qreal tolerance)
-{
-    return ((val + tolerance) >= range_min) && ((val - tolerance) <= range_max);
-}
-
-inline qreal KisWarpTransformWorker::det(QVector2D v0, QVector2D v1)
-{
-    return (v0.x() * v1.y() - v0.y() * v1.x());
-}
-
-//returns number of solutions found
-//if there is one valid solution, it will be put in sol1
-int KisWarpTransformWorker::inverseBilinInterp(QVector2D p0_minus_p, QVector2D p1_minus_p, QVector2D p0_minus_p2, QVector2D p1_minus_p3, QPointF& sol1, QPointF& sol2)
-{
-    bool t1_valid, t2_valid;
-
-    qreal A  = det(p0_minus_p, p0_minus_p2);
-    qreal B1 = det(p0_minus_p, p1_minus_p3);
-    qreal B2 = det(p1_minus_p, p0_minus_p2);
-    qreal C  = det(p1_minus_p, p1_minus_p3);
-    qreal B  = 0.5 * (B1 + B2);
-
-    qreal s1 = 0, s2 = 0, t1 = 0, t2 = 0;
-
-    qreal Am2BpC = A - 2 * B + C;
-    int num_valid_s = 0;
-
-    if (equals(Am2BpC, 0, 1e-10)) {
-        qreal AmC = A - C;
-        if (equals(AmC, 0, 1e-10))
-            //the input is a line
-            return 0;
-        s1 = A / AmC;
-        if (valInRange(s1, 0, 1, 1e-10))
-            num_valid_s = 1;
-    } else {
-        qreal sqrtBsqmAC = sqrt(B * B - A * C );
-        qreal AmB = A - B;
-        s1 = (AmB - sqrtBsqmAC) / Am2BpC;
-        s2 = (AmB + sqrtBsqmAC) / Am2BpC;
-        num_valid_s = 0;
-        if (valInRange(s1, 0, 1, 1e-10)) {
-            ++num_valid_s;
-            if (valInRange(s2, 0, 1, 1e-10))
-                ++num_valid_s;
-        } else {
-            if (valInRange(s2, 0, 1, 1e-10)) {
-                ++num_valid_s;
-                s1 = s2;
-            }
-        }
-    }
-
-    if (num_valid_s == 0)
-        //found no valid solution for s
-        return 0;
-
-    //necessarily num_valid_s is > 0
-    qreal tdenom_x = (1 - s1) * p0_minus_p2.x() + s1 * p1_minus_p3.x();
-    qreal tdenom_y = (1 - s1) * p0_minus_p2.y() + s1 * p1_minus_p3.y();
-    t1_valid = true;
-    if (equals(tdenom_x, 0, 1e-10) && equals(tdenom_y, 0, 1e-10))
-        t1_valid = false;
-    else {
-        //choose the more robust denominator
-        if (fabs(tdenom_x) > fabs(tdenom_y))
-            t1 = ((1 - s1) * p0_minus_p.x() + s1 * p1_minus_p.x()) / tdenom_x;
-        else
-            t1 = ((1 - s1) * p0_minus_p.y() + s1 * p1_minus_p.y()) / tdenom_y;
-        if (!valInRange(t1, 0, 1, 1e-10))
-            t1_valid = false;
-    }
-
-    //same thing for s2 and t2
-    if (num_valid_s == 2) {
-        tdenom_x = (1 - s2) * p0_minus_p2.x() + s2 * p1_minus_p3.x();
-        tdenom_y = (1 - s2) * p0_minus_p2.y() + s2 * p1_minus_p3.y();
-        t2_valid = true;
-        if (equals(tdenom_x, 0, 1e-10) && equals(tdenom_y, 0, 1e-10))
-            t2_valid = false;
-        else {
-            //choose the more robust denominator
-            if (fabs(tdenom_x) > fabs(tdenom_y))
-                t2 = ((1 - s2) * p0_minus_p.x() + s2 * p1_minus_p.x()) / tdenom_x;
-            else
-                t2 = ((1 - s2) * p0_minus_p.y() + s2 * p1_minus_p.y()) / tdenom_y;
-            if (!valInRange(t2, 0, 1, 1e-10))
-                t2_valid = false;
-        }
-    } else
-        t2_valid = false;
-
-    //Final cleanup
-    if (t2_valid && !t1_valid ) {
-        s1 = s2;
-        t1 = t2;
-        t1_valid = t2_valid;
-        t2_valid = false;
-    }
-
-    //Output
-    sol1 = QPointF(s1, t1);
-    sol2 = QPointF(s2, t2);
-    int num_valid_sol = 0;
-    if (t1_valid)
-        ++num_valid_sol;
-
-    if (t2_valid)
-        ++num_valid_sol;
-
-    return num_valid_sol;
-}
-
-void inline KisWarpTransformWorker::bilinInterp(QPointF p0, QPointF p1, QPointF p2, QPointF p3, QPointF st, QPointF& p)
-{
-    qreal s = st.x(), t = st.y();
-    p = t*(s * p3 + (1 - s) * p2) + (1 - t)*(s * p1 + (1 - s) * p0);
-}
-
-void KisWarpTransformWorker::quadInterpolation(QImage *src, QImage *dst, QPolygon pSrc, QPolygon pDst)
-{
-    QRgb *pixels = NULL;
-    QVarLengthArray<Side*> TC(dst->height() + 1);
-    Side *TCA = NULL;
-    int y;
-    Side *PrevSide = NULL, *CurrSide = NULL, *NextSide = NULL;
-    Side *Senti = NULL;
-    bool InsidePolygone = false, ChangeSideForNextPixel = false;
-    ExtendedSide *ExtSides = NULL, *CurrExtSide = NULL;
-    QRect clipRect;
-    QRgb transparent = qRgba(0, 0, 0, 0);
-
-    QTransform mTransform;
-    bool transfExists = QTransform::quadToQuad(QPolygonF(pDst), QPolygonF(pSrc), mTransform);
-
-    clipRect = QRect(QPoint(0, 0), QPoint(dst->width() - 1, dst->height() - 1));
-    QRect boundRect = clipRect.intersected(pDst.boundingRect());
-
-    for (int i = boundRect.top(); i <= boundRect.bottom() + 1; ++i)
-        TC[i] = NULL;
-
-    CreateExtSides(&ExtSides, pDst);
-    ClipPolygone(&ExtSides, &clipRect);
-
-    if (ExtSides == NULL || ExtSides->next == NULL) {
-        FreeExtSides(&ExtSides);
-        return;
-    }
-
-    Senti = new Side;
-    Senti->next = NULL;
-
-    //init TC
-    CurrExtSide = ExtSides;
-    while (CurrExtSide != NULL) {
-        if (CurrExtSide->P->y() <= CurrExtSide->S->y())
-            Insert(&TC[CurrExtSide->P->y()], CurrExtSide->Side_);
-        else
-            Insert(&TC[CurrExtSide->S->y()], CurrExtSide->Side_);
-
-        CurrExtSide = CurrExtSide->next;
-    }
-
-    y = boundRect.top();
-    if (transfExists) {
-        //if Qt was able to invert the bilinear interpolation, it's better to use it (faster)
-        while (TCA != NULL || y <= boundRect.bottom()) {
-            pixels = (QRgb *)dst->scanLine(y);
-
-            //insert elements of TC(y) in TCA
-            CurrSide = TC[y];
-            while (CurrSide != NULL) {
-                NextSide = CurrSide->next;
-                InsertInSortedList(&TCA, CurrSide);
-                CurrSide = NextSide;
-            }
-            TC[y] = NULL;
-
-            //delete elements of TCA for which ymax=y
-            if (TCA != NULL) {
-                Senti->next = TCA;
-                PrevSide = Senti;
-                CurrSide = Senti->next;
-                while (CurrSide != NULL) {
-                    NextSide = CurrSide->next;
-                    if (CurrSide->ymax == y)
-                        PrevSide->next = NextSide;
-                    else
-                        PrevSide = CurrSide;
-
-                    CurrSide = NextSide;
-                }
-                PrevSide->next = NULL;
-                TCA = Senti->next;
-            }
-
-            if (TCA != NULL) {
-                Senti->x_ymin = INT_MIN;
-                Senti->invm.divisor = 0;
-                //senti is first in TCA : it must be the smallest element
-                Senti->next = TCA;
-                PrevSide = Senti;
-            }
-
-            //fill scanline
-            CurrSide = TCA;
-            InsidePolygone = false;
-            ChangeSideForNextPixel = false;
-            pixels += ptrdiff_t(boundRect.left());
-            for (int j = boundRect.left(); j <= boundRect.right() + 1; ++j) {
-                if (ChangeSideForNextPixel) {
-                    InsidePolygone = !InsidePolygone;
-                    ChangeSideForNextPixel = false;
-                }
-
-                while (CurrSide != NULL && CurrSide->x_ymin <= j) {
-                    if (CurrSide->invm.sign * CurrSide->e <= 0 || CurrSide->x_ymin == boundRect.right() + 1)
-                        InsidePolygone = !InsidePolygone;
-                    else
-                        ChangeSideForNextPixel = !ChangeSideForNextPixel;
-
-                    CurrSide->e += CurrSide->invm.dividend;
-                    while (2 * CurrSide->e > (CurrSide->invm.divisor)) {
-                        CurrSide->x_ymin += CurrSide->invm.sign;
-                        CurrSide->e -= CurrSide->invm.divisor;
-                    }
-
-                    if (*CurrSide < *PrevSide) {
-                        //we need to re-sort (partially) TCA
-                        NextSide = CurrSide->next;
-                        PrevSide->next = NextSide;
-                        InsertInSortedList(&TCA, CurrSide);
-                        CurrSide = NextSide;
-                    } else {
-                        PrevSide = CurrSide;
-                        CurrSide = CurrSide->next;
-                    }
-                }
-
-                if (InsidePolygone) {
-                    QPoint p = mTransform.map(QPointF(j, y)).toPoint();
-
-                    if (src->rect().contains(p))
-                    *pixels = src->pixel(p);
-                    else
-                    *pixels = transparent;
-                }
-
-                ++pixels;
-            }
-
-            ++y;
-        }
-    } else {
-        //if Qt couldn't compute the transformation, we use our own inverse bilinear interpolation
-        //which may find an inverse for some points (less artefacts)
-        //it's a bit slower though
-        QRgb defaultColor = src->pixel(pSrc.at(0));
-
-        QPointF p0(pDst.at(0));
-        QPointF p1(pDst.at(1));
-        QPointF p2(pDst.at(3));
-        QPointF p3(pDst.at(2));
-        QPointF q0(pSrc.at(0));
-        QPointF q1(pSrc.at(1));
-        QPointF q2(pSrc.at(3));
-        QPointF q3(pSrc.at(2));
-        QVector2D p0_minus_p2(p0 - p2);
-        QVector2D p1_minus_p3(p1 - p3);
-        QVector2D p0_minus_p;
-        QVector2D p1_minus_p;
-        QPointF sol1, sol2;
-
-        while (TCA != NULL || y <= boundRect.bottom()) {
-            pixels = (QRgb *)dst->scanLine(y);
-
-            //insert elements of TC(y) in TCA
-            CurrSide = TC[y];
-            while (CurrSide != NULL) {
-                NextSide = CurrSide->next;
-                InsertInSortedList(&TCA, CurrSide);
-                CurrSide = NextSide;
-            }
-            TC[y] = NULL;
-
-            //delete elements of TCA for which ymax=y
-            if (TCA != NULL) {
-                Senti->next = TCA;
-                PrevSide = Senti;
-                CurrSide = Senti->next;
-                while (CurrSide != NULL) {
-                    NextSide = CurrSide->next;
-                    if (CurrSide->ymax == y)
-                        PrevSide->next = NextSide;
-                    else
-                        PrevSide = CurrSide;
-
-                    CurrSide = NextSide;
-                }
-                PrevSide->next = NULL;
-                TCA = Senti->next;
-            }
-
-            if (TCA != NULL) {
-                Senti->x_ymin = INT_MIN;
-                Senti->invm.divisor = 0;
-                //senti is first in TCA : it must be the smallest element
-                Senti->next = TCA;
-                PrevSide = Senti;
-            }
-
-            //fill scanline
-            CurrSide = TCA;
-            InsidePolygone = false;
-            ChangeSideForNextPixel = false;
-            pixels += ptrdiff_t(boundRect.left());
-            for (int j = boundRect.left(); j <= boundRect.right() + 1; ++j) {
-                if (ChangeSideForNextPixel) {
-                    InsidePolygone = !InsidePolygone;
-                    ChangeSideForNextPixel = false;
-                }
-
-                while (CurrSide != NULL && CurrSide->x_ymin <= j) {
-                    if (CurrSide->invm.sign * CurrSide->e <= 0 || CurrSide->x_ymin == boundRect.right() + 1)
-                        InsidePolygone = !InsidePolygone;
-                    else
-                        ChangeSideForNextPixel = !ChangeSideForNextPixel;
-
-                    CurrSide->e += CurrSide->invm.dividend;
-                    while (2 * CurrSide->e > (CurrSide->invm.divisor)) {
-                        CurrSide->x_ymin += CurrSide->invm.sign;
-                        CurrSide->e -= CurrSide->invm.divisor;
-                    }
-
-                    if (*CurrSide < *PrevSide) {
-                        //we need to re-sort (partially) TCA
-                        NextSide = CurrSide->next;
-                        PrevSide->next = NextSide;
-                        InsertInSortedList(&TCA, CurrSide);
-                        CurrSide = NextSide;
-                    } else {
-                        PrevSide = CurrSide;
-                        CurrSide = CurrSide->next;
-                    }
-                }
-
-                if (InsidePolygone) {
-                    QPointF p(j, y);
-                    p0_minus_p = QVector2D(p0 - p);
-                    p1_minus_p = QVector2D(p1 - p);
-                    int nbSol = inverseBilinInterp(p0_minus_p, p1_minus_p, p0_minus_p2, p1_minus_p3, sol1, sol2);
-                    if (nbSol == 0) {
-                        *pixels = defaultColor;
-                    } else {
-                        QPointF q;
-                        bilinInterp(q0, q1, q2, q3, sol1, q);
-                        *pixels = src->pixel(q.toPoint());
-                    }
-                }
-
-                ++pixels;
-            }
-
-            ++y;
-        }
-    }
-
-    delete Senti;
-    FreeExtSides(&ExtSides);
-}
+#include "kis_four_point_interpolator_backward.h"
 
 QPointF KisWarpTransformWorker::affineTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
 {
@@ -1047,566 +186,350 @@ QPointF KisWarpTransformWorker::rigidTransformMath(QPointF v, QVector<QPointF> p
     return res;
 }
 
-QPointF KisWarpTransformWorker::transformMath(WarpType warpType, QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
+KisWarpTransformWorker::KisWarpTransformWorker(WarpType warpType, KisPaintDeviceSP dev, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, KoUpdater *progress)
+        : m_dev(dev), m_progress(progress)
 {
-    switch (warpType) {
+    m_origPoint = origPoint;
+    m_transfPoint = transfPoint;
+    m_alpha = alpha;
+
+    switch(warpType) {
     case AFFINE_TRANSFORM:
-        return affineTransformMath(v, p, q, alpha);
+        m_warpMathFunction = &affineTransformMath;
         break;
     case SIMILITUDE_TRANSFORM:
-        return similitudeTransformMath(v, p, q, alpha);
+        m_warpMathFunction = &similitudeTransformMath;
         break;
     case RIGID_TRANSFORM:
-        return rigidTransformMath(v, p, q, alpha);
+        m_warpMathFunction = &rigidTransformMath;
         break;
     default:
-        return v;
+        m_warpMathFunction = NULL;
         break;
     }
 }
 
-inline void KisWarpTransformWorker::switchVertices(QPoint **a, QPoint **b)
+KisWarpTransformWorker::~KisWarpTransformWorker()
 {
-    QPoint *tmp = *a;
-    *a = *b;
-    *b = tmp;
 }
 
-QImage KisWarpTransformWorker::aux_transformation(WarpMathFunction warpMathFunction, QImage *src, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, QPointF originalTopLeft, QPointF *newTopLeft)
+template <class ProcessPolygon, class ForwardTransform>
+void processPixels(ProcessPolygon &polygonOp, ForwardTransform &transformOp,
+                   const QRect &srcBounds, const int pixelPrecision)
 {
+    if (srcBounds.isEmpty()) return;
 
-    if (warpMathFunction == NULL || origPoint.size() != transfPoint.size() ||
-            origPoint.size() == 0) {
-        *newTopLeft = originalTopLeft;
-        return QImage(*src);
-    }
+    const int alignmentMask = ~(pixelPrecision - 1);
 
-    qint32 nbPoints = origPoint.size();
-    QRectF transfBRect;
-    if (nbPoints == 1) {
-        *newTopLeft = originalTopLeft + transfPoint[0] - origPoint[0];
-        return QImage(*src);
-    } else {
-        //we need to find an approximation of the dimensions of the new image
-        transfBRect = QRectF(transfPoint[0], transfPoint[0]);
-
-        for (int i = 1; i < nbPoints; ++i) {
-            if ( transfPoint[i].x() < transfBRect.left() )
-                transfBRect.setLeft(transfPoint[i].x());
-            else if ( transfPoint[i].x() > transfBRect.right() )
-                transfBRect.setRight(transfPoint[i].x());
-            if ( transfPoint[i].y() < transfBRect.top() )
-                transfBRect.setTop(transfPoint[i].y());
-            else if ( transfPoint[i].y() > transfBRect.bottom() )
-                transfBRect.setBottom(transfPoint[i].y());
-        }
+    QVector<QPointF> prevLinePoints;
+    QVector<QPointF> currLinePoints;
 
-        QPolygonF polygon;
-        qreal width2 = (qreal)src->width() / 2.;
-        qreal height2 = (qreal)src->height() / 2.;
-        QPointF topLeft = warpMathFunction(originalTopLeft, origPoint, transfPoint, alpha);
-        QPointF topMiddle = warpMathFunction(originalTopLeft + QPointF(width2, 0), origPoint, transfPoint, alpha);
-        QPointF topRight = warpMathFunction(originalTopLeft + QPointF(src->width(), 0), origPoint, transfPoint, alpha);
-        QPointF middleRight = warpMathFunction(originalTopLeft + QPointF(src->width(), height2), origPoint, transfPoint, alpha);
-        QPointF bottomRight = warpMathFunction(originalTopLeft + QPointF(src->width(), src->height()), origPoint, transfPoint, alpha);
-        QPointF bottomMiddle = warpMathFunction(originalTopLeft + QPointF(width2, src->height()), origPoint, transfPoint, alpha);
-        QPointF bottomLeft = warpMathFunction(originalTopLeft + QPointF(0, src->height()), origPoint, transfPoint, alpha);
-        QPointF middleLeft = warpMathFunction(originalTopLeft + QPointF(0, height2), origPoint, transfPoint, alpha);
-        polygon << topLeft << topMiddle << topRight << middleRight << bottomRight << bottomMiddle << bottomLeft << middleLeft;
-        transfBRect |= polygon.boundingRect();
-    }
+    int prevRow = std::numeric_limits<int>::max();
+    int prevCol = std::numeric_limits<int>::max();
 
-    QImage dst(ceil(transfBRect.width()), ceil(transfBRect.height()), QImage::Format_ARGB32_Premultiplied);
-    *newTopLeft = transfBRect.topLeft();
+    int rowIndex = 0;
+    int colIndex = 0;
 
-    QPainter painter(&dst);
-    painter.setBrush(Qt::SolidPattern);
-    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing, false);
-    painter.setCompositionMode(QPainter::CompositionMode_Source);
-    painter.fillRect(0, 0, dst.width(), dst.height(), QColor(0, 0, 0, 0));
+    for (int row = srcBounds.top(); row <= srcBounds.bottom();) {
+        for (int col = srcBounds.left(); col <= srcBounds.right();) {
 
-    if (src->height() == 0)
-        return QImage();
+            QPointF dstPosF = transformOp(QPointF(col, row));
+            currLinePoints << dstPosF;
 
-    int pixelPrecision = 20;
-    int verticesPerLine = src->width() / pixelPrecision + 2; 
-    QPoint *previousLineVertices = new QPoint[verticesPerLine];
-    QPoint *currentLineVertices = new QPoint[verticesPerLine];
-
-    int i, prevI;
-    int j, prevJ;
-    int k, prevK;
-    qreal x, y;
-    bool lineDone;
-    bool imageDone;
-
-    //begin the treatment
-    //first line needs a special treatment
-    i = 0;
-    y = originalTopLeft.y();
-    j = 0;
-    x = originalTopLeft.x();
-    k = 0;
-    QRgb *srcLine = (QRgb *)src->scanLine(i);
-    QRgb *srcPix = srcLine;
-    lineDone = false;
-    while (!lineDone) {
-        while (j < src->width()) {
-            QPointF dstPosF = warpMathFunction(QPointF(x, y), origPoint, transfPoint, alpha);
-            dstPosF -= *newTopLeft;
-            QPoint dstPos = QPoint(qRound(dstPosF.x()), qRound(dstPosF.y()));
-
-            previousLineVertices[k] = dstPos;
-
-            srcPix += pixelPrecision;
-            j += pixelPrecision;
-            x += pixelPrecision;
-            ++k;
-        }
+            if (rowIndex >= 1 && colIndex >= 1) {
 
-        //maybe we skipped the last pixels of the column (if the image width is not a multiple of pixelPrecision)
-        if (j - pixelPrecision < src->width() - 1) {
-            j = src->width() - 1;
-            x = j + originalTopLeft.x();
-            srcPix = (QRgb *)src->scanLine(i) + src->width() - 1;
-        } else
-            lineDone = true;
-    }
+                QPolygonF srcPolygon;
 
-    //then the other lines
-    imageDone = false;
-    prevI = i;
-    i += pixelPrecision;
-    y += pixelPrecision;
-    while (!imageDone) {
-        while (i < src->height()) {
-            srcLine = (QRgb *)src->scanLine(i);
-            srcPix = srcLine;
-            x = originalTopLeft.x();
-
-            //first column needs a special treatment
-            QPointF dstPosF = warpMathFunction(QPointF(x, y), origPoint, transfPoint, alpha);
-            dstPosF -= *newTopLeft;
-            QPoint dstPos = QPoint(qRound(dstPosF.x()), qRound(dstPosF.y()));
-
-            currentLineVertices[0] = dstPos;
-
-            srcPix += pixelPrecision;
-            x += pixelPrecision;
-
-            //the other columns
-            j = pixelPrecision;
-            prevJ = 0;
-            k = 1;
-            prevK = 0;
-            lineDone = false;
-            while (!lineDone) {
-                while (j < src->width()) {
-                    dstPosF = warpMathFunction(QPointF(x, y), origPoint, transfPoint, alpha);
-                    dstPosF -= *newTopLeft;
-                    QPoint dstPos = QPoint(qRound(dstPosF.x()), qRound(dstPosF.y()));
-
-                    currentLineVertices[k] = dstPos;
-
-                    QPolygon pSrc, pDst;
-                    pSrc << QPoint(prevJ, prevI) << QPoint(j, prevI) << QPoint(j, i) << QPoint(prevJ, i);
-                    pDst << previousLineVertices[prevK] << previousLineVertices[k]  << currentLineVertices[k]  << currentLineVertices[prevK];
-
-                    quadInterpolation(src, &dst, pSrc, pDst);
-
-                    srcPix += pixelPrecision;
-                    prevJ = j;
-                    j += pixelPrecision;
-                    prevK = k;
-                    ++k;
-                    x += pixelPrecision;
-                }
+                srcPolygon << QPointF(prevCol, prevRow);
+                srcPolygon << QPointF(col, prevRow);
+                srcPolygon << QPointF(col, row);
+                srcPolygon << QPointF(prevCol, row);
+
+                QPolygonF dstPolygon;
+
+                dstPolygon << prevLinePoints.at(colIndex - 1);
+                dstPolygon << prevLinePoints.at(colIndex);
+                dstPolygon << currLinePoints.at(colIndex);
+                dstPolygon << currLinePoints.at(colIndex - 1);
 
-                if (j - pixelPrecision < src->width() - 1) {
-                    j = src->width() - 1;
-                    x = j + originalTopLeft.x();
-                    srcPix = (QRgb *)src->scanLine(i) + src->width() - 1;
-                } else
-                    lineDone = true;
+                polygonOp(srcPolygon, dstPolygon);
             }
 
-            switchVertices(&previousLineVertices, &currentLineVertices);
 
-            prevI = i;
-            i += pixelPrecision;
-            y += pixelPrecision;
+            prevCol = col;
+            col += pixelPrecision;
+            colIndex++;
+
+            if (col > srcBounds.right() &&
+                col < srcBounds.right() + pixelPrecision - 1) {
+
+                col = srcBounds.right();
+            } else {
+                col &= alignmentMask;
+            }
         }
 
-        if (i - pixelPrecision < src->height() - 1) {
-            i = src->height() - 1;
-            y = i + originalTopLeft.y();
-            srcPix = (QRgb *)src->scanLine(src->height() - 1);
-        } else
-            imageDone = true;
-    }
+        std::swap(prevLinePoints, currLinePoints);
 
-    delete[] previousLineVertices;
-    delete[] currentLineVertices;
+        // we are erasing elements for not free'ing the occupied
+        // memory, which is more efficient since we are going to fill
+        // the vector again
+        currLinePoints.erase(currLinePoints.begin(), currLinePoints.end());
+        colIndex = 0;
 
-    return dst;
-}
+        prevRow = row;
+        row += pixelPrecision;
+        rowIndex++;
 
-QImage KisWarpTransformWorker::transformation(WarpType warpType, QImage *src, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, QPointF originalTopLeft, QPointF *newTopLeft)
-{
-    switch (warpType) {
-    case AFFINE_TRANSFORM:
-        return aux_transformation(&affineTransformMath, src, origPoint, transfPoint, alpha, originalTopLeft, newTopLeft);
-        break;
-    case SIMILITUDE_TRANSFORM:
-        return aux_transformation(&similitudeTransformMath, src, origPoint, transfPoint, alpha, originalTopLeft, newTopLeft);
-        break;
-    case RIGID_TRANSFORM:
-        return aux_transformation(&rigidTransformMath, src, origPoint, transfPoint, alpha, originalTopLeft, newTopLeft);
-        break;
-    default:
-        return QImage(*src);
-        break;
+        if (row > srcBounds.bottom() &&
+            row < srcBounds.bottom() + pixelPrecision - 1) {
+
+            row = srcBounds.bottom();
+        } else {
+            row &= alignmentMask;
+        }
     }
 }
 
-KisWarpTransformWorker::KisWarpTransformWorker(WarpType warpType, KisPaintDeviceSP dev, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, KoUpdater *progress)
-        : m_dev(dev), m_progress(progress)
+struct KisWarpTransformWorker::FunctionTransformOp
 {
-    m_origPoint = origPoint;
-    m_transfPoint = transfPoint;
-    m_alpha = alpha;
+    FunctionTransformOp(KisWarpTransformWorker::WarpMathFunction function,
+                        const QVector<QPointF> &p,
+                        const QVector<QPointF> &q,
+                        qreal alpha)
+        : m_function(function),
+          m_p(p),
+          m_q(q),
+          m_alpha(alpha)
+    {
+    }
 
-    switch(warpType) {
-    case AFFINE_TRANSFORM:
-        m_warpMathFunction = &affineTransformMath;
-        break;
-    case SIMILITUDE_TRANSFORM:
-        m_warpMathFunction = &similitudeTransformMath;
-        break;
-    case RIGID_TRANSFORM:
-        m_warpMathFunction = &rigidTransformMath;
-        break;
-    default:
-        m_warpMathFunction = NULL;
-        break;
+    QPointF operator() (const QPointF &pt) const {
+        return m_function(pt, m_p, m_q, m_alpha);
     }
-}
 
-KisWarpTransformWorker::~KisWarpTransformWorker()
-{
-}
+    KisWarpTransformWorker::WarpMathFunction m_function;
+    const QVector<QPointF> &m_p;
+    const QVector<QPointF> &m_q;
+    qreal m_alpha;
+};
 
-void KisWarpTransformWorker::quadInterpolation(KisPaintDeviceSP src, KisPaintDeviceSP dst, QPolygon pSrc, QPolygon pDst)
+struct PaintDevicePolygonOp
 {
-    Side *TCA = NULL;
-    int y;
-    Side *PrevSide = NULL, *CurrSide = NULL, *NextSide = NULL;
-    Side *Senti = NULL;
-    bool insidePolygon = false, ChangeSideForNextPixel = false;
-    ExtendedSide *ExtSides = NULL, *CurrExtSide = NULL;
-    QRect clipRect;
-
-    QPointF p0(pDst.at(0));
-    QPointF p1(pDst.at(1));
-    QPointF p2(pDst.at(3));
-    QPointF p3(pDst.at(2));
-    QPointF q0(pSrc.at(0));
-    QPointF q1(pSrc.at(1));
-    QPointF q2(pSrc.at(3));
-    QPointF q3(pSrc.at(2));
-    QVector2D p0_minus_p2(p0 - p2);
-    QVector2D p1_minus_p3(p1 - p3);
-    QVector2D p0_minus_p;
-    QVector2D p1_minus_p;
-    QPointF sol1, sol2;
-
-    QRect boundRect = pDst.boundingRect();
-    int TC_offset = 0;
-    if (boundRect.top() < 0)
-        TC_offset = - boundRect.top();
-    QVarLengthArray<Side*> TC(boundRect.bottom() + TC_offset + 2);
-    for (int i = 0; i <= boundRect.bottom() + TC_offset + 1; ++i)
-        TC[i] = NULL;
-
-    CreateExtSides(&ExtSides, pDst);
-    ClipPolygone(&ExtSides, &boundRect);
-
-    if (ExtSides == NULL || ExtSides->next == NULL) {
-        FreeExtSides(&ExtSides);
-        return;
-    }
-
-    Senti = new Side;
+    PaintDevicePolygonOp(KisPaintDeviceSP srcDev, KisPaintDeviceSP dstDev)
+        : m_srcDev(srcDev), m_dstDev(dstDev) {}
 
-    //init TC
-    CurrExtSide = ExtSides;
-    while (CurrExtSide != NULL) {
-        if (CurrExtSide->P->y() <= CurrExtSide->S->y())
-            Insert(&TC[CurrExtSide->P->y() + TC_offset], CurrExtSide->Side_);
-        else
-            Insert(&TC[CurrExtSide->S->y() + TC_offset], CurrExtSide->Side_);
+    void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
+        QRect boundRect = dstPolygon.boundingRect().toAlignedRect();
+        KisSequentialIterator dstIt(m_dstDev, boundRect);
+        KisRandomSubAccessorSP srcAcc = m_srcDev->createRandomSubAccessor();
 
-        CurrExtSide = CurrExtSide->next;
-    }
+        KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon);
 
-    y = boundRect.top();
+        int y = boundRect.top();
+        interp.setY(y);
 
-    KisRandomSubAccessorSP srcAcc = src->createRandomSubAccessor();
-    while (TCA != NULL || y <= boundRect.bottom()) {
-        KisHLineIteratorSP pixels = dst->createHLineIteratorNG(boundRect.left(), y, boundRect.width() + 2);
+        do {
+            int newY = dstIt.y();
 
-        //insert elements of TC(y) in TCA
-        CurrSide = TC[y + TC_offset];
-        while (CurrSide != NULL) {
-            NextSide = CurrSide->next;
-            InsertInSortedList(&TCA, CurrSide);
-            CurrSide = NextSide;
-        }
-        TC[y + TC_offset] = NULL;
-
-        //delete elements of TCA for which ymax=y
-        if (TCA != NULL) {
-            Senti->next = TCA;
-            PrevSide = Senti;
-            CurrSide = Senti->next;
-            while (CurrSide != NULL) {
-                NextSide = CurrSide->next;
-                if (CurrSide->ymax == y)
-                    PrevSide->next = NextSide;
-                else
-                    PrevSide = CurrSide;
-
-                CurrSide = NextSide;
+            if (y != newY) {
+                y = newY;
+                interp.setY(y);
             }
-            PrevSide->next = NULL;
-            TCA = Senti->next;
-        }
 
-        if (TCA != NULL) {
-            Senti->x_ymin = INT_MIN;
-            Senti->invm.divisor = 0;
-            //senti is first in TCA : it must be the smallest element
-            Senti->next = TCA;
-            PrevSide = Senti;
-        }
+            QPointF srcPoint(dstIt.x(), y);
 
-        //fill scanline
-        CurrSide = TCA;
-        insidePolygon = false;
-        ChangeSideForNextPixel = false;
-        for (int j = boundRect.left(); j <= boundRect.right() + 1; ++j) {
-            if (ChangeSideForNextPixel) {
-                insidePolygon = !insidePolygon;
-                ChangeSideForNextPixel = false;
-            }
+            if (dstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
 
-            while (CurrSide != NULL && CurrSide->x_ymin <= j) {
-                if (CurrSide->invm.sign * CurrSide->e <= 0 || CurrSide->x_ymin == boundRect.right() + 1)
-                    insidePolygon = !insidePolygon;
-                else
-                    ChangeSideForNextPixel = !ChangeSideForNextPixel;
+                interp.setX(srcPoint.x());
+                QPointF dstPoint = interp.getValue();
 
-                CurrSide->e += CurrSide->invm.dividend;
-                while (2 * CurrSide->e > (CurrSide->invm.divisor)) {
-                    CurrSide->x_ymin += CurrSide->invm.sign;
-                    CurrSide->e -= CurrSide->invm.divisor;
-                }
+                // brain-blowing part:
+                //
+                // since the interpolator does the inverted
+                // transfomation we read data from "dstPoint"
+                // (which is non-transformed) and write it into
+                // "srcPoint" (which is transformed position)
 
-                if (*CurrSide < *PrevSide) {
-                    //we need to re-sort (partially) TCA
-                    NextSide = CurrSide->next;
-                    PrevSide->next = NextSide;
-                    InsertInSortedList(&TCA, CurrSide);
-                    CurrSide = NextSide;
-                } else {
-                    PrevSide = CurrSide;
-                    CurrSide = CurrSide->next;
-                }
+                srcAcc->moveTo(dstPoint);
+                srcAcc->sampledOldRawData(dstIt.rawData());
             }
 
-            if (insidePolygon) {
-                QPointF p(j, y);
-                p0_minus_p = QVector2D(p0 - p);
-                p1_minus_p = QVector2D(p1 - p);
-                int nbSol = inverseBilinInterp(p0_minus_p, p1_minus_p, p0_minus_p2, p1_minus_p3, sol1, sol2);
-                if (nbSol == 0) {
-                    srcAcc->moveTo(QPointF(q0));
-                } else {
-                    QPointF q;
-                    bilinInterp(q0, q1, q2, q3, sol1, q);
-                    srcAcc->moveTo(q);
+        } while (dstIt.nextPixel());
+
+    }
+
+    KisPaintDeviceSP m_srcDev;
+    KisPaintDeviceSP m_dstDev;
+};
+
+struct QImagePolygonOp
+{
+    QImagePolygonOp(const QImage &srcImage, QImage &dstImage,
+                    const QPointF &srcImageOffset,
+                    const QPointF &dstImageOffset)
+        : m_srcImage(srcImage), m_dstImage(dstImage),
+          m_srcImageOffset(srcImageOffset),
+          m_dstImageOffset(dstImageOffset),
+          m_srcImageRect(m_srcImage.rect()),
+          m_dstImageRect(m_dstImage.rect())
+    {
+    }
+
+    void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
+        QRect boundRect = dstPolygon.boundingRect().toAlignedRect();
+        KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon);
+
+        for (int y = boundRect.top(); y <= boundRect.bottom(); y++) {
+            interp.setY(y);
+            for (int x = boundRect.left(); x <= boundRect.right(); x++) {
+
+                QPointF srcPoint(x, y);
+                if (dstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
+
+                    interp.setX(srcPoint.x());
+                    QPointF dstPoint = interp.getValue();
+
+                    // about srcPoint/dstPoint hell please see a
+                    // comment in PaintDevicePolygonOp::operator() ()
+
+                    srcPoint -= m_dstImageOffset;
+                    dstPoint -= m_srcImageOffset;
+
+                    QPoint srcPointI = srcPoint.toPoint();
+                    QPoint dstPointI = dstPoint.toPoint();
+
+                    srcPointI.rx() = qBound(m_dstImageRect.x(), srcPointI.x(), m_dstImageRect.right());
+                    srcPointI.ry() = qBound(m_dstImageRect.y(), srcPointI.y(), m_dstImageRect.bottom());
+                    dstPointI.rx() = qBound(m_srcImageRect.x(), dstPointI.x(), m_srcImageRect.right());
+                    dstPointI.ry() = qBound(m_srcImageRect.y(), dstPointI.y(), m_srcImageRect.bottom());
+
+                    m_dstImage.setPixel(srcPointI, m_srcImage.pixel(dstPointI));
                 }
-                srcAcc->sampledOldRawData(pixels->rawData());
             }
-
-            pixels->nextPixel();
         }
-
-        ++y;
     }
 
-    delete Senti;
-    FreeExtSides(&ExtSides);
-}
+    const QImage &m_srcImage;
+    QImage &m_dstImage;
+    QPointF m_srcImageOffset;
+    QPointF m_dstImageOffset;
+
+    QRect m_srcImageRect;
+    QRect m_dstImageRect;
+};
 
 void KisWarpTransformWorker::run()
 {
 
-    if (m_warpMathFunction == NULL || m_origPoint.size() != m_transfPoint.size()
-            || m_origPoint.size() == 0)
+    if (!m_warpMathFunction ||
+        m_origPoint.isEmpty() ||
+        m_origPoint.size() != m_transfPoint.size()) {
+
         return;
+    }
 
-    qint32 nbPoints = m_origPoint.size();
     KisPaintDeviceSP srcdev = new KisPaintDevice(*m_dev.data());
-    KoColor defaultPixel(srcdev->defaultPixel(), srcdev->colorSpace());
-    QRect srcBounds;
-    if (defaultPixel.opacityU8() != OPACITY_TRANSPARENT_U8)
-        srcBounds = srcdev->dataManager()->extent();
-    else
-        srcBounds = srcdev->exactBounds();
-
-    QRectF transfBRect;
-    if (nbPoints == 1) {
-        QPointF translate(QPointF(m_dev->x(), m_dev->y()) + m_transfPoint[0] - m_origPoint[0]); 
+
+    if (m_origPoint.size() == 1) {
+        QPointF translate(QPointF(m_dev->x(), m_dev->y()) + m_transfPoint[0] - m_origPoint[0]);
         m_dev->move(translate.toPoint());
         return;
-    } else {
-        transfBRect = QPolygonF(m_transfPoint).boundingRect();
     }
 
+    const QRect srcBounds = srcdev->region().boundingRect();
+
     m_dev->clear();
 
-    if (srcBounds.height() == 0)
-        return;
+    const int pixelPrecision = 8;
 
-    int pixelPrecision = 5;
-    int verticesPerLine = srcBounds.width() / pixelPrecision + 2;
-
-    QPoint *previousLineVertices = new QPoint[verticesPerLine]();
-    QPoint *currentLineVertices = new QPoint[verticesPerLine]();
-
-    int i, prevX;
-    int j, prevY;
-    int k, prevK;
-    qreal x, y;
-    bool lineDone;
-    bool imageDone;
-
-    m_lastProgressReport = 0;
-    m_progressStep = 0;
-    m_progressTotalSteps = verticesPerLine * verticesPerLine;
-    m_progress->setProgress(0);
-
-    //begin the treatment
-    //first line needs a special treatment
-    i = 0;
-    y = srcBounds.top();
-    j = 0;
-    x = srcBounds.left();
-    k = 0;
-    KisHLineConstIteratorSP srcPix = srcdev->createHLineConstIteratorNG(x, y, srcBounds.width());
-    KisRandomSubAccessorSP dstAcc = m_dev->createRandomSubAccessor();
-    lineDone = false;
-    while (!lineDone) {
-        while (j < srcBounds.width()) {
-            QPointF dstPosF = m_warpMathFunction(QPointF(x, y), m_origPoint, m_transfPoint, m_alpha);
-            QPoint dstPos = QPoint(qRound(dstPosF.x()), qRound(dstPosF.y()));
-
-            previousLineVertices[k] = dstPos;
-
-            srcPix->nextPixels(pixelPrecision);
-            j += pixelPrecision;
-            x += pixelPrecision;
-            ++k;
-        }
+    FunctionTransformOp functionOp(m_warpMathFunction, m_origPoint, m_transfPoint, m_alpha);
+    PaintDevicePolygonOp polygonOp(srcdev, m_dev);
+    processPixels(polygonOp, functionOp,
+                  srcBounds, pixelPrecision);
+}
 
-        //maybe we skipped the last pixels of the column (if the image width is not a multiple of pixelPrecision)
-        if (j - pixelPrecision < srcBounds.width() - 1) {
-            j = srcBounds.width() - 1;
-            x = j + srcBounds.left();
-            srcPix = srcdev->createHLineConstIteratorNG(x, y, 1);
-        } else
-            lineDone = true;
+QImage KisWarpTransformWorker::transformQImage(WarpType warpType,
+                                               const QVector<QPointF> &origPoint,
+                                               const QVector<QPointF> &transfPoint,
+                                               qreal alpha,
+                                               const QImage& srcImage,
+                                               const QPointF &srcQImageOffset,
+                                               QPointF *newOffset)
+{
+    KIS_ASSERT_RECOVER(srcImage.format() == QImage::Format_ARGB32) {
+        return QImage();
     }
 
-    //then the other lines
-    imageDone = false;
-    prevY = y;
-    i += pixelPrecision;
-    y += pixelPrecision;
-    while (!imageDone) {
-        while (i < srcBounds.height()) {
-            srcPix = srcdev->createHLineConstIteratorNG(x, y, srcBounds.width());
-            x = srcBounds.left();
-
-            //first column needs a special treatment
-            QPointF dstPosF = m_warpMathFunction(QPointF(x, y), m_origPoint, m_transfPoint, m_alpha);
-            QPoint dstPos = QPoint(qRound(dstPosF.x()), qRound(dstPosF.y()));
-
-            currentLineVertices[0] = dstPos;
-
-            srcPix->nextPixels(pixelPrecision);
-            prevX = x;
-            x += pixelPrecision;
-
-            //the other columns
-            j = pixelPrecision;
-            k = 1;
-            prevK = 0;
-            lineDone = false;
-            while (!lineDone) {
-                while (j < srcBounds.width()) {
-                    dstPosF = m_warpMathFunction(QPointF(x, y), m_origPoint, m_transfPoint, m_alpha);
-                    QPoint dstPos = QPoint(qRound(dstPosF.x()), qRound(dstPosF.y()));
-
-                    currentLineVertices[k] = dstPos;
-
-                    QPolygon pSrc, pDst;
-                    pSrc << QPoint(prevX, prevY) << QPoint(x, prevY) << QPoint(x, y) << QPoint(prevX, y);
-                    pDst << previousLineVertices[prevK] << previousLineVertices[k]  << currentLineVertices[k]  << currentLineVertices[prevK];
-
-                    quadInterpolation(srcdev, m_dev, pSrc, pDst);
-
-                    srcPix->nextPixels(pixelPrecision);
-                    j += pixelPrecision;
-                    prevK = k;
-                    ++k;
-                    prevX = x;
-                    x += pixelPrecision;
-
-                    m_progressStep += 1.;
-                    if (m_lastProgressReport != (m_progressStep * 100) / m_progressTotalSteps) {
-                        m_lastProgressReport = (m_progressStep * 100) / m_progressTotalSteps;
-                        m_progress->setProgress(m_lastProgressReport);
-                    }
-                    if (m_progress->interrupted()) {
-                        break;
-                    }
-                }
+    WarpMathFunction warpMathFunction = &rigidTransformMath;
 
-                if (j - pixelPrecision < srcBounds.width() - 1) {
-                    j = srcBounds.width() - 1;
-                    x = j + srcBounds.left();
-                    srcPix = srcdev->createHLineConstIteratorNG(x, y, 1);
-                } else
-                    lineDone = true;
-            }
+    switch (warpType) {
+    case AFFINE_TRANSFORM:
+        warpMathFunction = &affineTransformMath;
+        break;
+    case SIMILITUDE_TRANSFORM:
+        warpMathFunction = &similitudeTransformMath;
+        break;
+    case RIGID_TRANSFORM:
+        warpMathFunction = &rigidTransformMath;
+        break;
+    }
+
+    if (!warpMathFunction ||
+        origPoint.isEmpty() ||
+        origPoint.size() != transfPoint.size()) {
+
+        return srcImage;
+    }
+
+    if (origPoint.size() == 1) {
+        *newOffset = srcQImageOffset + (transfPoint[0] - origPoint[0]).toPoint();
+        return srcImage;
+    }
+
+    FunctionTransformOp functionOp(warpMathFunction, origPoint, transfPoint, alpha);
+
+    const QRect srcBounds = srcImage.rect();
+    QRectF dstBounds;
 
-            switchVertices(&previousLineVertices, &currentLineVertices);
+    {
+        QPolygonF testPoints;
+        testPoints << srcBounds.topLeft();
+        testPoints << srcBounds.topRight();
+        testPoints << srcBounds.bottomRight();
+        testPoints << srcBounds.bottomLeft();
+        testPoints << srcBounds.topLeft();
 
-            prevY = y;
-            i += pixelPrecision;
-            y += pixelPrecision;
+        QPolygonF::iterator it = testPoints.begin() + 1;
+
+        while (it != testPoints.end()) {
+            it = testPoints.insert(it, 0.5 * (*it + *(it - 1)));
+            it += 2;
         }
 
-        if (i - pixelPrecision < srcBounds.height() - 1) {
-            i = srcBounds.height() - 1;
-            y = i + srcBounds.top();
-            srcPix = srcdev->createHLineConstIteratorNG(srcBounds.left(), y, 1);
-        } else
-            imageDone = true;
+        it = testPoints.begin();
+
+        while (it != testPoints.end()) {
+            *it = functionOp(*it);
+            ++it;
+        }
+
+        dstBounds = testPoints.boundingRect();
     }
 
-    delete[] previousLineVertices;
-    delete[] currentLineVertices;
+    QPointF dstQImageOffset = dstBounds.topLeft();
+    *newOffset = srcQImageOffset + dstQImageOffset;
+
+    QRect dstBoundsI = dstBounds.toAlignedRect();
+    QImage dstImage(dstBoundsI.size(), srcImage.format());
+    dstImage.fill(128);
+
+    QImagePolygonOp polygonOp(srcImage, dstImage, QPointF(), dstQImageOffset);
+
+    const int pixelPrecision = 32;
+
+    processPixels(polygonOp, functionOp,
+                  srcBounds, pixelPrecision);
+
+    return dstImage;
 }
diff --git a/krita/image/kis_warptransform_worker.h b/krita/image/kis_warptransform_worker.h
index 06da4dd..b846ddf 100644
--- a/krita/image/kis_warptransform_worker.h
+++ b/krita/image/kis_warptransform_worker.h
@@ -52,14 +52,14 @@ public:
     static QPointF affineTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha);
     static QPointF similitudeTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha);
     static QPointF rigidTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha);
-    // Convenience method : calls one of the 3 math function above depending on warpType
-    static QPointF transformMath(WarpType warpType, QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha);
-    // Puts in dst the transformed quad pDst interpolated using quad pSrc and pixels of image src
-    static void quadInterpolation(QImage *src, QImage *dst, QPolygon pSrc, QPolygon pDst);
-    static void quadInterpolation(KisPaintDeviceSP src, KisPaintDeviceSP dst, QPolygon pSrc, QPolygon pDst);
 
-    // Apply the transform on a QImage
-    static QImage transformation(WarpType warpType, QImage *src, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, QPointF originalTopLeft, QPointF *newTopLeft);
+    static QImage transformQImage(WarpType warpType,
+                                  const QVector<QPointF> &origPoint,
+                                  const QVector<QPointF> &transfPoint,
+                                  qreal alpha,
+                                  const QImage& srcImage,
+                                  const QPointF &srcQImageOffset,
+                                  QPointF *newOffset);
 
     // Prepare the transformation on dev
     KisWarpTransformWorker(WarpType warpType, KisPaintDeviceSP dev, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, KoUpdater *progress);
@@ -68,54 +68,16 @@ public:
     void run();
 
 private:
+    struct FunctionTransformOp;
     typedef QPointF (*WarpMathFunction)(QPointF, QVector<QPointF>, QVector<QPointF>, qreal);
 
 private:
-    qreal m_progressTotalSteps;
-    qreal m_lastProgressReport;
-    qreal m_progressStep;
     WarpMathFunction m_warpMathFunction;
     QVector<QPointF> m_origPoint;
     QVector<QPointF> m_transfPoint;
     qreal m_alpha;
     KisPaintDeviceSP m_dev;
     KoUpdater *m_progress;
-
-private:
-    struct s_Fraction;
-    typedef struct s_Fraction Fraction;
-
-    class Side;
-
-    struct s_ExtendedSide;
-    typedef struct s_ExtendedSide ExtendedSide;
-
-    enum ClipperSide_ {
-        LEFTSIDE = 0, RIGHTSIDE = 1, UPSIDE = 2, DOWNSIDE = 3
-    };
-    typedef enum ClipperSide_ ClipperSide;
-
-    static inline Side CalcSide(QPoint P1, QPoint P2, Side *next);
-    static inline Side CalcSide(QPoint *P1, QPoint *P2, Side *next);
-    static inline void AddExtSide(ExtendedSide **S, QPoint P0, QPoint P1);
-    static void CreateExtSides(ExtendedSide **sides, QPolygon polygon);
-    static inline void Insert(Side **L, Side *NewSide);
-    static inline void InsertInSortedList(Side **L, Side *NewSide);
-    static void FreeSidesList(Side *L);
-    static void FreeSidesTable(Side *TC[], int top, int bottom);
-    static void FreeExtSide(ExtendedSide *S);
-    static void FreeExtSides(ExtendedSide **S);
-    static inline void AddExtSide(ExtendedSide **dest, QPoint P, QPoint S, Side C);
-    static inline void setRegion(bool reg[4], int x0, int y0, QRect clipRect);
-    static void Sutherland_Hodgman(ExtendedSide **Dest, ExtendedSide *ExtSide, QRect clipRect, ClipperSide CS, bool &PreviousPointOut);
-    static void ClipPolygone(ExtendedSide **ExtSides, QRect *clipper);
-    static inline bool equals(qreal a, qreal b, qreal tolerance);
-    static inline bool valInRange(qreal val, qreal range_min, qreal range_max, qreal tolerance);
-    static int inverseBilinInterp(QVector2D p0_minus_p, QVector2D p1_minus_p, QVector2D p0_minus_p2, QVector2D p1_minus_p3, QPointF& sol1, QPointF& sol2);
-    static inline void bilinInterp(QPointF p0, QPointF p1, QPointF p2, QPointF p3, QPointF st, QPointF& p);
-    static inline qreal det(QVector2D v0, QVector2D v1);
-    static inline void switchVertices(QPoint **a, QPoint **b);
-    static QImage aux_transformation(WarpMathFunction warpMathFunction, QImage *src, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, QPointF originalTopLeft, QPointF *newTopLeft);
 };
 
 #endif
diff --git a/krita/image/tests/kis_warp_transform_worker_test.cpp b/krita/image/tests/kis_warp_transform_worker_test.cpp
index 962d798..ca0f494 100644
--- a/krita/image/tests/kis_warp_transform_worker_test.cpp
+++ b/krita/image/tests/kis_warp_transform_worker_test.cpp
@@ -67,11 +67,246 @@ void KisWarpTransformWorkerTest::test()
                                   transfPoints,
                                   alpha,
                                   updater);
-    worker.run();
+
+    QBENCHMARK_ONCE {
+        worker.run();
+    }
 
     QImage result = dev->convertToQImage(0);
 
     TestUtil::checkQImage(result, "warp_trasnform_test", "simple", "tr");
 }
 
+void KisWarpTransformWorkerTest::testQImage()
+{
+    TestUtil::TestProgressBar bar;
+    KoProgressUpdater pu(&bar);
+    KoUpdaterPtr updater = pu.startSubtask();
+
+//    QImage image(TestUtil::fetchDataFileLazy("test_transform_quality.png"));
+    QImage image(TestUtil::fetchDataFileLazy("test_transform_quality_second.png"));
+    image = image.convertToFormat(QImage::Format_ARGB32);
+
+    qDebug() << ppVar(image.format());
+
+
+    QVector<QPointF> origPoints;
+    QVector<QPointF> transfPoints;
+    qreal alpha = 1.0;
+
+    QRectF bounds(image.rect());
+
+    origPoints << bounds.topLeft();
+    origPoints << bounds.topRight();
+    origPoints << bounds.bottomRight();
+    origPoints << bounds.bottomLeft();
+
+    origPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight());
+    origPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + QPointF(-20, 0);
+
+
+    transfPoints << bounds.topLeft();
+    transfPoints << bounds.bottomLeft() + 0.6 * (bounds.topRight() - bounds.bottomLeft());
+    transfPoints << bounds.topLeft() + 0.8 * (bounds.bottomRight() - bounds.topLeft());
+    transfPoints << bounds.bottomLeft() + QPointF(200, 0);
+
+    transfPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + QPointF(40,20);
+    transfPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + QPointF(-20, 0) + QPointF(-40,20);
+
+
+    QImage result;
+    QPointF newOffset;
+
+    QBENCHMARK_ONCE {
+        result = KisWarpTransformWorker::transformQImage(
+            KisWarpTransformWorker::RIGID_TRANSFORM,
+            origPoints, transfPoints, alpha,
+            image, QPointF(), &newOffset);
+    }
+
+    qDebug() << ppVar(newOffset);
+
+    TestUtil::checkQImage(result, "warp_trasnform_test", "qimage", "tr");
+}
+
+#include "kis_four_point_interpolator_forward.h"
+
+void KisWarpTransformWorkerTest::testForwardInterpolator()
+{
+    QPolygonF src;
+
+    src << QPointF(0, 0);
+    src << QPointF(100, 0);
+    src << QPointF(100, 100);
+    src << QPointF(0, 100);
+
+    QPolygonF dst;
+
+    dst << QPointF(0, 0);
+    dst << QPointF(100, 10);
+    dst << QPointF(100, 120);
+    dst << QPointF(0, 100);
+
+    KisFourPointInterpolatorForward interp(src, dst);
+
+    QCOMPARE(interp.map(QPointF(0,50)), QPointF(0,50));
+    QCOMPARE(interp.map(QPointF(50,0)), QPointF(50,5));
+    QCOMPARE(interp.map(QPointF(100,0)), QPointF(100,10));
+    QCOMPARE(interp.map(QPointF(100,50)), QPointF(100,65));
+
+    QCOMPARE(interp.map(QPointF(100,100)), QPointF(100,120));
+    QCOMPARE(interp.map(QPointF(50,100)), QPointF(50,110));
+    QCOMPARE(interp.map(QPointF(50,50)), QPointF(50,57.5));
+}
+
+
+#include "kis_four_point_interpolator_backward.h"
+
+void KisWarpTransformWorkerTest::testBackwardInterpolatorXShear()
+{
+    QPolygonF src;
+
+    src << QPointF(0, 0);
+    src << QPointF(100, 0);
+    src << QPointF(100, 100);
+    src << QPointF(0, 100);
+
+    QPolygonF dst;
+
+    dst << QPointF(0, 0);
+    dst << QPointF(100, 0);
+    dst << QPointF(120, 100);
+    dst << QPointF(10, 100);
+
+    KisFourPointInterpolatorBackward interp(src, dst);
+
+    QCOMPARE(interp.map(QPointF(10,100)), QPointF(0,100));
+    QCOMPARE(interp.map(QPointF(5,50)), QPointF(0,50));
+    QCOMPARE(interp.map(QPointF(110,50)), QPointF(100,50));
+    QCOMPARE(interp.map(QPointF(57.5,50)), QPointF(50,50));
+}
+
+void KisWarpTransformWorkerTest::testBackwardInterpolatorYShear()
+{
+    QPolygonF src;
+
+    src << QPointF(0, 0);
+    src << QPointF(100, 0);
+    src << QPointF(100, 100);
+    src << QPointF(0, 100);
+
+    QPolygonF dst;
+
+    dst << QPointF(0, 0);
+    dst << QPointF(100, 10);
+    dst << QPointF(100, 120);
+    dst << QPointF(0, 100);
+
+    KisFourPointInterpolatorBackward interp(src, dst);
+
+    QCOMPARE(interp.map(QPointF(100,10)), QPointF(100,0));
+    QCOMPARE(interp.map(QPointF(50,5)), QPointF(50,0));
+    QCOMPARE(interp.map(QPointF(50,110)), QPointF(50,100));
+    QCOMPARE(interp.map(QPointF(50,57.5)), QPointF(50,50));
+}
+
+void KisWarpTransformWorkerTest::testBackwardInterpolatorXYShear()
+{
+    QPolygonF src;
+
+    src << QPointF(0, 0);
+    src << QPointF(100, 0);
+    src << QPointF(100, 100);
+    src << QPointF(0, 100);
+
+    QPolygonF dst;
+
+    dst << QPointF(0, 0);
+    dst << QPointF(100, 10);
+    dst << QPointF(140, 120);
+    dst << QPointF(20, 100);
+
+
+    KisFourPointInterpolatorBackward interp(src, dst);
+
+    QCOMPARE(interp.map(QPointF(100,10)), QPointF(100,0));
+    QCOMPARE(interp.map(QPointF(50,5)), QPointF(50,0));
+    QCOMPARE(interp.map(QPointF(80,110)), QPointF(50,100));
+    QCOMPARE(interp.map(QPointF(120,65)), QPointF(100,50));
+    QCOMPARE(interp.map(QPointF(10,50)), QPointF(0,50));
+}
+
+void KisWarpTransformWorkerTest::testBackwardInterpolatorRoundTrip()
+{
+    QPolygonF src;
+
+    src << QPointF(0, 0);
+    src << QPointF(100, 0);
+    src << QPointF(100, 100);
+    src << QPointF(0, 100);
+
+    QPolygonF dst;
+
+    dst << QPointF(100, 100);
+    dst << QPointF(20, 140);
+    dst << QPointF(10, 80);
+    dst << QPointF(15, 5);
+
+    KisFourPointInterpolatorForward f(src, dst);
+    KisFourPointInterpolatorBackward b(src, dst);
+
+    for (qreal y = 0; y <= 100; y += 1.0) {
+        for (qreal x = 0; x <= 100; x += 1.0) {
+
+            QPointF pt(x, y);
+
+            QPointF fwdPt = f.map(pt);
+            QPointF bwdPt = b.map(fwdPt);
+
+            //qDebug() << "R:" << ppVar(pt) << ppVar(fwdPt) << ppVar(bwdPt) << (bwdPt - pt);
+            QVERIFY((bwdPt - pt).manhattanLength() < 1e-3);
+        }
+    }
+}
+
+void KisWarpTransformWorkerTest::testIteration()
+{
+    const QRect srcBounds(3,3,98,98);
+
+    int pixelPrecision = 8;
+    int alignmentMask = ~(pixelPrecision - 1);
+
+    for (int row = srcBounds.top(); row <= srcBounds.bottom();) {
+
+        for (int col = srcBounds.left(); col <= srcBounds.right();) {
+
+            qDebug() << ppVar(col) << ppVar(row);
+
+
+            col += pixelPrecision;
+
+            if (col > srcBounds.right() &&
+                col < srcBounds.right() + pixelPrecision - 1) {
+
+                col = srcBounds.right();
+            } else {
+                col &= alignmentMask;
+            }
+        }
+
+
+        row += pixelPrecision;
+
+        if (row > srcBounds.bottom() &&
+            row < srcBounds.bottom() + pixelPrecision - 1) {
+
+            row = srcBounds.bottom();
+        } else {
+            row &= alignmentMask;
+        }
+    }
+
+}
+
+
 QTEST_KDEMAIN(KisWarpTransformWorkerTest, GUI)
diff --git a/krita/image/tests/kis_warp_transform_worker_test.h b/krita/image/tests/kis_warp_transform_worker_test.h
index 079319e..51cf9af 100644
--- a/krita/image/tests/kis_warp_transform_worker_test.h
+++ b/krita/image/tests/kis_warp_transform_worker_test.h
@@ -26,6 +26,14 @@ class KisWarpTransformWorkerTest : public QObject
     Q_OBJECT
 private slots:
     void test();
+    void testQImage();
+    void testForwardInterpolator();
+    void testBackwardInterpolatorXShear();
+    void testBackwardInterpolatorYShear();
+    void testBackwardInterpolatorXYShear();
+    void testBackwardInterpolatorRoundTrip();
+
+    void testIteration();
 };
 
 #endif /* __KIS_WARP_TRANSFORM_WORKER_TEST_H */
diff --git a/krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp b/krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp
index d5c6a96..2edd4c0 100644
--- a/krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp
+++ b/krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp
@@ -281,7 +281,13 @@ void KisWarpTransformStrategy::Private::recalculateTransformations()
 
         }
 
-        transformedImage = KisWarpTransformWorker::transformation(currentArgs.warpType(), &transformedImage, thumbOrigPoints, thumbTransfPoints, currentArgs.alpha(), origTLInFlake, &paintingOffset);
+        transformedImage =
+            KisWarpTransformWorker::transformQImage(
+                currentArgs.warpType(),
+                thumbOrigPoints, thumbTransfPoints,
+                currentArgs.alpha(),
+                transformedImage,
+                origTLInFlake, &paintingOffset);
     } else {
         transformedImage = q->originalImage();
         paintingOffset = imageToThumb(transaction.originalTopLeft(), false);


More information about the kimageshop mailing list