23 : srcBounds(_srcBounds)
24 , originalPointsContainer(_srcBounds)
25 , transformedPointsContainer(_srcBounds)
27 , pixelPrecision(_pixelPrecision)
49 template <
class ProcessOp>
54 template <
class ProcessOp>
60 template <
class ProcessOp>
71 : m_d(new
Private(srcBounds, progress, pixelPrecision))
80 : m_d(new
Private(*rhs.m_d.data()))
91 m_d->srcBounds == other.
m_d->srcBounds &&
92 m_d->pixelPrecision == other.
m_d->pixelPrecision &&
93 m_d->gridSize == other.
m_d->gridSize &&
94 m_d->originalPoints.size() == other.
m_d->originalPoints.size() &&
95 m_d->transformedPoints.size() == other.
m_d->transformedPoints.size();
97 if (!result)
return false;
99 const qreal
eps = 1e-6;
110 const qreal
eps = 1e-6;
121 return m_d->gridSize;
126 return m_d->originalPoints;
131 return m_d->transformedPoints;
139 int prevCol,
int prevRow,
140 int colIndex,
int rowIndex) {
147 QPointF pt(col, row);
158void KisLiquifyTransformWorker::Private::preparePoints()
166 const int numPoints = pointsOp.
m_points.size();
171 transformedPoints = pointsOp.
m_points;
173 originalPointsContainer.initializeWithGridPoints(srcBounds, pixelPrecision);
174 transformedPointsContainer.initializeWithGridPoints(srcBounds, pixelPrecision);
181 m_d->transformedPoints.size());
185 for (
int i = 0; i <
m_d->transformedPoints.count(); i++) {
186 m_d->originalPointsContainer.movePoint(i,
m_d->originalPoints[i],
m_d->originalPoints[i] + offset);
187 m_d->transformedPointsContainer.movePoint(i,
m_d->transformedPoints[i],
m_d->transformedPoints[i] + offset);
189 m_d->originalPoints[i] += offset;
190 m_d->transformedPoints[i] += offset;
193 m_d->accumulatedBrushStrokes.translate(offset);
200 for (
int i = 0; i <
m_d->transformedPoints.count(); i++) {
201 m_d->transformedPointsContainer.movePoint(i,
m_d->transformedPoints[i],
m_d->transformedPoints[i] + offset);
202 m_d->transformedPoints[i] += offset;
210 const qreal maxDistCoeff = 3.0;
211 const qreal maxDist = maxDistCoeff * sigma;
214 m_d->transformedPoints.size());
217 m_d->transformedPointsContainer.findAllInRange(indexes, base, maxDist);
218 for (
int i = 0; i < indexes.count(); i++) {
220 QPointF diff =
m_d->transformedPoints[indexes[i]] - base;
222 qreal lambda = exp(-0.5 *
pow2(dist / sigma));
225 QPointF oldPosition =
m_d->transformedPoints[indexes[i]];
226 m_d->transformedPoints[indexes[i]] =
m_d->originalPoints[indexes[i]] * lambda +
m_d->transformedPoints[indexes[i]] * (1.0 - lambda);
228 m_d->transformedPointsContainer.movePoint(indexes[i], oldPosition,
m_d->transformedPoints[indexes[i]]);
241struct RestoreShapeSample
248Eigen::Vector2d toEigenPoint(
const QPointF &pt)
250 return Eigen::Vector2d(pt.x(), pt.y());
253QPointF fromEigenPoint(
const Eigen::Vector2d &pt)
255 return QPointF(pt.x(), pt.y());
258bool isFinite(
const Eigen::Matrix2d &m)
260 return std::isfinite(m(0, 0)) &&
261 std::isfinite(m(0, 1)) &&
262 std::isfinite(m(1, 0)) &&
263 std::isfinite(m(1, 1));
275 bool preserveRotation,
277 bool preserveStretch)
279 const qreal maxDistCoeff = 3.0;
280 const qreal maxDist = maxDistCoeff * sigma;
283 m_d->transformedPoints.size());
285 QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
286 2 * maxDist, 2 * maxDist);
290 m_d->transformedPointsContainer.findAllInRange(indexes, base, maxDist);
293 samples.reserve(indexes.count());
295 qreal weightSum = 0.0;
296 Eigen::Vector2d originalCentroid(0.0, 0.0);
297 Eigen::Vector2d transformedCentroid(0.0, 0.0);
299 for (
int i = 0; i < indexes.count(); i++) {
300 const int index = indexes[i];
302 const QPointF diff =
m_d->transformedPoints[index] - base;
304 if (dist > maxDist)
continue;
306 const qreal weight = exp(-0.5 *
pow2(dist / sigma));
307 const qreal lambda = qBound<qreal>(0.0, weight * amount, 1.0);
308 if (lambda <= 0.0)
continue;
310 samples << RestoreShapeSample{index, weight, lambda};
312 originalCentroid += weight * toEigenPoint(
m_d->originalPoints[index]);
313 transformedCentroid += weight * toEigenPoint(
m_d->transformedPoints[index]);
317 if (samples.count() < 3 || weightSum <= 1e-12)
return;
319 originalCentroid /= weightSum;
320 transformedCentroid /= weightSum;
322 Eigen::Matrix2d originalMoment = Eigen::Matrix2d::Zero();
323 Eigen::Matrix2d transformedOriginalMoment = Eigen::Matrix2d::Zero();
325 for (
int i = 0; i < samples.count(); i++) {
326 const RestoreShapeSample &sample = samples[i];
328 const Eigen::Vector2d originalOffset =
329 toEigenPoint(
m_d->originalPoints[sample.index]) - originalCentroid;
330 const Eigen::Vector2d transformedOffset =
331 toEigenPoint(
m_d->transformedPoints[sample.index]) - transformedCentroid;
333 originalMoment += sample.weight * originalOffset * originalOffset.transpose();
334 transformedOriginalMoment += sample.weight * transformedOffset * originalOffset.transpose();
337 if (qAbs(originalMoment.determinant()) < 1e-12)
return;
339 const Eigen::Matrix2d affine =
340 transformedOriginalMoment * originalMoment.inverse();
342 if (!isFinite(affine))
return;
344 Eigen::JacobiSVD<Eigen::Matrix2d> svd(affine, Eigen::ComputeFullU | Eigen::ComputeFullV);
345 Eigen::Matrix2d rotation = svd.matrixU() * svd.matrixV().transpose();
347 if (rotation.determinant() < 0.0) {
348 Eigen::Matrix2d
u = svd.matrixU();
350 rotation =
u * svd.matrixV().transpose();
353 if (!isFinite(rotation))
return;
355 const Eigen::Vector2d singularValues = svd.singularValues();
356 const qreal uniformScale =
357 preserveScale ? qMax<qreal>(1e-6, 0.5 * (singularValues.x() + singularValues.y())) : 1.0;
359 Eigen::Matrix2d targetLinear = Eigen::Matrix2d::Identity();
360 if (preserveRotation) {
361 targetLinear = rotation;
363 targetLinear *= uniformScale;
365 if (preserveStretch) {
366 const Eigen::Matrix2d stretchInRotationSpace = rotation.transpose() * affine;
367 qreal stretchX = qMax<qreal>(1e-6, qAbs(stretchInRotationSpace(0, 0)));
368 qreal stretchY = qMax<qreal>(1e-6, qAbs(stretchInRotationSpace(1, 1)));
369 const qreal stretchNormalization =
370 preserveScale ? uniformScale : qMax<qreal>(1e-6, std::sqrt(stretchX * stretchY));
372 Eigen::Matrix2d stretch = Eigen::Matrix2d::Identity();
373 stretch(0, 0) = stretchX / stretchNormalization;
374 stretch(1, 1) = stretchY / stretchNormalization;
375 targetLinear *= stretch;
379 updates.reserve(samples.count());
381 Eigen::Vector2d updatedCentroid(0.0, 0.0);
382 qreal updatedWeightSum = 0.0;
384 for (
int i = 0; i < samples.count(); i++) {
385 const RestoreShapeSample &sample = samples[i];
386 const Eigen::Vector2d oldPosition = toEigenPoint(
m_d->transformedPoints[sample.index]);
387 const Eigen::Vector2d originalOffset =
388 toEigenPoint(
m_d->originalPoints[sample.index]) - originalCentroid;
389 const Eigen::Vector2d targetPosition =
390 transformedCentroid + targetLinear * originalOffset;
391 const Eigen::Vector2d newPosition =
392 oldPosition * (1.0 - sample.lambda) + targetPosition * sample.lambda;
394 updates << PointUpdate{sample.index,
395 m_d->transformedPoints[sample.index],
396 fromEigenPoint(newPosition)};
398 updatedCentroid += sample.weight * newPosition;
399 updatedWeightSum += sample.weight;
402 if (updatedWeightSum <= 1e-12)
return;
403 updatedCentroid /= updatedWeightSum;
405 const QPointF centroidCorrection = fromEigenPoint(transformedCentroid - updatedCentroid);
407 for (
int i = 0; i < updates.count(); i++) {
408 PointUpdate &update = updates[i];
409 update.newPosition += centroidCorrection;
410 m_d->transformedPoints[update.index] = update.newPosition;
411 m_d->transformedPointsContainer.movePoint(update.index,
417template <
class ProcessOp>
418void KisLiquifyTransformWorker::Private::
419processTransformedPixelsBuildUp(ProcessOp op,
423 const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
424 QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
425 2 * maxDist, 2 * maxDist);
427 accumulatedBrushStrokes |=
kisGrowRect(clipRect, pixelPrecision);
430 transformedPointsContainer.findAllInRange(indexes, base, maxDist);
432 for (
int i = 0; i < indexes.count(); i++) {
434 QPointF diff = transformedPoints[indexes[i]] - base;
436 if (dist > maxDist)
continue;
438 const qreal lambda = exp(-0.5 *
pow2(dist / sigma));
439 QPointF oldPosition = transformedPoints[indexes[i]];
440 transformedPoints[indexes[i]] = op(transformedPoints[indexes[i]], base, diff, lambda);
443 transformedPointsContainer.movePoint(indexes[i], oldPosition, transformedPoints[indexes[i]]);
448template <
class ProcessOp>
449void KisLiquifyTransformWorker::Private::
450processTransformedPixelsWash(ProcessOp op,
455 const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
456 QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
457 2 * maxDist, 2 * maxDist);
459 accumulatedBrushStrokes |=
kisGrowRect(clipRect, pixelPrecision);
462 transformedPoints.size());
468 originalPointsContainer.findAllInRange(indexes, base, maxDist);
469 for (
int i = 0; i < indexes.count(); i++) {
471 QPointF diff = originalPoints[indexes[i]] - base;
474 const qreal lambda = exp(-0.5 *
pow2(dist / sigma));
475 QPointF dstPt = op(originalPoints[indexes[i]], base, diff, lambda);
477 if (
kisDistance(dstPt, originalPoints[indexes[i]]) >
kisDistance(transformedPoints[indexes[i]], originalPoints[indexes[i]])) {
478 QPointF oldPosition = transformedPoints[indexes[i]];
479 transformedPoints[indexes[i]] = (1.0 - flow) * transformedPoints[indexes[i]] + flow * dstPt;
481 transformedPointsContainer.movePoint(indexes[i], oldPosition, transformedPoints[indexes[i]]);
486template <
class ProcessOp>
487void KisLiquifyTransformWorker::Private::
488processTransformedPixels(ProcessOp op,
495 processTransformedPixelsWash(op, base, sigma, flow);
497 processTransformedPixelsBuildUp(op, base, sigma);
533 return base + (1.0 +
m_scale * lambda) * diff;
554 const qreal angle =
m_angle * lambda;
555 const qreal sinA = std::sin(angle);
556 const qreal cosA = std::cos(angle);
558 qreal x = cosA * diff.x() + sinA * diff.y();
559 qreal y = -sinA * diff.x() + cosA * diff.y();
561 return base + QPointF(x, y);
572 const QPointF &offset,
578 m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
588 m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
598 m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
608 QRect correctSubGrid = calculateCorrectSubGrid(
m_d->srcBounds,
m_d->pixelPrecision,
m_d->accumulatedBrushStrokes,
m_d->gridSize);
616#ifdef DEBUG_PAINTING_POLYGONS
617 polygonOp.setDebugColor(Qt::red);
620 iterateThroughGrid<AlwaysCompletePolygonPolicy>(polygonOp, indexesOp,
623 m_d->transformedPoints,
625 QList<QRectF> areasToCopy = cutOutSubgridFromBounds(correctSubGrid,
m_d->srcBounds,
m_d->gridSize,
m_d->originalPoints);
626#ifdef DEBUG_PAINTING_POLYGONS
627 QList<QColor> colors = {Qt::blue, Qt::green, Qt::yellow, Qt::black};
629 for (
int i = 0; i < areasToCopy.length(); i++) {
630#ifdef DEBUG_PAINTING_POLYGONS
631 polygonOp.setDebugColor(colors[i]);
639 const qreal margin = 0.05;
640 QRect resultRect =
m_d->transformedPointsContainer.exactBounds().toRect();
652 return m_d->accumulatedBrushStrokes;
659 m_d->srcBounds = t.mapRect(
m_d->srcBounds);
662 for (
int i = 0; i <
m_d->transformedPoints.count(); i++) {
663 m_d->originalPointsContainer.movePoint(i,
m_d->originalPoints[i], t.map(
m_d->originalPoints[i]));
664 m_d->transformedPointsContainer.movePoint(i,
m_d->transformedPoints[i], t.map(
m_d->transformedPoints[i]));
666 m_d->originalPoints[i] = t.map(
m_d->originalPoints[i]);
667 m_d->transformedPoints[i] = t.map(
m_d->transformedPoints[i]);
669 m_d->accumulatedBrushStrokes = t.map(
m_d->accumulatedBrushStrokes).boundingRect();
670 if (t == QTransform::fromScale(t.m11(), t.m22()) && t.m11() == t.m22()) {
671 m_d->pixelPrecision *= t.m11();
685 using namespace std::placeholders;
687 typedef QPointF (QTransform::*MapFuncType)(
const QPointF&)
const;
688 return std::bind(
static_cast<MapFuncType
>(&QTransform::map), &transform, _1);
692 const QPointF &srcImageOffset,
693 const QTransform &imageToThumbTransform,
713 std::transform(originalPointsLocal.begin(), originalPointsLocal.end(),
714 originalPointsLocal.begin(), mapFunc);
716 std::transform(transformedPointsLocal.begin(), transformedPointsLocal.end(),
717 transformedPointsLocal.begin(), mapFunc);
720 Q_FOREACH (
const QPointF &pt, transformedPointsLocal) {
724 const QRectF
srcBounds(srcImageOffset, srcImage.size());
727 QPointF dstQImageOffset = dstBounds.topLeft();
728 *newOffset = dstQImageOffset;
730 QRect dstBoundsI = dstBounds.toAlignedRect();
732 QImage dstImage(dstBoundsI.size(), srcImage.format());
744 GridIterationTools::iterateThroughGrid<GridIterationTools::AlwaysCompletePolygonPolicy>(polygonOp, indexesOp,
747 transformedPointsLocal,
753 const qreal
eps = 0.001;
754 for (
int i = 0; i < areasToCopy.length(); i++) {
755 QPolygonF transformed = imageToThumbTransform.map(QPolygonF(areasToCopy[i]));
757 polygonOp.
fastCopyArea(transformed.boundingRect().toRect());
759 polygonOp.operator()(transformed, transformed);
768 QDomDocument doc = e->ownerDocument();
769 QDomElement liqEl = doc.createElement(
"liquify_points");
770 e->appendChild(liqEl);
781 QDomElement liquifyEl;
802 warnKrita <<
"WARNING: Failed to load liquify worker from XML";
812 numPoints != worker->
m_d->originalPoints.size() ||
814 warnKrita <<
"WARNING: Inconsistent number of points!";
825 QRectF changedRect = QRectF();
827 for (
int i = 0; i < numPoints; i++) {
837 worker->
m_d->transformedPointsContainer.initializeWith(worker->
m_d->transformedPoints);
838 worker->
m_d->originalPointsContainer.initializeWith(worker->
m_d->originalPoints);
840 worker->
m_d->accumulatedBrushStrokes = changedRect;
const KoColorSpace * colorSpace() const
#define KIS_ASSERT_RECOVER(cond)
#define KIS_SAFE_ASSERT_RECOVER(cond)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
#define KIS_ASSERT_RECOVER_RETURN(cond)
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
T kisGrowRect(const T &rect, U offset)
Rect blowRect(const Rect &rect, qreal coeff)
bool isPolygonPixelAlignedRect(const Polygon &poly, Difference tolerance)
void accumulateBounds(const Point &pt, Rect *bounds)
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)
QVector< QPointF > m_points
AllPointsFetcherOp(QRectF srcRect)
void processPoint(int col, int row, int prevCol, int prevRow, int colIndex, int rowIndex)
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