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