[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, ¤tLineVertices);
- 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, ¤tLineVertices);
+ {
+ 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