1 /*
2  *  kis_warptransform_worker.cc -- part of Krita
3  *
4  *  Copyright (c) 2010 Marc Pegon <pe.marc@free.fr>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #include "kis_warptransform_worker.h"
22 #include "kis_random_sub_accessor.h"
23 #include "kis_iterator_ng.h"
24 #include "kis_datamanager.h"
25 
26 #include <QTransform>
27 #include <QVector2D>
28 #include <QPainter>
29 #include <QVarLengthArray>
30 
31 #include <KoColorSpace.h>
32 #include <KoColor.h>
33 
34 #include <math.h>
35 
36 #include "kis_grid_interpolation_tools.h"
37 
affineTransformMath(QPointF v,QVector<QPointF> p,QVector<QPointF> q,qreal alpha)38 QPointF KisWarpTransformWorker::affineTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
39 {
40     int nbPoints = p.size();
41     QVarLengthArray<qreal> w(nbPoints);
42     qreal sumWi = 0;
43     QPointF pStar(0, 0), qStar(0, 0);
44     QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints);
45 
46     for (int i = 0; i < nbPoints; ++i) {
47         if (v == p[i])
48             return q[i];
49 
50         QVector2D tmp(p[i] - v);
51         w[i] = 1. / pow(tmp.lengthSquared(), alpha);
52         pStar += w[i] * p[i];
53         qStar += w[i] * q[i];
54         sumWi += w[i];
55     }
56     pStar /= sumWi;
57     qStar /= sumWi;
58 
59     qreal A_tmp[4] = {0, 0, 0, 0};
60     for (int i = 0; i < nbPoints; ++i) {
61         pHat[i] = p[i] - pStar;
62         qHat[i] = q[i] - qStar;
63 
64         A_tmp[0] += w[i] * pow(pHat[i].x(), 2);
65         A_tmp[3] += w[i] * pow(pHat[i].y(), 2);
66         A_tmp[1] += w[i] * pHat[i].x() * pHat[i].y();
67     }
68     A_tmp[2] = A_tmp[1];
69     qreal det_A_tmp = A_tmp[0] * A_tmp[3] - A_tmp[1] * A_tmp[2];
70 
71     qreal A_tmp_inv[4];
72 
73     if (det_A_tmp == 0)
74         return v;
75 
76     A_tmp_inv[0] = A_tmp[3] / det_A_tmp;
77     A_tmp_inv[1] = - A_tmp[1] / det_A_tmp;
78     A_tmp_inv[2] = A_tmp_inv[1];
79     A_tmp_inv[3] = A_tmp[0] / det_A_tmp;
80 
81     QPointF t = v - pStar;
82     QPointF A_precalc(t.x() * A_tmp_inv[0] + t.y() * A_tmp_inv[1], t.x() * A_tmp_inv[2] + t.y() * A_tmp_inv[3]);
83     qreal A_j;
84 
85     QPointF res = qStar;
86     for (int j = 0; j < nbPoints; ++j) {
87         A_j = A_precalc.x() * pHat[j].x() + A_precalc.y() * pHat[j].y();
88 
89         res += w[j] * A_j * qHat[j];
90     }
91 
92     return res;
93 }
94 
similitudeTransformMath(QPointF v,QVector<QPointF> p,QVector<QPointF> q,qreal alpha)95 QPointF KisWarpTransformWorker::similitudeTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
96 {
97     int nbPoints = p.size();
98     QVarLengthArray<qreal> w(nbPoints);
99     qreal sumWi = 0;
100     QPointF pStar(0, 0), qStar(0, 0);
101     QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints);
102 
103     for (int i = 0; i < nbPoints; ++i) {
104         if (v == p[i])
105             return q[i];
106 
107         QVector2D tmp(p[i] - v);
108         w[i] = 1. / pow(tmp.lengthSquared(), alpha);
109         pStar += w[i] * p[i];
110         qStar += w[i] * q[i];
111         sumWi += w[i];
112     }
113     pStar /= sumWi;
114     qStar /= sumWi;
115 
116     qreal mu_s = 0;
117     QPointF res_tmp(0, 0);
118     qreal qx, qy, px, py;
119     for (int i = 0; i < nbPoints; ++i) {
120         pHat[i] = p[i] - pStar;
121         qHat[i] = q[i] - qStar;
122 
123         QVector2D tmp(pHat[i]);
124         mu_s += w[i] * tmp.lengthSquared();
125 
126         qx = w[i] * qHat[i].x();
127         qy = w[i] * qHat[i].y();
128         px = pHat[i].x();
129         py = pHat[i].y();
130 
131         res_tmp += QPointF(qx * px + qy * py, qx * py - qy * px);
132     }
133 
134     res_tmp /= mu_s;
135     QPointF v_m_pStar(v - pStar);
136     QPointF res(res_tmp.x() * v_m_pStar.x() + res_tmp.y() * v_m_pStar.y(), res_tmp.x() * v_m_pStar.y() - res_tmp.y() * v_m_pStar.x());
137     res += qStar;
138 
139     return res;
140 }
141 
rigidTransformMath(QPointF v,QVector<QPointF> p,QVector<QPointF> q,qreal alpha)142 QPointF KisWarpTransformWorker::rigidTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
143 {
144     int nbPoints = p.size();
145     QVarLengthArray<qreal> w(nbPoints);
146     qreal sumWi = 0;
147     QPointF pStar(0, 0), qStar(0, 0);
148     QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints);
149 
150     for (int i = 0; i < nbPoints; ++i) {
151         if (v == p[i])
152             return q[i];
153 
154         QVector2D tmp(p[i] - v);
155         w[i] = 1. / pow(tmp.lengthSquared(), alpha);
156         pStar += w[i] * p[i];
157         qStar += w[i] * q[i];
158         sumWi += w[i];
159     }
160     pStar /= sumWi;
161     qStar /= sumWi;
162 
163     QVector2D res_tmp(0, 0);
164     qreal qx, qy, px, py;
165     for (int i = 0; i < nbPoints; ++i) {
166         pHat[i] = p[i] - pStar;
167         qHat[i] = q[i] - qStar;
168 
169         qx = w[i] * qHat[i].x();
170         qy = w[i] * qHat[i].y();
171         px = pHat[i].x();
172         py = pHat[i].y();
173 
174         res_tmp += QVector2D(qx * px + qy * py, qx * py - qy * px);
175     }
176 
177     QPointF f_arrow(res_tmp.normalized().toPointF());
178     QVector2D v_m_pStar(v - pStar);
179     QPointF res(f_arrow.x() * v_m_pStar.x() + f_arrow.y() * v_m_pStar.y(), f_arrow.x() * v_m_pStar.y() - f_arrow.y() * v_m_pStar.x());
180     res += qStar;
181 
182     return res;
183 }
184 
KisWarpTransformWorker(WarpType warpType,KisPaintDeviceSP dev,QVector<QPointF> origPoint,QVector<QPointF> transfPoint,qreal alpha,KoUpdater * progress)185 KisWarpTransformWorker::KisWarpTransformWorker(WarpType warpType, KisPaintDeviceSP dev, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, KoUpdater *progress)
186         : m_dev(dev), m_progress(progress)
187 {
188     m_origPoint = origPoint;
189     m_transfPoint = transfPoint;
190     m_alpha = alpha;
191 
192     switch(warpType) {
193     case AFFINE_TRANSFORM:
194         m_warpMathFunction = &affineTransformMath;
195         break;
196     case SIMILITUDE_TRANSFORM:
197         m_warpMathFunction = &similitudeTransformMath;
198         break;
199     case RIGID_TRANSFORM:
200         m_warpMathFunction = &rigidTransformMath;
201         break;
202     default:
203         m_warpMathFunction = 0;
204         break;
205     }
206 }
207 
~KisWarpTransformWorker()208 KisWarpTransformWorker::~KisWarpTransformWorker()
209 {
210 }
211 
212 struct KisWarpTransformWorker::FunctionTransformOp
213 {
FunctionTransformOpKisWarpTransformWorker::FunctionTransformOp214     FunctionTransformOp(KisWarpTransformWorker::WarpMathFunction function,
215                         const QVector<QPointF> &p,
216                         const QVector<QPointF> &q,
217                         qreal alpha)
218         : m_function(function),
219           m_p(p),
220           m_q(q),
221           m_alpha(alpha)
222     {
223     }
224 
operator ()KisWarpTransformWorker::FunctionTransformOp225     QPointF operator() (const QPointF &pt) const {
226         return m_function(pt, m_p, m_q, m_alpha);
227     }
228 
229     KisWarpTransformWorker::WarpMathFunction m_function;
230     const QVector<QPointF> &m_p;
231     const QVector<QPointF> &m_q;
232     qreal m_alpha;
233 };
234 
run()235 void KisWarpTransformWorker::run()
236 {
237 
238     if (!m_warpMathFunction ||
239         m_origPoint.isEmpty() ||
240         m_origPoint.size() != m_transfPoint.size()) {
241 
242         return;
243     }
244 
245     KisPaintDeviceSP srcdev = new KisPaintDevice(*m_dev.data());
246 
247     if (m_origPoint.size() == 1) {
248         QPointF translate(QPointF(m_dev->x(), m_dev->y()) + m_transfPoint[0] - m_origPoint[0]);
249         m_dev->moveTo(translate.toPoint());
250         return;
251     }
252 
253     const QRect srcBounds = srcdev->region().boundingRect();
254 
255     m_dev->clear();
256 
257     const int pixelPrecision = 8;
258 
259     FunctionTransformOp functionOp(m_warpMathFunction, m_origPoint, m_transfPoint, m_alpha);
260     GridIterationTools::PaintDevicePolygonOp polygonOp(srcdev, m_dev);
261     GridIterationTools::processGrid(polygonOp, functionOp,
262                                     srcBounds, pixelPrecision);
263 }
264 
265 #include "krita_utils.h"
266 
approxChangeRect(const QRect & rc)267 QRect KisWarpTransformWorker::approxChangeRect(const QRect &rc)
268 {
269     const qreal margin = 0.05;
270 
271     FunctionTransformOp functionOp(m_warpMathFunction, m_origPoint, m_transfPoint, m_alpha);
272     QRect resultRect = KisAlgebra2D::approximateRectWithPointTransform(rc, functionOp);
273 
274     return KisAlgebra2D::blowRect(resultRect, margin);
275 }
276 
approxNeedRect(const QRect & rc,const QRect & fullBounds)277 QRect KisWarpTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds)
278 {
279     Q_UNUSED(rc);
280     return fullBounds;
281 }
282 
transformQImage(WarpType warpType,const QVector<QPointF> & origPoint,const QVector<QPointF> & transfPoint,qreal alpha,const QImage & srcImage,const QPointF & srcQImageOffset,QPointF * newOffset)283 QImage KisWarpTransformWorker::transformQImage(WarpType warpType,
284                                                const QVector<QPointF> &origPoint,
285                                                const QVector<QPointF> &transfPoint,
286                                                qreal alpha,
287                                                const QImage& srcImage,
288                                                const QPointF &srcQImageOffset,
289                                                QPointF *newOffset)
290 {
291     KIS_ASSERT_RECOVER(srcImage.format() == QImage::Format_ARGB32) {
292         return QImage();
293     }
294 
295     WarpMathFunction warpMathFunction = &rigidTransformMath;
296 
297     switch (warpType) {
298     case AFFINE_TRANSFORM:
299         warpMathFunction = &affineTransformMath;
300         break;
301     case SIMILITUDE_TRANSFORM:
302         warpMathFunction = &similitudeTransformMath;
303         break;
304     case RIGID_TRANSFORM:
305         warpMathFunction = &rigidTransformMath;
306         break;
307     default:
308         KIS_ASSERT_RECOVER(0 && "Unknown warp mode") { return QImage(); }
309     }
310 
311     if (!warpMathFunction ||
312         origPoint.isEmpty() ||
313         origPoint.size() != transfPoint.size()) {
314 
315         return srcImage;
316     }
317 
318     if (origPoint.size() == 1) {
319         *newOffset = srcQImageOffset + (transfPoint[0] - origPoint[0]).toPoint();
320         return srcImage;
321     }
322 
323     FunctionTransformOp functionOp(warpMathFunction, origPoint, transfPoint, alpha);
324 
325     const QRectF srcBounds = QRectF(srcQImageOffset, srcImage.size());
326     QRectF dstBounds;
327 
328     {
329         QPolygonF testPoints;
330         testPoints << srcBounds.topLeft();
331         testPoints << srcBounds.topRight();
332         testPoints << srcBounds.bottomRight();
333         testPoints << srcBounds.bottomLeft();
334         testPoints << srcBounds.topLeft();
335 
336         QPolygonF::iterator it = testPoints.begin() + 1;
337 
338         while (it != testPoints.end()) {
339             it = testPoints.insert(it, 0.5 * (*it + *(it - 1)));
340             it += 2;
341         }
342 
343         it = testPoints.begin();
344 
345         while (it != testPoints.end()) {
346             *it = functionOp(*it);
347             ++it;
348         }
349 
350         dstBounds = testPoints.boundingRect();
351     }
352 
353     QPointF dstQImageOffset = dstBounds.topLeft();
354     *newOffset = dstQImageOffset;
355 
356     QRect dstBoundsI = dstBounds.toAlignedRect();
357     QImage dstImage(dstBoundsI.size(), srcImage.format());
358     dstImage.fill(0);
359 
360     const int pixelPrecision = 32;
361     GridIterationTools::QImagePolygonOp polygonOp(srcImage, dstImage, srcQImageOffset, dstQImageOffset);
362     GridIterationTools::processGrid(polygonOp, functionOp, srcBounds.toAlignedRect(), pixelPrecision);
363 
364     return dstImage;
365 }
366