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

0001 /*
0002  *  kis_warptransform_worker.cc -- part of Krita
0003  *
0004  *  SPDX-FileCopyrightText: 2010 Marc Pegon <pe.marc@free.fr>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kis_warptransform_worker.h"
0010 #include "kis_random_sub_accessor.h"
0011 #include "kis_iterator_ng.h"
0012 #include "kis_datamanager.h"
0013 
0014 #include <QTransform>
0015 #include <QVector2D>
0016 #include <QPainter>
0017 #include <QVarLengthArray>
0018 
0019 #include <KoColorSpace.h>
0020 #include <KoColor.h>
0021 
0022 #include <math.h>
0023 
0024 #include "kis_grid_interpolation_tools.h"
0025 
0026 QPointF KisWarpTransformWorker::affineTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
0027 {
0028     int nbPoints = p.size();
0029     QVarLengthArray<qreal> w(nbPoints);
0030     qreal sumWi = 0;
0031     QPointF pStar(0, 0), qStar(0, 0);
0032     QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints);
0033 
0034     for (int i = 0; i < nbPoints; ++i) {
0035         if (v == p[i])
0036             return q[i];
0037 
0038         QVector2D tmp(p[i] - v);
0039         w[i] = 1. / pow(tmp.lengthSquared(), alpha);
0040         pStar += w[i] * p[i];
0041         qStar += w[i] * q[i];
0042         sumWi += w[i];
0043     }
0044     pStar /= sumWi;
0045     qStar /= sumWi;
0046 
0047     qreal A_tmp[4] = {0, 0, 0, 0};
0048     for (int i = 0; i < nbPoints; ++i) {
0049         pHat[i] = p[i] - pStar;
0050         qHat[i] = q[i] - qStar;
0051 
0052         A_tmp[0] += w[i] * pow(pHat[i].x(), 2);
0053         A_tmp[3] += w[i] * pow(pHat[i].y(), 2);
0054         A_tmp[1] += w[i] * pHat[i].x() * pHat[i].y();
0055     }
0056     A_tmp[2] = A_tmp[1];
0057     qreal det_A_tmp = A_tmp[0] * A_tmp[3] - A_tmp[1] * A_tmp[2];
0058 
0059     qreal A_tmp_inv[4];
0060 
0061     if (det_A_tmp == 0)
0062         return v;
0063 
0064     A_tmp_inv[0] = A_tmp[3] / det_A_tmp;
0065     A_tmp_inv[1] = - A_tmp[1] / det_A_tmp;
0066     A_tmp_inv[2] = A_tmp_inv[1];
0067     A_tmp_inv[3] = A_tmp[0] / det_A_tmp;
0068 
0069     QPointF t = v - pStar;
0070     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]);
0071     qreal A_j;
0072 
0073     QPointF res = qStar;
0074     for (int j = 0; j < nbPoints; ++j) {
0075         A_j = A_precalc.x() * pHat[j].x() + A_precalc.y() * pHat[j].y();
0076 
0077         res += w[j] * A_j * qHat[j];
0078     }
0079 
0080     return res;
0081 }
0082 
0083 QPointF KisWarpTransformWorker::similitudeTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
0084 {
0085     int nbPoints = p.size();
0086     QVarLengthArray<qreal> w(nbPoints);
0087     qreal sumWi = 0;
0088     QPointF pStar(0, 0), qStar(0, 0);
0089     QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints);
0090 
0091     for (int i = 0; i < nbPoints; ++i) {
0092         if (v == p[i])
0093             return q[i];
0094 
0095         QVector2D tmp(p[i] - v);
0096         w[i] = 1. / pow(tmp.lengthSquared(), alpha);
0097         pStar += w[i] * p[i];
0098         qStar += w[i] * q[i];
0099         sumWi += w[i];
0100     }
0101     pStar /= sumWi;
0102     qStar /= sumWi;
0103 
0104     qreal mu_s = 0;
0105     QPointF res_tmp(0, 0);
0106     qreal qx, qy, px, py;
0107     for (int i = 0; i < nbPoints; ++i) {
0108         pHat[i] = p[i] - pStar;
0109         qHat[i] = q[i] - qStar;
0110 
0111         QVector2D tmp(pHat[i]);
0112         mu_s += w[i] * tmp.lengthSquared();
0113 
0114         qx = w[i] * qHat[i].x();
0115         qy = w[i] * qHat[i].y();
0116         px = pHat[i].x();
0117         py = pHat[i].y();
0118 
0119         res_tmp += QPointF(qx * px + qy * py, qx * py - qy * px);
0120     }
0121 
0122     res_tmp /= mu_s;
0123     QPointF v_m_pStar(v - pStar);
0124     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());
0125     res += qStar;
0126 
0127     return res;
0128 }
0129 
0130 QPointF KisWarpTransformWorker::rigidTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
0131 {
0132     int nbPoints = p.size();
0133     QVarLengthArray<qreal> w(nbPoints);
0134     qreal sumWi = 0;
0135     QPointF pStar(0, 0), qStar(0, 0);
0136     QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints);
0137 
0138     for (int i = 0; i < nbPoints; ++i) {
0139         if (v == p[i])
0140             return q[i];
0141 
0142         QVector2D tmp(p[i] - v);
0143         w[i] = 1. / pow(tmp.lengthSquared(), alpha);
0144         pStar += w[i] * p[i];
0145         qStar += w[i] * q[i];
0146         sumWi += w[i];
0147     }
0148     pStar /= sumWi;
0149     qStar /= sumWi;
0150 
0151     QVector2D res_tmp(0, 0);
0152     qreal qx, qy, px, py;
0153     for (int i = 0; i < nbPoints; ++i) {
0154         pHat[i] = p[i] - pStar;
0155         qHat[i] = q[i] - qStar;
0156 
0157         qx = w[i] * qHat[i].x();
0158         qy = w[i] * qHat[i].y();
0159         px = pHat[i].x();
0160         py = pHat[i].y();
0161 
0162         res_tmp += QVector2D(qx * px + qy * py, qx * py - qy * px);
0163     }
0164 
0165     QPointF f_arrow(res_tmp.normalized().toPointF());
0166     QVector2D v_m_pStar(v - pStar);
0167     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());
0168     res += qStar;
0169 
0170     return res;
0171 }
0172 
0173 KisWarpTransformWorker::KisWarpTransformWorker(WarpType warpType, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, KoUpdater *progress)
0174         : m_progress(progress)
0175 {
0176     m_origPoint = origPoint;
0177     m_transfPoint = transfPoint;
0178     m_alpha = alpha;
0179 
0180     switch(warpType) {
0181     case AFFINE_TRANSFORM:
0182         m_warpMathFunction = &affineTransformMath;
0183         break;
0184     case SIMILITUDE_TRANSFORM:
0185         m_warpMathFunction = &similitudeTransformMath;
0186         break;
0187     case RIGID_TRANSFORM:
0188         m_warpMathFunction = &rigidTransformMath;
0189         break;
0190     default:
0191         m_warpMathFunction = 0;
0192         break;
0193     }
0194 }
0195 
0196 KisWarpTransformWorker::~KisWarpTransformWorker()
0197 {
0198 }
0199 
0200 struct KisWarpTransformWorker::FunctionTransformOp
0201 {
0202     FunctionTransformOp(KisWarpTransformWorker::WarpMathFunction function,
0203                         const QVector<QPointF> &p,
0204                         const QVector<QPointF> &q,
0205                         qreal alpha)
0206         : m_function(function),
0207           m_p(p),
0208           m_q(q),
0209           m_alpha(alpha)
0210     {
0211     }
0212 
0213     QPointF operator() (const QPointF &pt) const {
0214         return m_function(pt, m_p, m_q, m_alpha);
0215     }
0216 
0217     KisWarpTransformWorker::WarpMathFunction m_function;
0218     const QVector<QPointF> &m_p;
0219     const QVector<QPointF> &m_q;
0220     qreal m_alpha;
0221 };
0222 
0223 void KisWarpTransformWorker::run(KisPaintDeviceSP srcDev, KisPaintDeviceSP dstDev)
0224 {
0225     KIS_SAFE_ASSERT_RECOVER_RETURN(*srcDev->colorSpace() == *dstDev->colorSpace());
0226 
0227     if (!m_warpMathFunction ||
0228         m_origPoint.isEmpty() ||
0229         m_origPoint.size() != m_transfPoint.size()) {
0230 
0231         return;
0232     }
0233 
0234     if (m_origPoint.size() == 1) {
0235         dstDev->makeCloneFromRough(srcDev, srcDev->extent());
0236         QPointF translate(QPointF(srcDev->x(), srcDev->y()) + m_transfPoint[0] - m_origPoint[0]);
0237         dstDev->moveTo(translate.toPoint());
0238         return;
0239     }
0240 
0241     const QRect srcBounds = srcDev->region().boundingRect();
0242 
0243     dstDev->clear();
0244 
0245     const int pixelPrecision = 8;
0246 
0247     FunctionTransformOp functionOp(m_warpMathFunction, m_origPoint, m_transfPoint, m_alpha);
0248     GridIterationTools::PaintDevicePolygonOp polygonOp(srcDev, dstDev);
0249     GridIterationTools::processGrid(polygonOp, functionOp,
0250                                     srcBounds, pixelPrecision);
0251 }
0252 
0253 #include "krita_utils.h"
0254 
0255 QRect KisWarpTransformWorker::approxChangeRect(const QRect &rc)
0256 {
0257     const qreal margin = 0.05;
0258 
0259     FunctionTransformOp functionOp(m_warpMathFunction, m_origPoint, m_transfPoint, m_alpha);
0260     QRect resultRect = KisAlgebra2D::approximateRectWithPointTransform(rc, functionOp);
0261 
0262     return KisAlgebra2D::blowRect(resultRect, margin);
0263 }
0264 
0265 QRect KisWarpTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds)
0266 {
0267     Q_UNUSED(rc);
0268     return fullBounds;
0269 }
0270 
0271 QImage KisWarpTransformWorker::transformQImage(WarpType warpType,
0272                                                const QVector<QPointF> &origPoint,
0273                                                const QVector<QPointF> &transfPoint,
0274                                                qreal alpha,
0275                                                const QImage& srcImage,
0276                                                const QPointF &srcQImageOffset,
0277                                                QPointF *newOffset)
0278 {
0279     KIS_ASSERT_RECOVER(srcImage.format() == QImage::Format_ARGB32) {
0280         return QImage();
0281     }
0282 
0283     WarpMathFunction warpMathFunction = &rigidTransformMath;
0284 
0285     switch (warpType) {
0286     case AFFINE_TRANSFORM:
0287         warpMathFunction = &affineTransformMath;
0288         break;
0289     case SIMILITUDE_TRANSFORM:
0290         warpMathFunction = &similitudeTransformMath;
0291         break;
0292     case RIGID_TRANSFORM:
0293         warpMathFunction = &rigidTransformMath;
0294         break;
0295     default:
0296         KIS_ASSERT_RECOVER(0 && "Unknown warp mode") { return QImage(); }
0297     }
0298 
0299     if (!warpMathFunction ||
0300         origPoint.isEmpty() ||
0301         origPoint.size() != transfPoint.size()) {
0302 
0303         return srcImage;
0304     }
0305 
0306     if (origPoint.size() == 1) {
0307         *newOffset = srcQImageOffset + (transfPoint[0] - origPoint[0]).toPoint();
0308         return srcImage;
0309     }
0310 
0311     FunctionTransformOp functionOp(warpMathFunction, origPoint, transfPoint, alpha);
0312 
0313     const QRectF srcBounds = QRectF(srcQImageOffset, srcImage.size());
0314     QRectF dstBounds;
0315 
0316     {
0317         QPolygonF testPoints;
0318         testPoints << srcBounds.topLeft();
0319         testPoints << srcBounds.topRight();
0320         testPoints << srcBounds.bottomRight();
0321         testPoints << srcBounds.bottomLeft();
0322         testPoints << srcBounds.topLeft();
0323 
0324         QPolygonF::iterator it = testPoints.begin() + 1;
0325 
0326         while (it != testPoints.end()) {
0327             it = testPoints.insert(it, 0.5 * (*it + *(it - 1)));
0328             it += 2;
0329         }
0330 
0331         it = testPoints.begin();
0332 
0333         while (it != testPoints.end()) {
0334             *it = functionOp(*it);
0335             ++it;
0336         }
0337 
0338         dstBounds = testPoints.boundingRect();
0339     }
0340 
0341     QPointF dstQImageOffset = dstBounds.topLeft();
0342     *newOffset = dstQImageOffset;
0343 
0344     QRect dstBoundsI = dstBounds.toAlignedRect();
0345     QImage dstImage(dstBoundsI.size(), srcImage.format());
0346     dstImage.fill(0);
0347 
0348     const int pixelPrecision = 32;
0349     GridIterationTools::QImagePolygonOp polygonOp(srcImage, dstImage, srcQImageOffset, dstQImageOffset);
0350     GridIterationTools::processGrid(polygonOp, functionOp, srcBounds.toAlignedRect(), pixelPrecision);
0351 
0352     return dstImage;
0353 }