File indexing completed on 2024-05-12 15:58:26

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_liquify_transform_worker.h"
0008 
0009 #include <KoColorSpace.h>
0010 #include "kis_grid_interpolation_tools.h"
0011 #include "kis_dom_utils.h"
0012 #include "krita_utils.h"
0013 
0014 
0015 struct Q_DECL_HIDDEN KisLiquifyTransformWorker::Private
0016 {
0017     Private(const QRect &_srcBounds,
0018             KoUpdater *_progress,
0019             int _pixelPrecision)
0020         : srcBounds(_srcBounds),
0021           progress(_progress),
0022           pixelPrecision(_pixelPrecision)
0023     {
0024     }
0025 
0026     QRect srcBounds;
0027 
0028     QVector<QPointF> originalPoints;
0029     QVector<QPointF> transformedPoints;
0030 
0031     KoUpdater *progress;
0032     int pixelPrecision;
0033     QSize gridSize;
0034 
0035     void preparePoints();
0036 
0037     struct MapIndexesOp;
0038 
0039     template <class ProcessOp>
0040     void processTransformedPixelsBuildUp(ProcessOp op,
0041                                          const QPointF &base,
0042                                          qreal sigma);
0043 
0044     template <class ProcessOp>
0045     void processTransformedPixelsWash(ProcessOp op,
0046                                       const QPointF &base,
0047                                       qreal sigma,
0048                                       qreal flow);
0049 
0050     template <class ProcessOp>
0051     void processTransformedPixels(ProcessOp op,
0052                                   const QPointF &base,
0053                                   qreal sigma,
0054                                   bool useWashMode,
0055                                   qreal flow);
0056 };
0057 
0058 KisLiquifyTransformWorker::KisLiquifyTransformWorker(const QRect &srcBounds,
0059                                                      KoUpdater *progress,
0060                                                      int pixelPrecision)
0061     : m_d(new Private(srcBounds, progress, pixelPrecision))
0062 {
0063     KIS_ASSERT_RECOVER_RETURN(!srcBounds.isEmpty());
0064 
0065     // TODO: implement 'progress' stuff
0066     m_d->preparePoints();
0067 }
0068 
0069 KisLiquifyTransformWorker::KisLiquifyTransformWorker(const KisLiquifyTransformWorker &rhs)
0070     : m_d(new Private(*rhs.m_d.data()))
0071 {
0072 }
0073 
0074 KisLiquifyTransformWorker::~KisLiquifyTransformWorker()
0075 {
0076 }
0077 
0078 bool KisLiquifyTransformWorker::operator==(const KisLiquifyTransformWorker &other) const
0079 {
0080     bool result =
0081             m_d->srcBounds == other.m_d->srcBounds &&
0082             m_d->pixelPrecision == other.m_d->pixelPrecision &&
0083             m_d->gridSize == other.m_d->gridSize &&
0084             m_d->originalPoints.size() == other.m_d->originalPoints.size() &&
0085             m_d->transformedPoints.size() == other.m_d->transformedPoints.size();
0086 
0087     if (!result) return false;
0088 
0089     const qreal eps = 1e-6;
0090 
0091     result =
0092         KisAlgebra2D::fuzzyPointCompare(m_d->originalPoints, other.m_d->originalPoints, eps) &&
0093         KisAlgebra2D::fuzzyPointCompare(m_d->transformedPoints, other.m_d->transformedPoints, eps);
0094 
0095     return result;
0096 }
0097 
0098 bool KisLiquifyTransformWorker::isIdentity() const
0099 {
0100     const qreal eps = 1e-6;
0101     return KisAlgebra2D::fuzzyPointCompare(m_d->originalPoints, m_d->transformedPoints, eps);
0102 }
0103 
0104 int KisLiquifyTransformWorker::pointToIndex(const QPoint &cellPt)
0105 {
0106     return GridIterationTools::pointToIndex(cellPt, m_d->gridSize);
0107 }
0108 
0109 QSize KisLiquifyTransformWorker::gridSize() const
0110 {
0111     return m_d->gridSize;
0112 }
0113 
0114 const QVector<QPointF>& KisLiquifyTransformWorker::originalPoints() const
0115 {
0116     return m_d->originalPoints;
0117 }
0118 
0119 QVector<QPointF>& KisLiquifyTransformWorker::transformedPoints()
0120 {
0121     return m_d->transformedPoints;
0122 }
0123 
0124 struct AllPointsFetcherOp
0125 {
0126     AllPointsFetcherOp(QRectF srcRect) : m_srcRect(srcRect) {}
0127 
0128     inline void processPoint(int col, int row,
0129                              int prevCol, int prevRow,
0130                              int colIndex, int rowIndex) {
0131 
0132         Q_UNUSED(prevCol);
0133         Q_UNUSED(prevRow);
0134         Q_UNUSED(colIndex);
0135         Q_UNUSED(rowIndex);
0136 
0137         QPointF pt(col, row);
0138         m_points << pt;
0139     }
0140 
0141     inline void nextLine() {
0142     }
0143 
0144     QVector<QPointF> m_points;
0145     QRectF m_srcRect;
0146 };
0147 
0148 void KisLiquifyTransformWorker::Private::preparePoints()
0149 {
0150     gridSize =
0151         GridIterationTools::calcGridSize(srcBounds, pixelPrecision);
0152 
0153     AllPointsFetcherOp pointsOp(srcBounds);
0154     GridIterationTools::processGrid(pointsOp, srcBounds, pixelPrecision);
0155 
0156     const int numPoints = pointsOp.m_points.size();
0157 
0158     KIS_ASSERT_RECOVER_RETURN(numPoints == gridSize.width() * gridSize.height());
0159 
0160     originalPoints = pointsOp.m_points;
0161     transformedPoints = pointsOp.m_points;
0162 }
0163 
0164 void KisLiquifyTransformWorker::translate(const QPointF &offset)
0165 {
0166     QVector<QPointF>::iterator it = m_d->transformedPoints.begin();
0167     QVector<QPointF>::iterator end = m_d->transformedPoints.end();
0168 
0169     QVector<QPointF>::iterator refIt = m_d->originalPoints.begin();
0170     KIS_ASSERT_RECOVER_RETURN(m_d->originalPoints.size() ==
0171                               m_d->transformedPoints.size());
0172 
0173     for (; it != end; ++it, ++refIt) {
0174         *it += offset;
0175         *refIt += offset;
0176     }
0177 }
0178 
0179 void KisLiquifyTransformWorker::translateDstSpace(const QPointF &offset)
0180 {
0181     QVector<QPointF>::iterator it = m_d->transformedPoints.begin();
0182     QVector<QPointF>::iterator end = m_d->transformedPoints.end();
0183 
0184     for (; it != end; ++it) {
0185         *it += offset;
0186     }
0187 }
0188 
0189 void KisLiquifyTransformWorker::undoPoints(const QPointF &base,
0190                                            qreal amount,
0191                                            qreal sigma)
0192 {
0193     const qreal maxDistCoeff = 3.0;
0194     const qreal maxDist = maxDistCoeff * sigma;
0195     QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
0196                     2 * maxDist, 2 * maxDist);
0197 
0198     QVector<QPointF>::iterator it = m_d->transformedPoints.begin();
0199     QVector<QPointF>::iterator end = m_d->transformedPoints.end();
0200 
0201     QVector<QPointF>::iterator refIt = m_d->originalPoints.begin();
0202     KIS_ASSERT_RECOVER_RETURN(m_d->originalPoints.size() ==
0203                               m_d->transformedPoints.size());
0204 
0205     for (; it != end; ++it, ++refIt) {
0206         if (!clipRect.contains(*it)) continue;
0207 
0208         QPointF diff = *it - base;
0209         qreal dist = KisAlgebra2D::norm(diff);
0210         if (dist > maxDist) continue;
0211 
0212         qreal lambda = exp(-0.5 * pow2(dist / sigma));
0213         lambda *= amount;
0214         *it = *refIt * lambda + *it * (1.0 - lambda);
0215     }
0216 }
0217 
0218 template <class ProcessOp>
0219 void KisLiquifyTransformWorker::Private::
0220 processTransformedPixelsBuildUp(ProcessOp op,
0221                                 const QPointF &base,
0222                                 qreal sigma)
0223 {
0224     const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
0225     QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
0226                     2 * maxDist, 2 * maxDist);
0227 
0228     QVector<QPointF>::iterator it = transformedPoints.begin();
0229     QVector<QPointF>::iterator end = transformedPoints.end();
0230 
0231     for (; it != end; ++it) {
0232         if (!clipRect.contains(*it)) continue;
0233 
0234         QPointF diff = *it - base;
0235         qreal dist = KisAlgebra2D::norm(diff);
0236         if (dist > maxDist) continue;
0237 
0238         const qreal lambda = exp(-0.5 * pow2(dist / sigma));
0239         *it = op(*it, base, diff, lambda);
0240     }
0241 }
0242 
0243 template <class ProcessOp>
0244 void KisLiquifyTransformWorker::Private::
0245 processTransformedPixelsWash(ProcessOp op,
0246                              const QPointF &base,
0247                              qreal sigma,
0248                              qreal flow)
0249 {
0250     const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
0251     QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
0252                     2 * maxDist, 2 * maxDist);
0253 
0254     QVector<QPointF>::iterator it = transformedPoints.begin();
0255     QVector<QPointF>::iterator end = transformedPoints.end();
0256 
0257     QVector<QPointF>::iterator refIt = originalPoints.begin();
0258     KIS_ASSERT_RECOVER_RETURN(originalPoints.size() ==
0259                               transformedPoints.size());
0260 
0261     for (; it != end; ++it, ++refIt) {
0262         if (!clipRect.contains(*it)) continue;
0263 
0264         QPointF diff = *refIt - base;
0265         qreal dist = KisAlgebra2D::norm(diff);
0266         if (dist > maxDist) continue;
0267 
0268         const qreal lambda = exp(-0.5 * pow2(dist / sigma));
0269         QPointF dstPt = op(*refIt, base, diff, lambda);
0270 
0271         if (kisDistance(dstPt, *refIt) > kisDistance(*it, *refIt)) {
0272             *it = (1.0 - flow) * (*it) + flow * dstPt;
0273         }
0274     }
0275 }
0276 
0277 template <class ProcessOp>
0278 void KisLiquifyTransformWorker::Private::
0279 processTransformedPixels(ProcessOp op,
0280                          const QPointF &base,
0281                          qreal sigma,
0282                          bool useWashMode,
0283                          qreal flow)
0284 {
0285     if (useWashMode) {
0286         processTransformedPixelsWash(op, base, sigma, flow);
0287     } else {
0288         processTransformedPixelsBuildUp(op, base, sigma);
0289     }
0290 }
0291 
0292 struct TranslateOp
0293 {
0294     TranslateOp(const QPointF &offset) : m_offset(offset) {}
0295 
0296     QPointF operator() (const QPointF &pt,
0297                         const QPointF &base,
0298                         const QPointF &diff,
0299                         qreal lambda)
0300     {
0301         Q_UNUSED(base);
0302         Q_UNUSED(diff);
0303         return pt + lambda * m_offset;
0304     }
0305 
0306     static const qreal maxDistCoeff;
0307 
0308     QPointF m_offset;
0309 };
0310 
0311 const qreal TranslateOp::maxDistCoeff = 3.0;
0312 
0313 struct ScaleOp
0314 {
0315     ScaleOp(qreal scale) : m_scale(scale) {}
0316 
0317     QPointF operator() (const QPointF &pt,
0318                         const QPointF &base,
0319                         const QPointF &diff,
0320                         qreal lambda)
0321     {
0322         Q_UNUSED(pt);
0323         Q_UNUSED(diff);
0324         return base + (1.0 + m_scale * lambda) * diff;
0325     }
0326 
0327     static const qreal maxDistCoeff;
0328 
0329     qreal m_scale;
0330 };
0331 
0332 const qreal ScaleOp::maxDistCoeff = 3.0;
0333 
0334 struct RotateOp
0335 {
0336     RotateOp(qreal angle) : m_angle(angle) {}
0337 
0338     QPointF operator() (const QPointF &pt,
0339                         const QPointF &base,
0340                         const QPointF &diff,
0341                         qreal lambda)
0342     {
0343         Q_UNUSED(pt);
0344 
0345         const qreal angle = m_angle * lambda;
0346         const qreal sinA = std::sin(angle);
0347         const qreal cosA = std::cos(angle);
0348 
0349         qreal x =  cosA * diff.x() + sinA * diff.y();
0350         qreal y = -sinA * diff.x() + cosA * diff.y();
0351 
0352         return base + QPointF(x, y);
0353     }
0354 
0355     static const qreal maxDistCoeff;
0356 
0357     qreal m_angle;
0358 };
0359 
0360 const qreal RotateOp::maxDistCoeff = 3.0;
0361 
0362 void KisLiquifyTransformWorker::translatePoints(const QPointF &base,
0363                                                 const QPointF &offset,
0364                                                 qreal sigma,
0365                                                 bool useWashMode,
0366                                                 qreal flow)
0367 {
0368     TranslateOp op(offset);
0369     m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
0370 }
0371 
0372 void KisLiquifyTransformWorker::scalePoints(const QPointF &base,
0373                                             qreal scale,
0374                                             qreal sigma,
0375                                             bool useWashMode,
0376                                             qreal flow)
0377 {
0378     ScaleOp op(scale);
0379     m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
0380 }
0381 
0382 void KisLiquifyTransformWorker::rotatePoints(const QPointF &base,
0383                                              qreal angle,
0384                                              qreal sigma,
0385                                              bool useWashMode,
0386                                              qreal flow)
0387 {
0388     RotateOp op(angle);
0389     m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
0390 }
0391 
0392 void KisLiquifyTransformWorker::run(KisPaintDeviceSP srcDevice, KisPaintDeviceSP dstDevice)
0393 {
0394     KIS_SAFE_ASSERT_RECOVER_RETURN(*srcDevice->colorSpace() == *dstDevice->colorSpace());
0395 
0396     dstDevice->clear();
0397 
0398     using namespace GridIterationTools;
0399 
0400     PaintDevicePolygonOp polygonOp(srcDevice, dstDevice);
0401     RegularGridIndexesOp indexesOp(m_d->gridSize);
0402     iterateThroughGrid<AlwaysCompletePolygonPolicy>(polygonOp, indexesOp,
0403                                                     m_d->gridSize,
0404                                                     m_d->originalPoints,
0405                                                     m_d->transformedPoints);
0406 }
0407 
0408 QRect KisLiquifyTransformWorker::approxChangeRect(const QRect &rc)
0409 {
0410     const qreal margin = 0.05;
0411 
0412     /**
0413      * Here we just return the full area occupied by the transformed grid.
0414      * We sample grid points for not doing too much work.
0415      */
0416     const int maxSamplePoints = 200;
0417     const int minStep = 3;
0418     const int step = qMax(minStep, m_d->transformedPoints.size() / maxSamplePoints);
0419     Q_UNUSED(step);
0420 
0421     QVector<QPoint> samplePoints;
0422     for (auto it = m_d->transformedPoints.constBegin(); it != m_d->transformedPoints.constEnd(); ++it) {
0423         samplePoints << it->toPoint();
0424     }
0425 
0426     QRect resultRect = KisAlgebra2D::approximateRectFromPoints(samplePoints);
0427     return KisAlgebra2D::blowRect(resultRect | rc, margin);
0428 }
0429 
0430 QRect KisLiquifyTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds)
0431 {
0432     Q_UNUSED(rc);
0433     return fullBounds;
0434 }
0435 
0436 void KisLiquifyTransformWorker::transformSrcAndDst(const QTransform &t)
0437 {
0438     KIS_SAFE_ASSERT_RECOVER_RETURN(t.type() <= QTransform::TxScale);
0439 
0440     m_d->srcBounds = t.mapRect(m_d->srcBounds);
0441 
0442     for (auto it = m_d->originalPoints.begin(); it != m_d->originalPoints.end(); ++it) {
0443         *it = t.map(*it);
0444     }
0445     for (auto it = m_d->transformedPoints.begin(); it != m_d->transformedPoints.end(); ++it) {
0446         *it = t.map(*it);
0447     }
0448 }
0449 
0450 #include <functional>
0451 #include <QTransform>
0452 
0453 using PointMapFunction = std::function<QPointF (const QPointF&)>;
0454 
0455 
0456 PointMapFunction bindPointMapTransform(const QTransform &transform) {
0457     using namespace std::placeholders;
0458 
0459     typedef QPointF (QTransform::*MapFuncType)(const QPointF&) const;
0460     return std::bind(static_cast<MapFuncType>(&QTransform::map), &transform, _1);
0461 }
0462 
0463 QImage KisLiquifyTransformWorker::runOnQImage(const QImage &srcImage,
0464                                               const QPointF &srcImageOffset,
0465                                               const QTransform &imageToThumbTransform,
0466                                               QPointF *newOffset)
0467 {
0468     KIS_ASSERT_RECOVER(m_d->originalPoints.size() == m_d->transformedPoints.size()) {
0469         return QImage();
0470     }
0471 
0472     KIS_ASSERT_RECOVER(!srcImage.isNull()) {
0473         return QImage();
0474     }
0475 
0476     KIS_ASSERT_RECOVER(srcImage.format() == QImage::Format_ARGB32) {
0477         return QImage();
0478     }
0479 
0480     QVector<QPointF> originalPointsLocal(m_d->originalPoints);
0481     QVector<QPointF> transformedPointsLocal(m_d->transformedPoints);
0482 
0483     PointMapFunction mapFunc = bindPointMapTransform(imageToThumbTransform);
0484 
0485     std::transform(originalPointsLocal.begin(), originalPointsLocal.end(),
0486                    originalPointsLocal.begin(), mapFunc);
0487 
0488     std::transform(transformedPointsLocal.begin(), transformedPointsLocal.end(),
0489                    transformedPointsLocal.begin(), mapFunc);
0490 
0491     QRectF dstBounds;
0492     Q_FOREACH (const QPointF &pt, transformedPointsLocal) {
0493         KisAlgebra2D::accumulateBounds(pt, &dstBounds);
0494     }
0495 
0496     const QRectF srcBounds(srcImageOffset, srcImage.size());
0497     dstBounds |= srcBounds;
0498 
0499     QPointF dstQImageOffset = dstBounds.topLeft();
0500     *newOffset = dstQImageOffset;
0501 
0502     QRect dstBoundsI = dstBounds.toAlignedRect();
0503 
0504     QImage dstImage(dstBoundsI.size(), srcImage.format());
0505     dstImage.fill(0);
0506 
0507     GridIterationTools::QImagePolygonOp polygonOp(srcImage, dstImage, srcImageOffset, dstQImageOffset);
0508     GridIterationTools::RegularGridIndexesOp indexesOp(m_d->gridSize);
0509     GridIterationTools::iterateThroughGrid
0510         <GridIterationTools::AlwaysCompletePolygonPolicy>(polygonOp, indexesOp,
0511                                                           m_d->gridSize,
0512                                                           originalPointsLocal,
0513                                                           transformedPointsLocal);
0514     return dstImage;
0515 }
0516 
0517 void KisLiquifyTransformWorker::toXML(QDomElement *e) const
0518 {
0519     QDomDocument doc = e->ownerDocument();
0520     QDomElement liqEl = doc.createElement("liquify_points");
0521     e->appendChild(liqEl);
0522 
0523     KisDomUtils::saveValue(&liqEl, "srcBounds", m_d->srcBounds);
0524     KisDomUtils::saveValue(&liqEl, "originalPoints", m_d->originalPoints);
0525     KisDomUtils::saveValue(&liqEl, "transformedPoints", m_d->transformedPoints);
0526     KisDomUtils::saveValue(&liqEl, "pixelPrecision", m_d->pixelPrecision);
0527     KisDomUtils::saveValue(&liqEl, "gridSize", m_d->gridSize);
0528 }
0529 
0530 KisLiquifyTransformWorker* KisLiquifyTransformWorker::fromXML(const QDomElement &e)
0531 {
0532     QDomElement liquifyEl;
0533 
0534     QRect srcBounds;
0535     QVector<QPointF> originalPoints;
0536     QVector<QPointF> transformedPoints;
0537     int pixelPrecision;
0538     QSize gridSize;
0539 
0540     bool result = false;
0541 
0542 
0543     result =
0544         KisDomUtils::findOnlyElement(e, "liquify_points", &liquifyEl) &&
0545 
0546         KisDomUtils::loadValue(liquifyEl, "srcBounds", &srcBounds) &&
0547         KisDomUtils::loadValue(liquifyEl, "originalPoints", &originalPoints) &&
0548         KisDomUtils::loadValue(liquifyEl, "transformedPoints", &transformedPoints) &&
0549         KisDomUtils::loadValue(liquifyEl, "pixelPrecision", &pixelPrecision) &&
0550         KisDomUtils::loadValue(liquifyEl, "gridSize", &gridSize);
0551 
0552     if (!result) {
0553         warnKrita << "WARNING: Failed to load liquify worker from XML";
0554         return new KisLiquifyTransformWorker(QRect(0,0,1024, 1024), 0, 8);
0555     }
0556 
0557     KisLiquifyTransformWorker *worker =
0558         new KisLiquifyTransformWorker(srcBounds, 0, pixelPrecision);
0559 
0560     const int numPoints = originalPoints.size();
0561 
0562     if (numPoints != transformedPoints.size() ||
0563         numPoints != worker->m_d->originalPoints.size() ||
0564         gridSize != worker->m_d->gridSize) {
0565         warnKrita << "WARNING: Inconsistent number of points!";
0566         warnKrita << ppVar(originalPoints.size());
0567         warnKrita << ppVar(transformedPoints.size());
0568         warnKrita << ppVar(gridSize);
0569         warnKrita << ppVar(worker->m_d->originalPoints.size());
0570         warnKrita << ppVar(worker->m_d->transformedPoints.size());
0571         warnKrita << ppVar(worker->m_d->gridSize);
0572 
0573         return worker;
0574     }
0575 
0576     for (int i = 0; i < numPoints; i++) {
0577         worker->m_d->originalPoints[i] = originalPoints[i];
0578         worker->m_d->transformedPoints[i] = transformedPoints[i];
0579     }
0580 
0581 
0582     return worker;
0583 }