Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_liquify_transform_worker.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <KoColorSpace.h>
11#include "kis_dom_utils.h"
12#include "krita_utils.h"
13#include "KisSpatialContainer.h"
14
15
17{
18 Private(const QRect &_srcBounds,
19 KoUpdater *_progress,
20 int _pixelPrecision)
21 : srcBounds(_srcBounds),
22 progress(_progress),
23 pixelPrecision(_pixelPrecision)
24 , originalPointsContainer(_srcBounds)
25 , transformedPointsContainer(_srcBounds)
26 {
27 }
28
29 QRect srcBounds;
30
33
36
38
41 QSize gridSize;
42
44
45 struct MapIndexesOp;
46
47 template <class ProcessOp>
49 const QPointF &base,
50 qreal sigma);
51
52 template <class ProcessOp>
54 const QPointF &base,
55 qreal sigma,
56 qreal flow);
57
58 template <class ProcessOp>
59 void processTransformedPixels(ProcessOp op,
60 const QPointF &base,
61 qreal sigma,
62 bool useWashMode,
63 qreal flow);
64};
65
67 KoUpdater *progress,
68 int pixelPrecision)
69 : m_d(new Private(srcBounds, progress, pixelPrecision))
70{
72
73 // TODO: implement 'progress' stuff
74 m_d->preparePoints();
75}
76
81
85
87{
88 bool result =
89 m_d->srcBounds == other.m_d->srcBounds &&
90 m_d->pixelPrecision == other.m_d->pixelPrecision &&
91 m_d->gridSize == other.m_d->gridSize &&
92 m_d->originalPoints.size() == other.m_d->originalPoints.size() &&
93 m_d->transformedPoints.size() == other.m_d->transformedPoints.size();
94
95 if (!result) return false;
96
97 const qreal eps = 1e-6;
98
99 result =
100 KisAlgebra2D::fuzzyPointCompare(m_d->originalPoints, other.m_d->originalPoints, eps) &&
101 KisAlgebra2D::fuzzyPointCompare(m_d->transformedPoints, other.m_d->transformedPoints, eps);
102
103 return result;
104}
105
107{
108 const qreal eps = 1e-6;
109 return KisAlgebra2D::fuzzyPointCompare(m_d->originalPoints, m_d->transformedPoints, eps);
110}
111
113{
114 return GridIterationTools::pointToIndex(cellPt, m_d->gridSize);
115}
116
118{
119 return m_d->gridSize;
120}
121
123{
124 return m_d->originalPoints;
125}
126
128{
129 return m_d->transformedPoints;
130}
131
133{
134 AllPointsFetcherOp(QRectF srcRect, QSize gridSize) : m_srcRect(srcRect), m_gridSize(gridSize) {}
135
136 inline void processPoint(int col, int row,
137 int prevCol, int prevRow,
138 int colIndex, int rowIndex) {
139
140 Q_UNUSED(prevCol);
141 Q_UNUSED(prevRow);
142
143 // it would give us an integer point that signifies a start of the pixel
144 // but we want a decimal value at the end
145 if (colIndex == m_gridSize.width() - 1) {
146 col++;
147 }
148 if (rowIndex == m_gridSize.height() - 1) {
149 row++;
150 }
151
152 QPointF pt(col, row);
153 m_points << pt;
154 }
155
156 inline void nextLine() {
157 }
158
160 QRectF m_srcRect;
162};
163
164void KisLiquifyTransformWorker::Private::preparePoints()
165{
166 gridSize =
167 GridIterationTools::calcGridSize(srcBounds, pixelPrecision);
168
169 AllPointsFetcherOp pointsOp(srcBounds, gridSize);
170 GridIterationTools::processGrid(pointsOp, srcBounds, pixelPrecision);
171
172 const int numPoints = pointsOp.m_points.size();
173
174 KIS_ASSERT_RECOVER_RETURN(numPoints == gridSize.width() * gridSize.height());
175
176 originalPoints = pointsOp.m_points;
177 transformedPoints = pointsOp.m_points;
178
179 originalPointsContainer.initializeWithGridPoints(srcBounds, pixelPrecision);
180 transformedPointsContainer.initializeWithGridPoints(srcBounds, pixelPrecision);
181
182}
183
184void KisLiquifyTransformWorker::translate(const QPointF &offset)
185{
186 KIS_ASSERT_RECOVER_RETURN(m_d->originalPoints.size() ==
187 m_d->transformedPoints.size());
188
189 // TODO: make it within Spatial Container, either a hidden offset, or just offsetting all points at once
190 // and benchmark
191 for (int i = 0; i < m_d->transformedPoints.count(); i++) {
192 m_d->originalPointsContainer.movePoint(i, m_d->originalPoints[i], m_d->originalPoints[i] + offset);
193 m_d->transformedPointsContainer.movePoint(i, m_d->transformedPoints[i], m_d->transformedPoints[i] + offset);
194
195 m_d->originalPoints[i] += offset;
196 m_d->transformedPoints[i] += offset;
197 }
198
199 m_d->accumulatedBrushStrokes.translate(offset);
200}
201
203{
204 // TODO: make it within Spatial Container, either a hidden offset, or just offsetting all points at once
205 // and benchmark
206 for (int i = 0; i < m_d->transformedPoints.count(); i++) {
207 m_d->transformedPointsContainer.movePoint(i, m_d->transformedPoints[i], m_d->transformedPoints[i] + offset);
208 m_d->transformedPoints[i] += offset;
209 }
210}
211
213 qreal amount,
214 qreal sigma)
215{
216 const qreal maxDistCoeff = 3.0;
217 const qreal maxDist = maxDistCoeff * sigma;
218
219 KIS_ASSERT_RECOVER_RETURN(m_d->originalPoints.size() ==
220 m_d->transformedPoints.size());
221
222 QVector<int> indexes;
223 m_d->transformedPointsContainer.findAllInRange(indexes, base, maxDist);
224 for (int i = 0; i < indexes.count(); i++) {
225
226 QPointF diff = m_d->transformedPoints[indexes[i]] - base;
227 qreal dist = KisAlgebra2D::norm(diff);
228 qreal lambda = exp(-0.5 * pow2(dist / sigma));
229 lambda *= amount;
230
231 QPointF oldPosition = m_d->transformedPoints[indexes[i]];
232 m_d->transformedPoints[indexes[i]] = m_d->originalPoints[indexes[i]] * lambda + m_d->transformedPoints[indexes[i]] * (1.0 - lambda);
233
234 m_d->transformedPointsContainer.movePoint(indexes[i], oldPosition, m_d->transformedPoints[indexes[i]]);
235 }
236}
237
238template <class ProcessOp>
239void KisLiquifyTransformWorker::Private::
240processTransformedPixelsBuildUp(ProcessOp op,
241 const QPointF &base,
242 qreal sigma)
243{
244 const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
245 QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
246 2 * maxDist, 2 * maxDist);
247
248 accumulatedBrushStrokes |= clipRect;
249
250 QVector<int> indexes;
251 transformedPointsContainer.findAllInRange(indexes, base, maxDist);
252
253 for (int i = 0; i < indexes.count(); i++) {
254
255 QPointF diff = transformedPoints[indexes[i]] - base;
256 qreal dist = KisAlgebra2D::norm(diff);
257 if (dist > maxDist) continue;
258
259 const qreal lambda = exp(-0.5 * pow2(dist / sigma));
260 QPointF oldPosition = transformedPoints[indexes[i]];
261 transformedPoints[indexes[i]] = op(transformedPoints[indexes[i]], base, diff, lambda);
262
263
264 transformedPointsContainer.movePoint(indexes[i], oldPosition, transformedPoints[indexes[i]]);
265
266 }
267}
268
269template <class ProcessOp>
270void KisLiquifyTransformWorker::Private::
271processTransformedPixelsWash(ProcessOp op,
272 const QPointF &base,
273 qreal sigma,
274 qreal flow)
275{
276 const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
277 QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
278 2 * maxDist, 2 * maxDist);
279
280 accumulatedBrushStrokes |= clipRect;
281
282 KIS_ASSERT_RECOVER_RETURN(originalPoints.size() ==
283 transformedPoints.size());
284
285 // TODO: remove the originalPointsContainer entirely, and use GridIterationTools to figure out indexes instead
286 // and add unit tests for it
287
288 QVector<int> indexes;
289 originalPointsContainer.findAllInRange(indexes, base, maxDist);
290 for (int i = 0; i < indexes.count(); i++) {
291
292 QPointF diff = originalPoints[indexes[i]] - base;
293 qreal dist = KisAlgebra2D::norm(diff);
294
295 const qreal lambda = exp(-0.5 * pow2(dist / sigma));
296 QPointF dstPt = op(originalPoints[indexes[i]], base, diff, lambda);
297
298 if (kisDistance(dstPt, originalPoints[indexes[i]]) > kisDistance(transformedPoints[indexes[i]], originalPoints[indexes[i]])) {
299 QPointF oldPosition = transformedPoints[indexes[i]];
300 transformedPoints[indexes[i]] = (1.0 - flow) * transformedPoints[indexes[i]] + flow * dstPt;
301
302 transformedPointsContainer.movePoint(indexes[i], oldPosition, transformedPoints[indexes[i]]);
303 }
304 }
305}
306
307template <class ProcessOp>
308void KisLiquifyTransformWorker::Private::
309processTransformedPixels(ProcessOp op,
310 const QPointF &base,
311 qreal sigma,
312 bool useWashMode,
313 qreal flow)
314{
315 if (useWashMode) {
316 processTransformedPixelsWash(op, base, sigma, flow);
317 } else {
318 processTransformedPixelsBuildUp(op, base, sigma);
319 }
320}
321
323{
324 TranslateOp(const QPointF &offset) : m_offset(offset) {}
325
326 QPointF operator() (const QPointF &pt,
327 const QPointF &base,
328 const QPointF &diff,
329 qreal lambda)
330 {
331 Q_UNUSED(base);
332 Q_UNUSED(diff);
333 return pt + lambda * m_offset;
334 }
335
336 static const qreal maxDistCoeff;
337
338 QPointF m_offset;
339};
340
341const qreal TranslateOp::maxDistCoeff = 3.0;
342
344{
345 ScaleOp(qreal scale) : m_scale(scale) {}
346
347 QPointF operator() (const QPointF &pt,
348 const QPointF &base,
349 const QPointF &diff,
350 qreal lambda)
351 {
352 Q_UNUSED(pt);
353 Q_UNUSED(diff);
354 return base + (1.0 + m_scale * lambda) * diff;
355 }
356
357 static const qreal maxDistCoeff;
358
359 qreal m_scale;
360};
361
362const qreal ScaleOp::maxDistCoeff = 3.0;
363
365{
366 RotateOp(qreal angle) : m_angle(angle) {}
367
368 QPointF operator() (const QPointF &pt,
369 const QPointF &base,
370 const QPointF &diff,
371 qreal lambda)
372 {
373 Q_UNUSED(pt);
374
375 const qreal angle = m_angle * lambda;
376 const qreal sinA = std::sin(angle);
377 const qreal cosA = std::cos(angle);
378
379 qreal x = cosA * diff.x() + sinA * diff.y();
380 qreal y = -sinA * diff.x() + cosA * diff.y();
381
382 return base + QPointF(x, y);
383 }
384
385 static const qreal maxDistCoeff;
386
387 qreal m_angle;
388};
389
390const qreal RotateOp::maxDistCoeff = 3.0;
391
393 const QPointF &offset,
394 qreal sigma,
395 bool useWashMode,
396 qreal flow)
397{
398 TranslateOp op(offset);
399 m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
400}
401
403 qreal scale,
404 qreal sigma,
405 bool useWashMode,
406 qreal flow)
407{
408 ScaleOp op(scale);
409 m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
410}
411
413 qreal angle,
414 qreal sigma,
415 bool useWashMode,
416 qreal flow)
417{
418 RotateOp op(angle);
419 m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
420}
421
423{
424 KIS_SAFE_ASSERT_RECOVER_RETURN(*srcDevice->colorSpace() == *dstDevice->colorSpace());
425
426 dstDevice->clear();
427
428 using namespace GridIterationTools;
429 QRectF accumulatedBrushStrokesWithMargin = kisGrowRect(m_d->accumulatedBrushStrokes, 2*m_d->pixelPrecision);
430 QRect correctSubGrid = calculateCorrectSubGrid(m_d->srcBounds, m_d->pixelPrecision, accumulatedBrushStrokesWithMargin, m_d->gridSize);
431
432 PaintDevicePolygonOp polygonOp(srcDevice, dstDevice);
433 RegularGridIndexesOp indexesOp(m_d->gridSize);
434
435 bool canMergeRects = GridIterationTools::canProcessRectsInRandomOrder(indexesOp, m_d->transformedPoints, correctSubGrid);
436 polygonOp.setCanMergeRects(canMergeRects);
437
438#ifdef DEBUG_PAINTING_POLYGONS
439 polygonOp.setDebugColor(Qt::red);
440#endif
441
442 iterateThroughGrid<AlwaysCompletePolygonPolicy>(polygonOp, indexesOp,
443 m_d->gridSize,
444 m_d->originalPoints,
445 m_d->transformedPoints,
446 correctSubGrid);
447 QList<QRectF> areasToCopy = cutOutSubgridFromBounds(correctSubGrid, m_d->srcBounds, m_d->gridSize, m_d->originalPoints);
448#ifdef DEBUG_PAINTING_POLYGONS
449 QList<QColor> colors = {Qt::blue, Qt::green, Qt::yellow, Qt::black};
450#endif
451 for (int i = 0; i < areasToCopy.length(); i++) {
452#ifdef DEBUG_PAINTING_POLYGONS
453 polygonOp.setDebugColor(colors[i]);
454#endif
455 polygonOp.fastCopyArea(areasToCopy[i].toRect(), false);
456 }
457}
458
460{
461 const qreal margin = 0.05;
462 QRect resultRect = m_d->transformedPointsContainer.exactBounds().toRect();
463 return KisAlgebra2D::blowRect(resultRect | rc, margin);
464}
465
466QRect KisLiquifyTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds)
467{
468 Q_UNUSED(rc);
469 return fullBounds;
470}
471
473{
474 return m_d->accumulatedBrushStrokes;
475}
476
478{
479 KIS_SAFE_ASSERT_RECOVER_RETURN(t.type() <= QTransform::TxScale);
480
481 m_d->srcBounds = t.mapRect(m_d->srcBounds);
482
483 // TODO: do it within Spatial Container
484 for (int i = 0; i < m_d->transformedPoints.count(); i++) {
485 m_d->originalPointsContainer.movePoint(i, m_d->originalPoints[i], t.map(m_d->originalPoints[i]));
486 m_d->transformedPointsContainer.movePoint(i, m_d->transformedPoints[i], t.map(m_d->transformedPoints[i]));
487
488 m_d->originalPoints[i] = t.map(m_d->originalPoints[i]);
489 m_d->transformedPoints[i] = t.map(m_d->transformedPoints[i]);
490 }
491 m_d->accumulatedBrushStrokes = t.map(m_d->accumulatedBrushStrokes).boundingRect();
492 if (t == QTransform::fromScale(t.m11(), t.m22()) && t.m11() == t.m22()) {
493 m_d->pixelPrecision *= t.m11();
494 KIS_SAFE_ASSERT_RECOVER(m_d->pixelPrecision > 0) { m_d->pixelPrecision = 1; }
495 KIS_SAFE_ASSERT_RECOVER(QList<int>({1, 2, 4, 8, 16}).contains(m_d->pixelPrecision) || m_d->pixelPrecision%16 == 0) { m_d->pixelPrecision = 1; }
496 // should check if pixelPrecision is a power of 2, but that's more complicated
497 }
498}
499
500#include <functional>
501#include <QTransform>
502
503using PointMapFunction = std::function<QPointF (const QPointF&)>;
504
505
506PointMapFunction bindPointMapTransform(const QTransform &transform) {
507 using namespace std::placeholders;
508
509 typedef QPointF (QTransform::*MapFuncType)(const QPointF&) const;
510 return std::bind(static_cast<MapFuncType>(&QTransform::map), &transform, _1);
511}
512
513QImage KisLiquifyTransformWorker::runOnQImage(const QImage &srcImage,
514 const QPointF &srcImageOffset,
515 const QTransform &imageToThumbTransform,
516 QPointF *newOffset)
517{
518 KIS_ASSERT_RECOVER(m_d->originalPoints.size() == m_d->transformedPoints.size()) {
519 return QImage();
520 }
521
522 KIS_ASSERT_RECOVER(!srcImage.isNull()) {
523 return QImage();
524 }
525
526 KIS_ASSERT_RECOVER(srcImage.format() == QImage::Format_ARGB32) {
527 return QImage();
528 }
529
530 QVector<QPointF> originalPointsLocal(m_d->originalPoints);
531 QVector<QPointF> transformedPointsLocal(m_d->transformedPoints);
532
533 PointMapFunction mapFunc = bindPointMapTransform(imageToThumbTransform);
534
535 std::transform(originalPointsLocal.begin(), originalPointsLocal.end(),
536 originalPointsLocal.begin(), mapFunc);
537
538 std::transform(transformedPointsLocal.begin(), transformedPointsLocal.end(),
539 transformedPointsLocal.begin(), mapFunc);
540
541 QRectF dstBounds;
542 Q_FOREACH (const QPointF &pt, transformedPointsLocal) {
543 KisAlgebra2D::accumulateBounds(pt, &dstBounds);
544 }
545
546 const QRectF srcBounds(srcImageOffset, srcImage.size());
547 dstBounds |= srcBounds;
548
549 QPointF dstQImageOffset = dstBounds.topLeft();
550 *newOffset = dstQImageOffset;
551
552 QRect dstBoundsI = dstBounds.toAlignedRect();
553
554 QImage dstImage(dstBoundsI.size(), srcImage.format());
555 dstImage.fill(0);
556
557 GridIterationTools::QImagePolygonOp polygonOp(srcImage, dstImage, srcImageOffset, dstQImageOffset);
559
560
561 QRectF accumulatedBrushStrokesWithMargin = kisGrowRect(m_d->accumulatedBrushStrokes, 3*m_d->pixelPrecision);
562 QRect correctSubGrid = GridIterationTools::calculateCorrectSubGrid(m_d->srcBounds, m_d->pixelPrecision, accumulatedBrushStrokesWithMargin, m_d->gridSize);
563 bool canMergeRects = GridIterationTools::canProcessRectsInRandomOrder(indexesOp, m_d->transformedPoints, correctSubGrid);
564 polygonOp.setCanMergeRects(canMergeRects);
565
566
567 GridIterationTools::iterateThroughGrid<GridIterationTools::AlwaysCompletePolygonPolicy>(polygonOp, indexesOp,
568 m_d->gridSize,
569 originalPointsLocal,
570 transformedPointsLocal,
571 correctSubGrid);
572
573
574 QList<QRectF> areasToCopy = GridIterationTools::cutOutSubgridFromBounds(correctSubGrid, m_d->srcBounds, m_d->gridSize, m_d->originalPoints);
575 polygonOp.setCanMergeRects(false);
576 for (int i = 0; i < areasToCopy.length(); i++) {
577 polygonOp.fastCopyArea(imageToThumbTransform.map(QPolygonF(areasToCopy[i])));
578 }
579 return dstImage;
580}
581
582void KisLiquifyTransformWorker::toXML(QDomElement *e) const
583{
584 QDomDocument doc = e->ownerDocument();
585 QDomElement liqEl = doc.createElement("liquify_points");
586 e->appendChild(liqEl);
587
588 KisDomUtils::saveValue(&liqEl, "srcBounds", m_d->srcBounds);
589 KisDomUtils::saveValue(&liqEl, "originalPoints", m_d->originalPoints);
590 KisDomUtils::saveValue(&liqEl, "transformedPoints", m_d->transformedPoints);
591 KisDomUtils::saveValue(&liqEl, "pixelPrecision", m_d->pixelPrecision);
592 KisDomUtils::saveValue(&liqEl, "gridSize", m_d->gridSize);
593}
594
596{
597 QDomElement liquifyEl;
598
599 QRect srcBounds;
602 int pixelPrecision;
603 QSize gridSize;
604
605 bool result = false;
606
607
608 result =
609 KisDomUtils::findOnlyElement(e, "liquify_points", &liquifyEl) &&
610
611 KisDomUtils::loadValue(liquifyEl, "srcBounds", &srcBounds) &&
612 KisDomUtils::loadValue(liquifyEl, "originalPoints", &originalPoints) &&
613 KisDomUtils::loadValue(liquifyEl, "transformedPoints", &transformedPoints) &&
614 KisDomUtils::loadValue(liquifyEl, "pixelPrecision", &pixelPrecision) &&
615 KisDomUtils::loadValue(liquifyEl, "gridSize", &gridSize);
616
617 if (!result) {
618 warnKrita << "WARNING: Failed to load liquify worker from XML";
619 return new KisLiquifyTransformWorker(QRect(0,0,1024, 1024), 0, 8);
620 }
621
624
625 const int numPoints = originalPoints.size();
626
627 if (numPoints != transformedPoints.size() ||
628 numPoints != worker->m_d->originalPoints.size() ||
629 gridSize != worker->m_d->gridSize) {
630 warnKrita << "WARNING: Inconsistent number of points!";
631 warnKrita << ppVar(originalPoints.size());
634 warnKrita << ppVar(worker->m_d->originalPoints.size());
635 warnKrita << ppVar(worker->m_d->transformedPoints.size());
636 warnKrita << ppVar(worker->m_d->gridSize);
637
638 return worker;
639 }
640
641 QRectF changedRect = QRectF();
642
643 for (int i = 0; i < numPoints; i++) {
644 worker->m_d->originalPoints[i] = originalPoints[i];
645 worker->m_d->transformedPoints[i] = transformedPoints[i];
648 }
649 }
650
651 worker->m_d->transformedPointsContainer.initializeWith(worker->m_d->transformedPoints);
652 worker->m_d->originalPointsContainer.initializeWith(worker->m_d->originalPoints);
653
654 worker->m_d->accumulatedBrushStrokes = changedRect;
655
656
657 return worker;
658}
virtual void clear()
const KoColorSpace * colorSpace() const
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
const qreal eps
#define warnKrita
Definition kis_debug.h:87
#define ppVar(var)
Definition kis_debug.h:155
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:190
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
T pow2(const T &x)
Definition kis_global.h:166
std::function< QPointF(const QPointF &)> PointMapFunction
PointMapFunction bindPointMapTransform(const QTransform &transform)
bool canProcessRectsInRandomOrder(IndexesOp &indexesOp, const QVector< QPointF > &transformedPoints, QSize grid)
QList< QRectF > cutOutSubgridFromBounds(QRect subGrid, QRect srcBounds, const QSize &gridSize, const QVector< QPointF > &originalPoints)
QRect calculateCorrectSubGrid(QRect originalBoundsForGrid, int pixelPrecision, QRectF currentBounds, QSize gridSize)
int pointToIndex(const QPoint &cellPt, const QSize &gridSize)
void processGrid(ProcessCell &cellOp, const QRect &srcBounds, const int pixelPrecision)
QSize calcGridSize(const QRect &srcBounds, const int pixelPrecision)
Rect blowRect(const Rect &rect, qreal coeff)
void accumulateBounds(const Point &pt, Rect *bounds)
qreal norm(const T &a)
bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2)
void saveValue(QDomElement *parent, const QString &tag, const QSize &size)
bool findOnlyElement(const QDomElement &parent, const QString &tag, QDomElement *el, QStringList *errorMessages)
bool loadValue(const QDomElement &e, float *v)
void processPoint(int col, int row, int prevCol, int prevRow, int colIndex, int rowIndex)
AllPointsFetcherOp(QRectF srcRect, QSize gridSize)
void rotatePoints(const QPointF &base, qreal angle, qreal sigma, bool useWashMode, qreal flow)
Private(const QRect &_srcBounds, KoUpdater *_progress, int _pixelPrecision)
KisLiquifyTransformWorker(const QRect &srcBounds, KoUpdater *progress, int pixelPrecision=8)
void translate(const QPointF &offset)
void undoPoints(const QPointF &base, qreal amount, qreal sigma)
void translateDstSpace(const QPointF &offset)
QImage runOnQImage(const QImage &srcImage, const QPointF &srcImageOffset, const QTransform &imageToThumbTransform, QPointF *newOffset)
void transformSrcAndDst(const QTransform &t)
void scalePoints(const QPointF &base, qreal scale, qreal sigma, bool useWashMode, qreal flow)
void processTransformedPixelsWash(ProcessOp op, const QPointF &base, qreal sigma, qreal flow)
static KisLiquifyTransformWorker * fromXML(const QDomElement &e)
const QScopedPointer< Private > m_d
void processTransformedPixelsBuildUp(ProcessOp op, const QPointF &base, qreal sigma)
void processTransformedPixels(ProcessOp op, const QPointF &base, qreal sigma, bool useWashMode, qreal flow)
bool operator==(const KisLiquifyTransformWorker &other) const
void translatePoints(const QPointF &base, const QPointF &offset, qreal sigma, bool useWashMode, qreal flow)
QRect approxNeedRect(const QRect &rc, const QRect &fullBounds)
void run(KisPaintDeviceSP srcDevice, KisPaintDeviceSP dstDevice)
QPointF operator()(const QPointF &pt, const QPointF &base, const QPointF &diff, qreal lambda)
static const qreal maxDistCoeff
static const qreal maxDistCoeff
QPointF operator()(const QPointF &pt, const QPointF &base, const QPointF &diff, qreal lambda)
TranslateOp(const QPointF &offset)
QPointF operator()(const QPointF &pt, const QPointF &base, const QPointF &diff, qreal lambda)
static const qreal maxDistCoeff